Compare commits

...

10 Commits

Author SHA1 Message Date
guo-yao-whu d40a16a1b6 后端轮数
4 months ago
liulianbuqu 9f8fef15c7 删掉时间
4 months ago
weidoudou7 af00d8c8e8 辩论结束跳转复盘,并传递数据到复盘
4 months ago
liulianbuqu ec278aab9b 历史记录后端
4 months ago
liulianbuqu c6ec6954a6 历史记录后端
4 months ago
liulianbuqu 4d9b5a2e69 历史记录后端
4 months ago
liulianbuqu dbdd037ed7 Merge remote-tracking branch 'origin/main'
4 months ago
liulianbuqu 81ed1db029 历史记录后端
4 months ago
weidoudou7 6fb1111ce2 轮数选择弹窗UI,显示轮数进度
4 months ago
weidoudou7 5e2956da8d (database):
4 months ago

@ -0,0 +1,37 @@
package com.learning.newdemo.controller;
import com.learning.newdemo.common.Result;
import com.learning.newdemo.entity.DebateHistory;
import com.learning.newdemo.service.DebateHistoryService;
import com.learning.newdemo.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/debate-history")
@RequiredArgsConstructor
public class DebateHistoryController {
private final DebateHistoryService historyService;
private final JwtUtil jwtUtil;
@PostMapping
public Result<?> saveHistory(@RequestBody DebateHistory history,
@RequestHeader("Authorization") String token) {
Integer userId = jwtUtil.getUserIdFromToken(token);
if (userId == null) {
return Result.error(401, "无效的认证令牌");
}
history.setUserId(userId);
historyService.saveDebateHistory(history);
return Result.success();
}
@GetMapping
public Result<List<DebateHistory>> getHistories(@RequestHeader("Authorization") String token) {
Integer userId = jwtUtil.getUserIdFromToken(token);
return Result.success(historyService.getHistoriesByUser(userId));
}
}

@ -0,0 +1,112 @@
package com.learning.newdemo.controller;
import com.learning.newdemo.common.Result;
import com.learning.newdemo.entity.DebateHistory;
import com.learning.newdemo.service.DebateHistoryService;
import com.learning.newdemo.service.WxReviewService;
import com.learning.newdemo.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("api/review")
public class ReviewHistoryController {
private final WxReviewService wxReviewService;
private final DebateHistoryService historyService;
private final JwtUtil jwtUtil;
@PostMapping("/auto-save")
public Result<Map<String, Object>> autoSaveReview(
@RequestBody Map<String, String> params,
@RequestHeader("Authorization") String token) {
log.info("自动保存复盘请求: {}", params);
Integer userId = parseUserId(token);
if (userId == null) return Result.error(401, "认证失败");
String content = params.get("content");
String topic = params.get("topic");
String stance = params.get("stance");
if (content == null || content.isEmpty()) {
return Result.error("内容不能为空");
}
try {
String review = wxReviewService.GetReview(content);
if (review == null) return Result.error("复盘生成失败");
DebateHistory history = new DebateHistory();
history.setUserId(userId);
history.setTopic(topic);
history.setStance(stance);
history.setContent(content);
history.setReview(review);
history.setRounds(0); // 默认0轮可根据实际情况调整
historyService.saveDebateHistory(history);
Map<String, Object> data = new HashMap<>();
data.put("review", review);
data.put("historyId", history.getId());
return Result.success(data);
} catch (Exception e) {
log.error("复盘保存失败", e);
return Result.error("处理失败: " + e.getMessage());
}
}
@PostMapping("/save-only")
public Result<Map<String, Object>> saveOnly(
@RequestBody Map<String, String> params,
@RequestHeader("Authorization") String token) {
Integer userId = parseUserId(token);
if (userId == null) return Result.error(401, "认证失败");
String content = params.get("content");
String review = params.get("review");
String topic = params.get("topic");
String stance = params.get("stance");
if (content == null || review == null) {
return Result.error("内容和复盘结果不能为空");
}
try {
DebateHistory history = new DebateHistory();
history.setUserId(userId);
history.setTopic(topic);
history.setStance(stance);
history.setContent(content);
history.setReview(review);
history.setRounds(0);
historyService.saveDebateHistory(history);
Map<String, Object> data = new HashMap<>();
data.put("review", review);
data.put("historyId", history.getId());
return Result.success(data);
} catch (Exception e) {
log.error("保存失败", e);
return Result.error("保存失败: " + e.getMessage());
}
}
private Integer parseUserId(String token) {
try {
return jwtUtil.getUserIdFromToken(token);
} catch (Exception e) {
log.warn("Token解析失败", e);
return null;
}
}
}

@ -1,14 +1,17 @@
package com.learning.newdemo.controller;
import com.learning.newdemo.common.Result;
import com.learning.newdemo.entity.DebateHistory;
import com.learning.newdemo.service.DebateHistoryService;
import com.learning.newdemo.service.WxArgumentService;
import com.learning.newdemo.service.WxDebateService;
import com.learning.newdemo.service.WxReviewService;
import com.learning.newdemo.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -27,20 +30,23 @@ public class WxAIController {
@Autowired
private WxDebateService wxDebateService;
@Autowired
private DebateHistoryService debateHistoryService;
@Autowired
private JwtUtil jwtUtil;
private String topic;
private String stance;
private String content;
@PostMapping("/argument")
public Result<Map<String, Object>> getArgument(@RequestBody Map<String, String> params){
public Result<Map<String, Object>> getArgument(@RequestBody Map<String, String> params) {
topic = params.get("topic");
stance = params.get("stance");
if(topic == null || stance == null){
if(topic == null || stance == null) {
return Result.error("立论主题或者内容为空");
}
@ -54,20 +60,18 @@ public class WxAIController {
Map<String, Object> data = new HashMap<>();
data.put("argument", argument);
// 查看data
log.info("立论获取成功:{}", argument);
return Result.success(data);
}catch (Exception e){
} catch (Exception e) {
log.error("立论获取失败", e);
return Result.error("立论获取失败:" + e.getMessage());
}
}
@PostMapping("/review")
public Result<Map<String, Object>> review(@RequestBody Map<String, String> params){
public Result<Map<String, Object>> review(@RequestBody Map<String, String> params) {
log.info("请求内容: {}", params);
content = params.get("content");
try {
@ -78,35 +82,70 @@ public class WxAIController {
Map<String, Object> data = new HashMap<>();
data.put("review", review);
// 查看data
log.info("复盘获取成功:{}", review);
return Result.success(data);
}catch (Exception e){
} catch (Exception e) {
log.error("复盘获取失败", e);
return Result.error("复盘获取失败:" + e.getMessage());
}
}
@PostMapping("/debate")
public Result<Map<String, Object>> debate(@RequestBody Map<String, String> params){
public Result<Map<String, Object>> debate(@RequestBody Map<String, String> params,
@RequestHeader("Authorization") String token) {
log.info("请求内容: {}", params);
String history = params.get("history");
String userMessage = params.get("userMessage");
Integer currentRound = params.get("currentRound") != null ? Integer.parseInt(params.get("currentRound")) : 1;
String debateMode = params.get("debateMode"); // "ten" 或 "twenty"
// 根据辩论模式设置最大轮数
int maxRounds = "twenty".equals(debateMode) ? 20 : 10;
try {
String debate = wxDebateService.GetDebate(history, userMessage);
// 获取辩论结果
Map<String, Object> debateResult = wxDebateService.GetDebate(history, userMessage, currentRound, maxRounds);
String debate = (String) debateResult.get("response");
boolean isDebateEnded = (boolean) debateResult.get("isEnded");
if (debate == null) {
return Result.error("辩论获取失败");
}
Map<String, Object> data = new HashMap<>();
data.put("debate", debate);
// 查看data
log.info("辩论获取成功:{}", debate);
data.put("currentRound", currentRound);
data.put("maxRounds", maxRounds);
data.put("isEnded", isDebateEnded);
data.put("debateMode", debateMode);
// 如果辩论结束,自动保存历史记录
if (isDebateEnded) {
Integer userId = jwtUtil.getUserIdFromToken(token);
if (userId != null) {
DebateHistory debateHistory = new DebateHistory();
debateHistory.setUserId(userId);
debateHistory.setTopic(topic);
debateHistory.setStance(stance);
debateHistory.setContent(history + "\n\n用户发言: " + userMessage + "\n\nAI回复: " + debate);
debateHistory.setRounds(currentRound);
// 生成并保存复盘
String review = wxReviewService.GetReview(debateHistory.getContent());
debateHistory.setReview(review);
debateHistoryService.saveDebateHistory(debateHistory);
data.put("review", review);
data.put("historyId", debateHistory.getId());
}
}
log.info("辩论获取成功:{}", debate);
return Result.success(data);
}catch (Exception e){
} catch (Exception e) {
log.error("辩论获取失败", e);
return Result.error("辩论获取失败:" + e.getMessage());
}
}
}
}

@ -0,0 +1,21 @@
package com.learning.newdemo.entity;
import com.learning.newdemo.enums.Position;
import lombok.Data;
import java.util.Date;
@Data
public class ArgumentHistory {
private Integer id;
private Integer userId; // 关联用户ID
private String topic; // 辩题
private String argumentContent; // 立论内容
private Position position; // 用户持方(枚举类型)
private Date createTime; // 创建时间
}

@ -0,0 +1,23 @@
package com.learning.newdemo.entity;
import lombok.Data;
import java.util.Date;
@Data
public class DebateHistory {
private Integer id;
private Integer userId;
private String topic;
private String stance; // "正方"或"反方"
private String content; // 辩论内容JSON
private String review; // AI复盘内容
private Integer rounds;
// 注意setStance方法应该验证输入
public void setStance(String stance) {
if (!"正方".equals(stance) && !"反方".equals(stance)) {
throw new IllegalArgumentException("持方必须是'正方'或'反方'");
}
this.stance = stance;
}
}

@ -0,0 +1,36 @@
package com.learning.newdemo.enums;
public enum Position {
POSITIVE("正方"),
NEGATIVE("反方");
private final String name;
Position(String name) {
this.name = name;
}
public String getName() {
return name;
}
/**
*
* Position
* 使
*
* @param name
* @return null
*/
public static Position getPosition(String name) {
// 遍历Position枚举中的所有值
for (Position position : Position.values()) {
// 比较当前枚举实例的名称是否与传入的名称相同
if (position.getName().equals(name)) {
// 如果相同,则返回当前枚举实例
return position;
}
}
// 如果遍历结束后没有找到匹配的枚举则返回null
return null;
}
}

@ -0,0 +1,71 @@
package com.learning.newdemo.mapper;
import com.learning.newdemo.entity.ArgumentHistory;
import com.learning.newdemo.enums.Position;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ArgumentHistoryMapper {
/**
*
* @param argumentHistory
* @return
*/
int insert(ArgumentHistory argumentHistory);
/**
* ID
* @param userId ID
* @return
*/
List<ArgumentHistory> selectByUserId(@Param("userId") Integer userId);
/**
* ID
* @param userId ID
* @param position /
* @return
*/
List<ArgumentHistory> selectByUserAndPosition(
@Param("userId") Integer userId,
@Param("position") Position position);
/**
* ID
* @param id ID
* @return
*/
ArgumentHistory selectByPrimaryKey(@Param("id") Integer id);
/**
*
* @param userId ID
* @return
*/
ArgumentHistory selectLatestByUserId(@Param("userId") Integer userId);
/**
* IDi
*
* @param userId ID
* @param limit
* @return i
*/
List<ArgumentHistory> selectLatestByUserIdWithLimit(
@Param("userId") Integer userId,
@Param("limit") Integer limit);
/**
* IDi
*
* @param userId ID
* @param position
* @param limit
* @return i
*/
List<ArgumentHistory> selectLatestByUserAndPositionWithLimit(
@Param("userId") Integer userId,
@Param("position") Position position,
@Param("limit") Integer limit);
}

@ -0,0 +1,32 @@
package com.learning.newdemo.mapper;
import com.learning.newdemo.entity.DebateHistory;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface DebateHistoryMapper {
// 注意所有方法名必须与XML中的id严格匹配
int insert(DebateHistory debateHistory);
List<DebateHistory> selectByUserId(@Param("userId") Integer userId);
List<DebateHistory> selectLatestByUserId(
@Param("userId") Integer userId,
@Param("limit") Integer limit
);
DebateHistory selectByPrimaryKey(@Param("id") Integer id);
int updateReviewResult(
@Param("id") Integer id,
@Param("reviewResult") String reviewResult
);
// 关联查询方法
DebateHistory selectWithArgument(@Param("id") Integer id);
// 清理历史记录
int cleanOverflowHistories(@Param("userId") Integer userId);
}

@ -0,0 +1,9 @@
package com.learning.newdemo.service;
import com.learning.newdemo.entity.DebateHistory;
import java.util.List;
public interface DebateHistoryService {
void saveDebateHistory(DebateHistory history);
List<DebateHistory> getHistoriesByUser(Integer userId);
}

@ -1,5 +1,9 @@
package com.learning.newdemo.service;
import java.util.Map;
public interface WxDebateService {
String GetDebate(String history, String userMessage);
}
Map<String, Object> GetDebate(String history, String userMessage, int currentRound, int maxRounds);
}

@ -0,0 +1,31 @@
// DebateHistoryServiceImpl.java
package com.learning.newdemo.service.impl;
import com.learning.newdemo.entity.DebateHistory;
import com.learning.newdemo.mapper.DebateHistoryMapper;
import com.learning.newdemo.service.DebateHistoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional
public class DebateHistoryServiceImpl implements DebateHistoryService {
private final DebateHistoryMapper historyMapper;
@Override
public void saveDebateHistory(DebateHistory history) {
historyMapper.insert(history);
// 清理超出10条的旧记录
historyMapper.cleanOverflowHistories(history.getUserId());
}
@Override
public List<DebateHistory> getHistoriesByUser(Integer userId) {
return historyMapper.selectLatestByUserId(userId, 10);
}
}

@ -1,19 +1,26 @@
package com.learning.newdemo.service.impl;
import com.learning.newdemo.service.WxDebateService;
import com.learning.newdemo.service.WxReviewService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class WxDebateServiceImpl implements WxDebateService {
// 通过构造函数从IOC容器中注入RestTemplate
private final RestTemplate restTemplate;
@Autowired
private WxReviewService wxReviewService;
@Value("${ai.debate.body.message.content-sys}") private String contentSys;
@Value("${ai.debate.header.Authorization}") private String authorizationHeader;
@ -36,18 +43,29 @@ public class WxDebateServiceImpl implements WxDebateService {
}
@Override
public String GetDebate(String history, String userMessage){
public Map<String, Object> GetDebate(String history, String userMessage, int currentRound, int maxRounds) {
Map<String, Object> result = new HashMap<>();
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", authorizationHeader);
// 构建系统提示,包含轮数信息
String systemPrompt = contentSys + String.format("\n当前是第%d轮辩论共%d轮。", currentRound, maxRounds);
if (currentRound >= maxRounds) {
systemPrompt += "\n这是最后一轮辩论请给出总结性回复。";
} else if (maxRounds == 10) {
systemPrompt += "\n这是10回合快速对辩模式请保持回答简洁有力。";
} else if (maxRounds == 20) {
systemPrompt += "\n这是20回合深度对辩模式可以进行更深入的分析和讨论。";
}
StringBuilder requestBodyBuilder = new StringBuilder();
requestBodyBuilder.append("{")
.append("\"messages\": [")
.append("{")
.append("\"role\": \"").append(roleSys).append("\",")
.append("\"content\": \"").append(escapeJson(contentSys)).append("\"")
.append("\"content\": \"").append(escapeJson(systemPrompt)).append("\"")
.append("},")
.append("{")
.append("\"role\": \"").append(roleUser).append("\",")
@ -74,10 +92,21 @@ public class WxDebateServiceImpl implements WxDebateService {
log.info("请求体:{}", requestBody);
HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
return response.getBody();
} catch (Exception e){
// 判断辩论是否结束
boolean isDebateEnded = currentRound >= maxRounds;
result.put("response", response.getBody());
result.put("currentRound", currentRound);
result.put("maxRounds", maxRounds);
result.put("isEnded", isDebateEnded);
return result;
} catch (Exception e) {
log.error("模拟辩论获取失败", e);
return null;
result.put("response", null);
result.put("isEnded", false);
return result;
}
}
@ -90,4 +119,4 @@ public class WxDebateServiceImpl implements WxDebateService {
.replace("\r", "\\r")
.replace("\t", "\\t");
}
}
}

@ -109,4 +109,31 @@ public class JwtUtil {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
/**
* TokenClaims
* @param token JWT
* @return Claims
*/
public Claims parseToken(String token) {
// 去除可能的"Bearer "前缀
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
}
return getClaimsFromToken(token);
}
/**
* tokenID
* @param token JWT
* @return ID
*/
public Integer getUserIdFromToken(String token) {
try {
Claims claims = parseToken(token);
return claims.get("userId", Integer.class);
} catch (Exception e) {
return null;
}
}
}

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learning.newdemo.mapper.ArgumentHistoryMapper">
<resultMap id="BaseResultMap" type="com.learning.newdemo.entity.ArgumentHistory">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="user_id" property="userId" jdbcType="INTEGER"/>
<result column="topic" property="topic" jdbcType="VARCHAR"/>
<result column="argument_content" property="argumentContent" jdbcType="LONGVARCHAR"/>
<result column="position" property="position"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"
jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id, user_id, topic, argument_content, position, create_time
</sql>
<!-- 插入立论记录 -->
<insert id="insert" parameterType="com.learning.newdemo.entity.ArgumentHistory"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO argument_history (
user_id, topic, argument_content, position
)
VALUES (
#{userId,jdbcType=INTEGER},
#{topic,jdbcType=VARCHAR},
#{argumentContent,jdbcType=LONGVARCHAR},
#{position,jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
</insert>
<!-- 根据用户ID查询 -->
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
ORDER BY create_time DESC
</select>
<!-- 根据用户ID和持方查询 -->
<select id="selectByUserAndPosition" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
AND position = #{position,jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
ORDER BY create_time DESC
</select>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE id = #{id,jdbcType=INTEGER}
</select>
<!-- 获取用户最新立论记录 -->
<select id="selectLatestByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
ORDER BY create_time DESC
LIMIT 1
</select>
<!-- 根据用户ID查询最新的i条记录 -->
<select id="selectLatestByUserIdWithLimit" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
ORDER BY create_time DESC
LIMIT #{limit,jdbcType=INTEGER}
</select>
<!-- 根据用户ID和持方查询最新的i条记录 -->
<select id="selectLatestByUserAndPositionWithLimit" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
<if test="position != null">
AND position = #{position,jdbcType=VARCHAR,
typeHandler=org.apache.ibatis.type.EnumTypeHandler}
</if>
ORDER BY create_time DESC
LIMIT #{limit,jdbcType=INTEGER}
</select>
</mapper>

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learning.newdemo.mapper.DebateHistoryMapper">
<resultMap id="BaseResultMap" type="com.learning.newdemo.entity.DebateHistory">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="user_id" property="userId" jdbcType="INTEGER"/>
<result column="argument_id" property="argumentId" jdbcType="INTEGER"/>
<result column="topic" property="topic" jdbcType="VARCHAR"/>
<result column="position" property="position"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"
jdbcType="VARCHAR"/>
<result column="total_rounds" property="totalRounds" jdbcType="SMALLINT"/>
<result column="debate_content" property="debateContent" jdbcType="LONGVARCHAR"/>
<result column="review_result" property="reviewResult" jdbcType="LONGVARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>
<resultMap id="WithArgumentResultMap" type="com.learning.newdemo.entity.DebateHistory"
extends="BaseResultMap">
<association property="argumentHistory" javaType="com.learning.newdemo.entity.ArgumentHistory">
<id column="arg_id" property="id"/>
<result column="arg_user_id" property="userId"/>
<result column="arg_topic" property="topic"/>
<result column="arg_content" property="argumentContent"/>
<result column="arg_position" property="position"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
<result column="arg_create_time" property="createTime"/>
</association>
</resultMap>
<sql id="Base_Column_List">
id, user_id, argument_id, topic, position, total_rounds,
debate_content, review_result, create_time
</sql>
<sql id="WithArgument_Column_List">
dh.id, dh.user_id, dh.argument_id, dh.topic, dh.position,
dh.total_rounds, dh.debate_content, dh.review_result, dh.create_time,
ah.id as arg_id, ah.user_id as arg_user_id, ah.topic as arg_topic,
ah.argument_content as arg_content, ah.position as arg_position,
ah.create_time as arg_create_time
</sql>
<insert id="insert" parameterType="com.learning.newdemo.entity.DebateHistory"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO debate_history (
user_id, argument_id, topic, position,
total_rounds, debate_content, create_time
) VALUES (
#{userId}, #{argumentId}, #{topic},
#{position,typeHandler=org.apache.ibatis.type.EnumTypeHandler},
#{totalRounds}, #{debateContent}, NOW()
)
</insert>
<!-- 清理历史记录保留最近10条 -->
<delete id="cleanOverflowHistories">
DELETE FROM debate_history
WHERE user_id = #{userId}
AND id NOT IN (
SELECT id FROM (
SELECT id FROM debate_history
WHERE user_id = #{userId}
ORDER BY create_time DESC
LIMIT 10
) t
)
</delete>
</mapper>

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learning.newdemo.mapper.ArgumentHistoryMapper">
<resultMap id="BaseResultMap" type="com.learning.newdemo.entity.ArgumentHistory">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="user_id" property="userId" jdbcType="INTEGER"/>
<result column="topic" property="topic" jdbcType="VARCHAR"/>
<result column="argument_content" property="argumentContent" jdbcType="LONGVARCHAR"/>
<result column="position" property="position"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"
jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id, user_id, topic, argument_content, position, create_time
</sql>
<!-- 插入立论记录 -->
<insert id="insert" parameterType="com.learning.newdemo.entity.ArgumentHistory"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO argument_history (
user_id, topic, argument_content, position
)
VALUES (
#{userId,jdbcType=INTEGER},
#{topic,jdbcType=VARCHAR},
#{argumentContent,jdbcType=LONGVARCHAR},
#{position,jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
)
</insert>
<!-- 根据用户ID查询 -->
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
ORDER BY create_time DESC
</select>
<!-- 根据用户ID和持方查询 -->
<select id="selectByUserAndPosition" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
AND position = #{position,jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
ORDER BY create_time DESC
</select>
<!-- 根据主键查询 -->
<select id="selectByPrimaryKey" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE id = #{id,jdbcType=INTEGER}
</select>
<!-- 获取用户最新立论记录 -->
<select id="selectLatestByUserId" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
ORDER BY create_time DESC
LIMIT 1
</select>
<!-- 根据用户ID查询最新的i条记录 -->
<select id="selectLatestByUserIdWithLimit" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
ORDER BY create_time DESC
LIMIT #{limit,jdbcType=INTEGER}
</select>
<!-- 根据用户ID和持方查询最新的i条记录 -->
<select id="selectLatestByUserAndPositionWithLimit" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM argument_history
WHERE user_id = #{userId,jdbcType=INTEGER}
<if test="position != null">
AND position = #{position,jdbcType=VARCHAR,
typeHandler=org.apache.ibatis.type.EnumTypeHandler}
</if>
ORDER BY create_time DESC
LIMIT #{limit,jdbcType=INTEGER}
</select>
</mapper>

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.learning.newdemo.mapper.DebateHistoryMapper">
<resultMap id="BaseResultMap" type="com.learning.newdemo.entity.DebateHistory">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="user_id" property="userId" jdbcType="INTEGER"/>
<result column="argument_id" property="argumentId" jdbcType="INTEGER"/>
<result column="topic" property="topic" jdbcType="VARCHAR"/>
<result column="position" property="position"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"
jdbcType="VARCHAR"/>
<result column="total_rounds" property="totalRounds" jdbcType="SMALLINT"/>
<result column="debate_content" property="debateContent" jdbcType="LONGVARCHAR"/>
<result column="review_result" property="reviewResult" jdbcType="LONGVARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
</resultMap>
<resultMap id="WithArgumentResultMap" type="com.learning.newdemo.entity.DebateHistory"
extends="BaseResultMap">
<association property="argumentHistory" javaType="com.learning.newdemo.entity.ArgumentHistory">
<id column="arg_id" property="id"/>
<result column="arg_user_id" property="userId"/>
<result column="arg_topic" property="topic"/>
<result column="arg_content" property="argumentContent"/>
<result column="arg_position" property="position"
typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
<result column="arg_create_time" property="createTime"/>
</association>
</resultMap>
<sql id="Base_Column_List">
id, user_id, argument_id, topic, position, total_rounds,
debate_content, review_result, create_time
</sql>
<sql id="WithArgument_Column_List">
dh.id, dh.user_id, dh.argument_id, dh.topic, dh.position,
dh.total_rounds, dh.debate_content, dh.review_result, dh.create_time,
ah.id as arg_id, ah.user_id as arg_user_id, ah.topic as arg_topic,
ah.argument_content as arg_content, ah.position as arg_position,
ah.create_time as arg_create_time
</sql>
<insert id="insert" parameterType="com.learning.newdemo.entity.DebateHistory"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO debate_history (
user_id, argument_id, topic, position,
total_rounds, debate_content, create_time
) VALUES (
#{userId}, #{argumentId}, #{topic},
#{position,typeHandler=org.apache.ibatis.type.EnumTypeHandler},
#{totalRounds}, #{debateContent}, NOW()
)
</insert>
<!-- 清理历史记录保留最近10条 -->
<delete id="cleanOverflowHistories">
DELETE FROM debate_history
WHERE user_id = #{userId}
AND id NOT IN (
SELECT id FROM (
SELECT id FROM debate_history
WHERE user_id = #{userId}
ORDER BY create_time DESC
LIMIT 10
) t
)
</delete>
</mapper>

@ -2,12 +2,7 @@
<view class="debate-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>
<!-- 顶部卡片 - 可收缩 -->
@ -15,27 +10,18 @@
<view class="card-header" @click="toggleCard">
<view class="card-icon-wrapper">
<view class="card-icon">
<image
src="/static/icons/robot-2-line.png"
mode="aspectFit"
></image>
<image src="/static/icons/robot-2-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="card-text">
<view class="card-title">模拟辩论</view>
<view class="card-content" v-if="!isCardCollapsed"
>与AI进行实时辩论对练提升应变能力</view
>
<view class="card-content" v-if="!isCardCollapsed">AI</view>
</view>
<view class="collapse-icon">
<image
:src="
isCardCollapsed
? '/static/icons/arrow-down-s-line.png'
: '/static/icons/arrow-up-s-line.png'
"
mode="aspectFit"
></image>
<image :src="isCardCollapsed
? '/static/icons/arrow-down-s-line.png'
: '/static/icons/arrow-up-s-line.png'
" mode="aspectFit"></image>
</view>
</view>
</view>
@ -43,32 +29,17 @@
<!-- 辩论主题选择器 -->
<view class="topic-selector" v-if="!isCardCollapsed">
<scroll-view scroll-x class="topic-scroll" show-scrollbar="false">
<view
v-for="(topic, idx) in debateTopics"
:key="idx"
:class="['topic-pill', { active: selectedTopic === idx }]"
@click="selectTopic(idx)"
>
<view v-for="(topic, idx) in debateTopics" :key="idx" :class="['topic-pill', { active: selectedTopic === idx }]"
@click="selectTopic(idx)">
{{ topic }}
</view>
</scroll-view>
</view>
<!-- 聊天展示区 -->
<scroll-view
class="chat-area"
scroll-y
:scroll-into-view="scrollToView"
scroll-with-animation
>
<view
v-for="(msg, index) in messages"
:key="index"
:id="'msg' + index"
class="chat-message"
:class="msg.role === 'user' ? 'from-user' : 'from-ai'"
@longpress="onLongPress()"
>
<scroll-view class="chat-area" scroll-y :scroll-into-view="scrollToView" scroll-with-animation>
<view v-for="(msg, index) in messages" :key="index" :id="'msg' + index" class="chat-message"
:class="msg.role === 'user' ? 'from-user' : 'from-ai'" @longpress="onLongPress()">
<view class="avatar" v-if="msg.role === 'ai'">AI</view>
<view class="bubble">
<block v-if="msg.loading">
@ -86,22 +57,41 @@
</view>
</scroll-view>
<!-- 轮数选择与进度条区域 -->
<view class="rounds-control" :class="{ 'rounds-selected': debateRounds > 0 }">
<!-- 未选择轮数时显示选择按钮 -->
<view v-if="roundsSelected === false" class="select-rounds-btn" @click="showRoundsPopup = true">
<image src="/static/icons/repeat-line.png" mode="aspectFit"></image>
<text>选择辩论轮数</text>
</view>
<!-- 已选择轮数时显示进度条 -->
<view v-else class="rounds-selected-container">
<view class="rounds-label" @click="showRoundsPopup = true">
<image src="/static/icons/repeat-line.png" mode="aspectFit"></image>
<text>{{ debateRounds }}回合</text>
</view>
<view class="progress-container">
<view class="progress-bar">
<view class="progress-inner" :style="{ width: progressPercentage + '%' }"></view>
</view>
<view class="rounds-counter">{{ currentRound }}/{{ debateRounds }}</view>
<!-- 添加复盘按钮 -->
<view v-if="showReviewButton" class="review-button" @click="showReviewModal">
<image src="/static/icons/file-chart-line.png" mode="aspectFit"></image>
</view>
</view>
</view>
</view>
<!-- 输入框与发送按钮 -->
<view class="chat-input">
<view class="input-group">
<view class="textarea-container">
<textarea
class="textarea-box"
v-model="content"
placeholder="输入你的辩论观点与AI进行思辨交锋"
auto-height
maxlength="1000"
/>
<textarea class="textarea-box" v-model="content" placeholder="输入你的辩论观点与AI进行思辨交锋" auto-height
maxlength="1000" />
<button class="send-button-embedded" @click="sendMessage()">
<image
src="/static/icons/send-plane-fill.png"
mode="aspectFit"
></image>
<image src="/static/icons/send-plane-fill.png" mode="aspectFit"></image>
</button>
</view>
</view>
@ -110,13 +100,56 @@
<!-- 底部安全区域 -->
<view class="safe-area-bottom"></view>
<Popup
:visible="showSheet"
buttonText="开始复盘"
buttonIcon="🧠"
@close="showSheet = false"
@click="handleSheetClick"
/>
<Popup :visible="showSheet" buttonText="开始复盘" buttonIcon="🧠" @close="showSheet = false"
@click="handleSheetClick" />
<!-- 轮数选择弹窗 -->
<view v-if="showRoundsPopup" class="rounds-popup-mask" @click="showRoundsPopup = false">
<view class="rounds-popup" @click.stop>
<view class="popup-header">
<view class="popup-title">选择辩论轮数</view>
<view class="close-icon" @click="showRoundsPopup = false">
<image src="/static/icons/close-line.png" mode="aspectFit"></image>
</view>
</view>
<view class="rounds-options">
<view class="round-option" :class="{ selected: selectedOption === 1 }" @click="selectRounds(1)">
<view class="option-icon"></view>
<view class="option-text">
<view class="option-title">快速交锋</view>
<view class="option-desc">10回合 · 适合快速练习</view>
</view>
</view>
<view class="round-option" :class="{ selected: selectedOption === 2 }" @click="selectRounds(2)">
<view class="option-icon">🔍</view>
<view class="option-text">
<view class="option-title">深度辩论</view>
<view class="option-desc">20回合 · 深入探讨话题</view>
</view>
</view>
</view>
<button class="confirm-button-selectRounds" @click="confirmRounds"></button>
</view>
</view>
<!-- 复盘确认弹窗 -->
<view v-if="showReviewConfirm" class="review-confirm-mask" @click="showReviewConfirm = false">
<view class="review-confirm-popup" @click.stop>
<view class="popup-header">
<view class="popup-title">辩论完成</view>
</view>
<view class="popup-content">
<text>辩论已完成{{ debateRounds }}回合是否立即进行复盘分析</text>
</view>
<view class="popup-buttons">
<button class="cancel-button" @click="showReviewConfirm = false">稍后再说</button>
<button class="confirm-button-gotoReview" @click="startReview"></button>
</view>
</view>
</view>
</view>
</template>
@ -136,13 +169,20 @@ export default {
mounted() {
const pinia = this.$pinia;
const store = useArgumentStore(pinia);
this.content = store.selectedArgument.content;
if (store.selectedArgument && store.selectedArgument.content) {
this.content = store.selectedArgument.content;
} else {
this.content = "";
}
//
this.showRoundsPopup = true;
},
data() {
return {
showSheet:false,
StoreHistory:"",
showSheet: false,
StoreHistory: "",
input: "",
messages: [
{
@ -151,6 +191,17 @@ export default {
"在这里你可以和我进行辩论,现在,辩论开始!请选择一个话题或提出你想讨论的观点。",
},
],
//
showRoundsPopup: false, //
roundsSelected: false, //
debateRounds: 0, //
currentRound: 0, //
selectedOption: 0, //
showReviewConfirm: false,//
showReviewButton: false,//
scrollToView: "",
content: "",
debateTopics: [
@ -165,6 +216,26 @@ export default {
isCardCollapsed: false, //
};
},
computed: {
//
progressPercentage() {
if (this.debateRounds === 0) return 0;
return (this.currentRound / this.debateRounds) * 100;
},
},
watch: {
//
currentRound(newVal) {
if (newVal >= this.debateRounds && this.debateRounds > 0) {
//
setTimeout(() => {
this.showReviewConfirm = true;
}, 1000);
}
}
},
methods: {
handleSheetClick() {
const pinia = this.$pinia;
@ -244,7 +315,28 @@ export default {
},
async sendMessage() {
if (!this.content.trim()) return;
//
if (!this.roundsSelected) {
uni.showToast({
title: "请选择辩论轮数",
icon: "none",
duration: 2000,
});
return;
}
//
if (this.currentRound >= this.debateRounds) {
uni.showToast({
title: "辩论轮数已达上限",
icon: "none",
duration: 2000,
});
return;
}
//
if (!this.content.trim() || this.currentRound >= this.debateRounds) return;
//
this.messages.push({
@ -279,6 +371,25 @@ export default {
// loading AI
this.messages.splice(aiIndex, 1, { role: "ai", content: reply });
this.scrollToBottom();
//----------------------
//
this.currentRound++;
//
if (this.currentRound >= this.debateRounds) {
this.messages.push({
role: "ai",
content: `本轮辩论已完成(${this.debateRounds}回合)。您可以点击消息进行复盘分析。`,
});
};
if (this.currentRound >= this.debateRounds) {
//
this.showReviewButton = true;
return;
}
},
async callAI(history, content) {
@ -320,6 +431,84 @@ export default {
this.scrollToView = "msg" + (this.messages.length - 1);
});
},
//
selectRounds(type) {
this.selectedOption = type;
},
//
confirmRounds() {
if (this.selectedOption === 0) {
uni.showToast({
title: '请选择轮数',
icon: 'none',
duration: 2000
});
return;
}
//
if (this.roundsSelected) {
uni.showToast({
title: '已选择轮数',
icon: 'none',
duration: 2000
});
return;
}
//
if (this.selectedOption === 1) {
this.debateRounds = 10;
} else if (this.selectedOption === 2) {
this.debateRounds = 20;
}
this.currentRound = 0;
this.roundsSelected = true;
this.showRoundsPopup = false;
this.showReviewButton = false;
this.showReviewConfirm = false;
//
this.message = [{
role: 'ai',
content: `已选择 ${this.debateRounds} 回合辩论!请分享你的观点,我们开始吧!`
}];
this.scrollToBottom();
},
//
startReview() {
// //
// let history = this.messages
// .filter((msg) => !msg.loading)
// .map((msg) => {
// if (msg.role === 'user') {
// return '${ msg.content}';
// } else if (msg.role === 'ai') {
// return 'AI ${ msg.content}';
// }
// return '';
// }).join('\n');
//
// //store
// const pinia = this.$pinia;
// const store = useDebateStore(pinia);
// store.setDebate(history);
// //
// this.$emit('tab-change', 3);
this.onLongPress();
//
this.showReviewConfirm = false;
},
//
showReviewModal() {
this.showReviewConfirm = true;
},
},
};
</script>
@ -334,7 +523,8 @@ export default {
background: linear-gradient(135deg, #4338ca 0%, #7c3aed 100%);
overflow-x: hidden;
box-sizing: border-box;
padding-bottom: 180rpx; /* 为底部TabBar留出空间 */
padding-bottom: 180rpx;
/* 为底部TabBar留出空间 */
position: relative;
}
@ -352,11 +542,9 @@ 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;
/* 添加过渡效果 */
@ -364,16 +552,20 @@ export default {
}
@keyframes float {
0%,
100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(5%, 10%) scale(1.05);
}
50% {
transform: translate(10%, 5%) scale(0.95);
}
75% {
transform: translate(5%, 15%) scale(1.1);
}
@ -426,6 +618,7 @@ export default {
opacity: 0;
transform: translateY(-30rpx);
}
to {
opacity: 1;
transform: translateY(0);
@ -462,12 +655,10 @@ export default {
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(
to bottom right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%
);
background: linear-gradient(to bottom right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0) 100%);
transform: rotate(45deg);
animation: shine 3s infinite;
}
@ -476,6 +667,7 @@ export default {
0% {
transform: translateX(-100%) rotate(45deg);
}
20%,
100% {
transform: translateX(100%) rotate(45deg);
@ -628,7 +820,8 @@ export default {
word-break: break-word;
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
position: relative;
transition: all 0.3s ease; /* 添加过渡效果 */
transition: all 0.3s ease;
/* 添加过渡效果 */
}
.from-user .bubble {
@ -647,7 +840,8 @@ export default {
height: 0;
border-left: 16rpx solid #f59e0b;
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
transition: all 0.3s ease;
/* 添加过渡效果 */
}
.from-ai .bubble {
@ -666,7 +860,8 @@ export default {
height: 0;
border-right: 16rpx solid rgba(255, 255, 255, 0.2);
border-top: 16rpx solid transparent;
transition: all 0.3s ease; /* 添加过渡效果 */
transition: all 0.3s ease;
/* 添加过渡效果 */
}
/* 加载动画 */
@ -695,16 +890,143 @@ export default {
}
@keyframes bounce {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
/* 轮数控制区域 ------------------------*/
.rounds-control {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 16rpx;
margin: 0 auto 20rpx;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
animation: slideUp 0.4s ease-out;
width: 70%;
transition: all 0.5s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 选择轮数按钮样式 */
.select-rounds-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 20rpx;
background: rgba(104, 57, 224, 0.3);
border-radius: 16rpx;
font-size: 28rpx;
color: white;
font-weight: 500;
}
.select-rounds-btn image {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
filter: brightness(0) invert(1);
}
.rounds-selected-container {
display: flex;
align-items: center;
width: 100%;
}
.rounds-label {
display: flex;
align-items: center;
padding: 10rpx 20rpx;
background: rgba(104, 57, 224, 0.3);
border-radius: 12rpx;
margin-right: 20rpx;
font-size: 26rpx;
color: white;
font-weight: 500;
min-width: 140rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.rounds-label image {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
filter: brightness(0) invert(1);
}
.progress-container {
flex: 1;
display: flex;
align-items: center;
}
.progress-bar {
flex: 1;
height: 16rpx;
background: rgba(255, 255, 255, 0.2);
border-radius: 8rpx;
overflow: hidden;
margin-right: 20rpx;
}
/* 进度条动画 */
.progress-inner {
height: 100%;
background: linear-gradient(90deg, #f59e0b, #fcd34d);
border-radius: 8rpx;
transition: width 0.5s ease;
}
/* 进度条加载动画 */
@keyframes progress-grow {
from {
width: 0;
}
to {
width: v-bind('progressPercentage + "%"');
}
}
.progress-inner {
animation: progress-grow 0.8s ease-out forwards;
}
.rounds-counter {
font-size: 28rpx;
font-weight: 600;
color: white;
min-width: 80rpx;
text-align: center;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
}
/* 输入区域 */
.chat-input {
display: flex;
@ -721,13 +1043,15 @@ export default {
.textarea-box {
background: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 16rpx 20rpx 16rpx 90rpx; /* 增加左侧内边距为发送按钮留出空间 */
padding: 16rpx 20rpx 16rpx 90rpx;
/* 增加左侧内边距为发送按钮留出空间 */
font-size: 28rpx;
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.3);
transition: all 0.3s;
min-height: 70rpx;
max-height: 180rpx; /* 限制最大高度 */
max-height: 180rpx;
/* 限制最大高度 */
width: 100%;
box-sizing: border-box;
}
@ -797,14 +1121,252 @@ export default {
.send-button-embedded image {
width: 40rpx;
height: 40rpx;
filter: invert(72%) sepia(87%) saturate(1242%) hue-rotate(325deg)
brightness(101%) contrast(96%);
filter: invert(72%) sepia(87%) saturate(1242%) hue-rotate(325deg) brightness(101%) contrast(96%);
}
/* 底部安全区域 */
.safe-area-bottom {
height: 20rpx;
height: calc(20rpx + constant(safe-area-inset-bottom)); /* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom)); /* iOS 11.2+ */
height: calc(20rpx + constant(safe-area-inset-bottom));
/* iOS 11.0-11.2 */
height: calc(20rpx + env(safe-area-inset-bottom));
/* iOS 11.2+ */
}
/* 轮数选择弹窗样式 */
.rounds-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.rounds-popup {
width: 80%;
max-width: 600rpx;
background: white;
border-radius: 28rpx;
overflow: hidden;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
animation: scaleIn 0.3s ease-out;
}
@keyframes scaleIn {
from {
transform: scale(0.9);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
background: #7C3AED;
}
.popup-title {
font-size: 36rpx;
font-weight: 700;
color: white;
}
.close-icon {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.close-icon image {
width: 30rpx;
height: 30rpx;
filter: brightness(0) invert(1);
}
.rounds-options {
padding: 30rpx;
}
.round-option {
display: flex;
align-items: center;
padding: 30rpx;
border-radius: 20rpx;
background: #f8f9ff;
border: 2rpx solid #e6e6ff;
margin-bottom: 25rpx;
transition: all 0.3s;
}
.round-option.selected {
background: #6839E0;
border-color: #6839E0;
transform: translateY(-5rpx);
box-shadow: 0 8rpx 20rpx rgba(104, 57, 224, 0.3);
width: auto;
margin: 0 30rpx 20rpx;
}
.option-icon {
font-size: 50rpx;
margin-right: 25rpx;
width: 80rpx;
text-align: center;
}
.round-option.selected .option-icon {
color: white;
}
.option-title {
font-size: 32rpx;
font-weight: 600;
margin-bottom: 8rpx;
}
.option-desc {
font-size: 26rpx;
color: #666;
}
.round-option.selected .option-title,
.round-option.selected .option-desc {
color: white;
}
.confirm-button-selectRounds {
background: #6839E0;
color: white;
border-radius: 0;
height: 100rpx;
line-height: 100rpx;
font-size: 32rpx;
font-weight: 600;
border: none;
}
/* 复盘确认弹窗样式 */
.review-confirm-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
animation: fadeIn 0.3s;
}
.review-confirm-popup {
width: 80%;
max-width: 600rpx;
background: white;
border-radius: 28rpx;
overflow: hidden;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
}
.popup-header {
padding: 30rpx;
background: #7C3AED;
text-align: center;
}
.popup-title {
font-size: 36rpx;
font-weight: 700;
color: white;
}
.popup-content {
padding: 40rpx 30rpx;
font-size: 30rpx;
color: #333;
text-align: center;
line-height: 1.6;
}
.popup-buttons {
display: flex;
padding: 20rpx;
border-top: 1rpx solid #eee;
}
.cancel-button {
flex: 1;
background: #f5f5f5;
color: #666;
border-radius: 12rpx;
margin-right: 15rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
}
.confirm-button-gotoReview {
flex: 1;
background: #6839E0;
color: white;
border-radius: 12rpx;
margin-left: 15rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
}
/* 复盘按钮样式 */
.review-button {
width: 50rpx;
height: 50rpx;
margin-left: 20rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 8rpx;
}
.review-button image {
width: 100%;
height: 100%;
filter: brightness(0) invert(1);
}
/* 调整进度条容器布局 */
.progress-container {
flex: 1;
display: flex;
align-items: center;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -1,6 +1,6 @@
create database if not exists wx_miniapp default charset utf8mb4;
create database if not exists wx_miniApp default charset utf8mb4;
use wx_miniapp;
use wx_miniApp;
CREATE TABLE IF NOT EXISTS `wx_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
@ -16,4 +16,19 @@ CREATE TABLE IF NOT EXISTS `wx_user` (
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_openid` (`openid`) COMMENT 'openid唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信用户表';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信用户表';
CREATE TABLE IF NOT EXISTS `debate_history` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '关联用户ID',
`topic` varchar(255) NOT NULL COMMENT '辩题',
`stance` enum('正方','反方') NOT NULL COMMENT '持方',
`content` text COMMENT '辩论内容JSON',
`review` text COMMENT 'AI复盘内容',
`rounds` smallint(6) DEFAULT 0 COMMENT '辩论轮数',
PRIMARY KEY (`id`),
KEY `idx_user_time` (`user_id`, `create_time`),
CONSTRAINT `fk_user_history` FOREIGN KEY (`user_id`) REFERENCES `wx_user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='辩论历史记录表';

@ -0,0 +1,31 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
projectJDK: 17 #(Applied in CI/CD pipeline)
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-jvm-community:latest
Loading…
Cancel
Save