怎么长期记忆区不显示单词卡片，但long_term_memory已经存有数据了
package com.example.springboot.mapper;

import com.example.springboot.entity.LongTermMemory;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

@Mapper
public interface LongTermMemoryMapper {
    int insertLongTermMemory(LongTermMemory ltm);

    List<Integer> selectExistingWordIds(@Param("userId") Integer userId, @Param("wordIds") List<Integer> wordIds);

    List<LongTermMemory> selectDueReviews(@Param("userId") Integer userId,
                                          @Param("now") LocalDateTime now,
                                          @Param("offset") Integer offset,
                                          @Param("size") Integer size,
                                          @Param("longmemorySeen") Integer longmemorySeen);

    int updateLongTermMemory(LongTermMemory ltm);

    LongTermMemory selectById(@Param("id") Integer id);
}
package com.example.springboot.controller;

import com.example.springboot.entity.LongTermMemory;
import com.example.springboot.entity.Study;
import com.example.springboot.service.StudyService;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/study")
public class StudyController {

    @Resource
    private StudyService studyService;
    //  用户选择单词
    @PostMapping("/select-words")
    public ResponseEntity<?> userSelectWords(@RequestBody Map<String, Object> payload) {
        Integer userId = (Integer) payload.get("userId");
        List<Integer> wordIds = (List<Integer>) payload.get("wordIds");

        if (userId == null || wordIds == null || wordIds.isEmpty()) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("参数错误");
        }

        int insertedCount = studyService.insertUserSelectedWords(userId, wordIds);

        if (insertedCount == 0) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("所有单词已存在，无需再添加");
        }

        return ResponseEntity.ok("成功添加 " + insertedCount + " 个新单词");
    }

   //  获取已经选择的单词列表
    @GetMapping("/select-words")
    public ResponseEntity<?> getSelectedWords(@RequestParam(value = "userId", required = false) String userIdStr) {
        if (userIdStr == null || !userIdStr.matches("\\d+")) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("缺少或非法的 userId");
        }

        Integer userId = Integer.valueOf(userIdStr);
        return ResponseEntity.ok(studyService.getSelectedWordsByUserId(userId));
    }

    // 获取用户可选的未学习单词（用于前端弹窗选择）
    @GetMapping("/pending-words")
    public ResponseEntity<?> getPendingWords(
            @RequestParam("userId") Integer userId,
            @RequestParam(value = "limit", defaultValue = "100") Integer limit) {
        return ResponseEntity.ok(studyService.getPendingWords(userId, limit));
    }

    // 删除用户选择的单词
    @PostMapping("/remove-word")
    public ResponseEntity<?> removeWord(@RequestBody Map<String, Object> payload) {
        Integer userId = (Integer) payload.get("userId");
        Integer wordId = (Integer) payload.get("wordId");
        if (userId == null || wordId == null) {
            return ResponseEntity.badRequest().body("参数错误");
        }
        int rows = studyService.deleteWord(userId, wordId);
        if (rows > 0) {
            return ResponseEntity.ok("删除成功");
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("未找到单词");
        }
    }

    //  批量删除用户选择的单词
    @PostMapping("/remove-words")
    public ResponseEntity<?> removeWords(@RequestBody Map<String, Object> payload) {
        Integer userId = (Integer) payload.get("userId");
        List<Integer> wordIds = (List<Integer>) payload.get("wordIds");
        if (userId == null || wordIds == null || wordIds.isEmpty()) {
            return ResponseEntity.badRequest().body("参数错误");
        }
        int rows = studyService.deleteWords(userId, wordIds);
        if (rows > 0) {
            return ResponseEntity.ok("批量删除成功");
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("未找到单词");
        }
    }

    // 获取当前轮复习列表
    @GetMapping("/review-list")
    public ResponseEntity<?> getReviewList(@RequestParam Integer userId, @RequestParam Integer round) {
        List<Study> reviewList = studyService.getReviewList(userId, round);
        return ResponseEntity.ok(reviewList);
    }

    // 处理复习结果
    @PostMapping("/review-result")
    public ResponseEntity<?> postReviewResult(@RequestBody Map<String, Object> payload) {
        Integer userId = (Integer) payload.get("userId");
        Integer wordId = (Integer) payload.get("wordId");
        Integer round = (Integer) payload.get("round");
        Boolean remembered = (Boolean) payload.get("remembered");

        if (userId == null || wordId == null || round == null || remembered == null) {
            return ResponseEntity.badRequest().body("参数错误");
        }

        studyService.processReviewResult(userId, wordId, round, remembered);
        return ResponseEntity.ok("复习结果处理成功");
    }

    // 获取用户待复习单词数量
    @GetMapping("/pending-count")
    public ResponseEntity<?> getPendingCount(@RequestParam Integer userId) {
        int count = studyService.getPendingCount(userId);
        return ResponseEntity.ok(count);
    }

    /**
     * 获取长期记忆阶段待复习单词列表（nextReviewDate <= 当前时间）
     */
    @GetMapping("/long-term-review-list")
    public ResponseEntity<?> getLongTermReviewList(
            @RequestParam Integer userId,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "3") int size,
            @RequestParam(defaultValue = "0") int longmemorySeen  // 传递当前轮次，默认0或1看你的设计
    ) {
        int offset = page * size;
        List<LongTermMemory> list = studyService.getLongTermReviewList(userId, longmemorySeen, offset, size);
        return ResponseEntity.ok(list);
    }

    /**
     * 处理长期记忆复习结果反馈
     * 请求体示例：
     * {
     *   "id": 123,
     *   "remembered": true
     * }
     */
    @PostMapping("/long-term-review-result")
    public ResponseEntity<?> postLongTermReviewResult(@RequestBody Map<String, Object> payload) {
        Integer id = (Integer) payload.get("id");
        Boolean remembered = (Boolean) payload.get("remembered");

        if (id == null || remembered == null) {
            return ResponseEntity.badRequest().body("参数错误");
        }

        LongTermMemory ltm = studyService.getLongTermMemoryById(id);
        if (ltm == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("未找到对应的长期记忆记录");
        }

        studyService.processLongTermReviewResult(ltm, remembered);
        return ResponseEntity.ok("长期记忆复习结果处理成功");
    }

}




package com.example.springboot.service;

import com.example.springboot.entity.LongTermMemory;
import com.example.springboot.entity.Study;
import com.example.springboot.entity.Word;
import com.example.springboot.mapper.LongTermMemoryMapper;
import com.example.springboot.mapper.StudyMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class StudyService {

    // --- Mapper 注入 ---
    @Resource
    private StudyMapper studyMapper;

    @Resource
    private LongTermMemoryMapper longTermMemoryMapper;

    // ================= 短期记忆相关 =================

    private static final int COOLDOWN = 2;
    private static final int PASS_SCORE = 3;
    private static final int N = 3;

    /**
     * 获取当前轮次的短期记忆复习候选单词
     */
    public List<Study> getReviewList(Integer userId, int round) {
        return studyMapper.selectReviewCandidates(userId, round, COOLDOWN, N);
    }

    /**
     * 处理短期记忆复习结果：
     * 通过累加分数判断是否通过阶段，
     * 通过后迁移到长期记忆表，否则更新分数和复习轮次
     */
    public void processReviewResult(Integer userId, Integer wordId, int currentRound, boolean remembered) {
        Study study = studyMapper.selectStudyByUserIdAndWordId(userId, wordId);
        if (study == null) return;

        int newScore = study.getScore();
        if (remembered) {
            newScore += 1;
        } else {
            newScore = Math.max(newScore - 1, 0);
        }

        if (newScore >= PASS_SCORE) {
            // 通过短期考核，迁移到长期记忆，删除短期记录
            moveToLongTermMemory(study, currentRound, newScore);
            studyMapper.deleteStudyByUserIdAndWordId(userId, wordId);
        } else {
            // 未通过，更新短期记忆分数和最近复习的轮次
            study.setScore(newScore);
            study.setLastSeen(currentRound);
            studyMapper.updateStudy(study);
        }
    }

    /**
     * 短期记忆→长期记忆迁移
     * intervalDays=0 表示首次长期复习未完成，
     * nextReviewDate设置为当前时间以供首次长期复习
     */
    private void moveToLongTermMemory(Study study, int currentRound, int score) {
        LongTermMemory ltm = new LongTermMemory();

        ltm.setUserId(study.getUserId());
        ltm.setWordId(study.getWordId());
        ltm.setReviewCount(0);       // 首次长期复习前 复习计数0
        ltm.setIntervalDays(0);      // 0表示首次长期复习阶段
        LocalDateTime now = LocalDateTime.now();
        ltm.setLastReviewDate(now);
        ltm.setNextReviewDate(now);  // 立即可复习首次长期任务
        // longmemoryScore, longmemorySeen等字段保持默认即可

        longTermMemoryMapper.insertLongTermMemory(ltm);
    }

    /**
     * 用户手动添加单词到短期记忆
     */
    public int insertUserSelectedWords(Integer userId, List<Integer> wordIds) {
        int count = 0;
        for (Integer wordId : wordIds) {
            Study exist = studyMapper.selectStudyByUserIdAndWordId(userId, wordId);
            if (exist == null) {
                Study study = new Study();
                study.setUserId(userId);
                study.setWordId(wordId);
                study.setScore(0);
                study.setLastSeen(-1);
                studyMapper.insertStudy(study);
                count++;
                studyMapper.deletePlanWord(userId, wordId);
            }
        }
        return count;
    }

    /**
     * 获取用户已选短期记忆单词列表
     */
    public List<Word> getSelectedWordsByUserId(Integer userId) {
        return studyMapper.selectSelectedWordsByUserId(userId);
    }

    /**
     * 获取用户待学习的短期记忆单词（待加入短期记忆）
     */
    public List<Study> getPendingWords(Integer userId, int limit) {
        return studyMapper.selectPendingWordsFromPlan(userId, limit);
    }

    /**
     * 删除单个短期记忆单词记录
     */
    public int deleteWord(Integer userId, Integer wordId) {
        return studyMapper.deleteStudyByUserIdAndWordId(userId, wordId);
    }

    /**
     * 批量删除短期记忆单词记录
     */
    public int deleteWords(Integer userId, List<Integer> wordIds) {
        return studyMapper.deleteStudiesByUserIdAndWordIds(userId, wordIds);
    }

    /**
     * 查询用户短期记忆待复习单词总数（用于展示）
     */
    public int getPendingCount(Integer userId) {
        return studyMapper.countPendingWords(userId);
    }


    // ================= 长期记忆相关 =================

    // 长期记忆复习阶段间隔（单位：天）
    private static final int[] INTERVALS = {1, 2, 4, 7, 15};

    /**
     * 获取长期记忆阶段待复习单词列表（nextReviewDate <= 当前时间）
     */
    public List<LongTermMemory> getLongTermReviewList(Integer userId, int longMemorySeen, int offset, int size) {
        LocalDateTime now = LocalDateTime.now();
        return longTermMemoryMapper.selectDueReviews(userId, now, offset, size, longMemorySeen);
    }

    /**
     * 根据长期记忆ID查询对应实体
     */
    public LongTermMemory getLongTermMemoryById(Integer id) {
        return longTermMemoryMapper.selectById(id);
    }

    /**
     * 处理长期记忆复习结果：
     * 需累计通过3次才进入下一阶段间隔；
     * 未通过扣减分数，但不降低阶段；
     * 通过后更新时间间隔及复习时间。
     */
    public void processLongTermReviewResult(LongTermMemory ltm, boolean remembered) {
        if (ltm == null) return;

        // 累计复习次数
        Integer reviewCount = ltm.getReviewCount() == null ? 0 : ltm.getReviewCount() + 1;

        // 当前阶段短期分数
        Integer score = ltm.getLongmemoryScore() == null ? 0 : ltm.getLongmemoryScore();

        // 当前累计连续通过次数
        Integer passCount = ltm.getPassCount() == null ? 0 : ltm.getPassCount();

        // 当前复习轮次，由 longmemorySeen 表示
        int round = ltm.getLongmemorySeen() == null ? 0 : ltm.getLongmemorySeen();

        int[] INTERVALS = {1, 2, 4, 7, 15};
        int intervalIndex = 0;

        // 计算当前阶段索引，根据 intervalDays 字段
        Integer intervalDays = ltm.getIntervalDays() == null ? 0 : ltm.getIntervalDays();
        for (int i = 0; i < INTERVALS.length; i++) {
            if (intervalDays != null && INTERVALS[i] == intervalDays.intValue()) {
                intervalIndex = i;
                break;
            }
        }

        if (remembered) {
            // 复习通过，分数+1，计数+1
            score++;
            passCount++;

            // 连续通过超过阈值，准备进入下一轮次
            if (passCount >= 3) {
                round++;              // 轮次+1
                passCount = 0;        // 复位连续通过计数
                score = 0;            // 复位分数

                // 阶段后移，不能超过最大间隔
                if (intervalIndex < INTERVALS.length - 1) {
                    intervalIndex++;
                }

                int nextInterval = INTERVALS[intervalIndex];
                // 设置下次复习时间（当前时间 + 阶段间隔）
                ltm.setNextReviewDate(LocalDateTime.now().plusDays(nextInterval));
                // 更新间隔天数字段
                ltm.setIntervalDays(nextInterval);
                // 更新轮次
                ltm.setLongmemorySeen(round);
            }
        } else {
            // 未通过则分数减1最低不低于0
            score = Math.max(score - 1, 0);
            // 不更新轮次和间隔
        }

        // 每次复习都更新以下字段
        ltm.setReviewCount(reviewCount);
        ltm.setLongmemoryScore(score);
        ltm.setPassCount(passCount);
        ltm.setLastReviewDate(LocalDateTime.now());

        // 更新数据库
        longTermMemoryMapper.updateLongTermMemory(ltm);
    }

}
<?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.example.springboot.mapper.StudyMapper">

    <!-- 从 user_word_plan 中抽取待学习且 user_word_memory 中不存在的单词 -->
    <select id="selectPendingWordsFromPlan" parameterType="map" resultType="com.example.springboot.entity.Study">
        SELECT
            NULL AS id,
            up.user_id AS userId,
            up.word_id AS wordId,
            w.word AS word,
            w.definition AS definition,
            0 AS score,
            -1 AS lastSeen
        FROM user_word_plan up
                 JOIN words w ON up.word_id = w.id
                 LEFT JOIN user_word_memory um ON up.user_id = um.user_id AND up.word_id = um.word_id
        WHERE up.user_id = #{userId} AND up.status = 'pending' AND um.id IS NULL
        ORDER BY up.added_at ASC
        LIMIT #{limit}
    </select>


    <!-- 查询 user_word_memory -->
    <select id="selectStudyByUserIdAndWordId" parameterType="map" resultType="com.example.springboot.entity.Study">
        SELECT id, user_id AS userId, word_id AS wordId, score, last_seen AS lastSeen
        FROM user_word_memory
        WHERE user_id = #{userId} AND word_id = #{wordId}
    </select>

    <!-- 插入 user_word_memory -->
    <insert id="insertStudy" parameterType="com.example.springboot.entity.Study" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user_word_memory(user_id, word_id, score, last_seen)
        VALUES(#{userId}, #{wordId}, #{score}, #{lastSeen})
    </insert>

    <!-- 更新 user_word_memory -->
    <update id="updateStudy" parameterType="com.example.springboot.entity.Study">
        UPDATE user_word_memory
        SET score = #{score},
            last_seen = #{lastSeen}
        WHERE id = #{id}
    </update>

    <!-- 查询单词详情 -->
    <select id="selectWordById" parameterType="int" resultType="com.example.springboot.entity.Word">
        SELECT id, word, definition, pronunciation, custom_content AS customContent, created_at AS createdAt
        FROM words
        WHERE id = #{id}
    </select>

    <!-- 查询用户已选择的单词 -->
    <select id="selectSelectedWordsByUserId" parameterType="int" resultType="com.example.springboot.entity.Word">
        SELECT
            w.id,
            w.word,
            w.definition,
            w.pronunciation,
            w.custom_content AS customContent,
            w.created_at AS createdAt
        FROM user_word_memory uwm
                 JOIN words w ON uwm.word_id = w.id
        WHERE uwm.user_id = #{userId}
    </select>

    <!--  删除单个单词 -->
    <delete id="deleteStudyByUserIdAndWordId" parameterType="map">
        DELETE FROM user_word_memory WHERE user_id = #{userId} AND word_id = #{wordId}
    </delete>

    <!--  批量删除单词 -->
    <delete id="deleteStudiesByUserIdAndWordIds" parameterType="map">
        DELETE FROM user_word_memory WHERE user_id = #{userId} AND word_id IN
        <foreach item="wordId" collection="wordIds" open="(" separator="," close=")">
            #{wordId}
        </foreach>
    </delete>

    <select id="selectReviewCandidates" resultType="com.example.springboot.entity.Study" parameterType="map">
        SELECT
            um.id,
            um.user_id AS userId,
            um.word_id AS wordId,
            um.score,
            um.last_seen AS lastSeen,
            w.word,
            w.definition,
            w.pronunciation
        FROM user_word_memory um
                 JOIN words w ON um.word_id = w.id
        WHERE um.user_id = #{userId} AND (#{round} - um.last_seen) >= #{cooldown}
        ORDER BY um.score ASC, um.last_seen ASC
        LIMIT #{limit}
    </select>

    <!-- 删除user_word_plan中对应记录 -->
    <delete id="deletePlanWord" parameterType="map">
        DELETE FROM user_word_plan WHERE user_id = #{userId} AND word_id = #{wordId}
    </delete>

    <select id="countPendingWords" resultType="int" parameterType="int">
        SELECT COUNT(*) FROM user_word_memory WHERE user_id = #{userId}
    </select>




</mapper>
<?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.example.springboot.mapper.LongTermMemoryMapper">

    <!-- 插入新记录，返回自增主键 -->
    <insert id="insertLongTermMemory" parameterType="com.example.springboot.entity.LongTermMemory" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO long_term_memory(
            user_id, word_id, review_count, pass_count, interval_days,
            next_review_date, last_review_date,
            longmemory_score, longmemory_seen, created_at, updated_at
        )
        VALUES(
                  #{userId}, #{wordId}, #{reviewCount}, #{passCount}, #{intervalDays},
                  #{nextReviewDate}, #{lastReviewDate},
                  #{longmemoryScore}, #{longmemorySeen}, NOW(), NOW()
              )
    </insert>

    <!-- 查询某用户的已有单词id列表 -->
    <select id="selectExistingWordIds" resultType="int" parameterType="map">
        SELECT word_id
        FROM long_term_memory
        WHERE user_id = #{userId}
        AND word_id IN
        <foreach collection="wordIds" item="wordId" open="(" separator="," close=")">
            #{wordId}
        </foreach>
    </select>

    <!-- 查询当天及以前可复习的长期记忆单词，左连接单词表 -->
    <select id="selectDueReviews" resultType="com.example.springboot.dto.LongTermMemoryWithWord" parameterType="map">
        SELECT ltm.*, w.word, w.definition
        FROM long_term_memory ltm
                 LEFT JOIN words w ON ltm.word_id = w.id
        WHERE ltm.user_id = #{userId}
          AND ltm.longmemory_seen = #{longmemorySeen}
          AND ltm.next_review_date &lt;= #{now}
        ORDER BY
            ltm.longmemory_score ASC,
            ltm.next_review_date ASC
        LIMIT #{size} OFFSET #{offset}
    </select>

    <!-- 更新长期记忆记录 -->
    <update id="updateLongTermMemory" parameterType="com.example.springboot.entity.LongTermMemory">
        UPDATE long_term_memory
        SET review_count = #{reviewCount},
            pass_count = #{passCount},
            interval_days = #{intervalDays},
            next_review_date = #{nextReviewDate},
            last_review_date = #{lastReviewDate},
            longmemory_score = #{longmemoryScore},
            longmemory_seen = #{longmemorySeen},
            updated_at = NOW()
        WHERE id = #{id}
    </update>

    <!-- 通过id查询长期记忆记录 -->
    <select id="selectById" resultType="com.example.springboot.entity.LongTermMemory" parameterType="int">
        SELECT *
        FROM long_term_memory
        WHERE id = #{id}
    </select>

</mapper>
<template>
  <el-row style="height: 100vh; padding: 20px;" :gutter="20">

    <!-- 左侧：学习单词区域 -->
    <el-col :span="12" class="left-panel" style="height: 100%;">

      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
        <h2>短期记忆区域</h2>
        <div>
          <el-button type="primary" @click="openSelectedDialog">查看已选单词</el-button>
          <el-button type="success" @click="openDialog" style="margin-left: 10px;">选择单词</el-button>
        </div>
      </div>

      <!-- 待复习单词数量 -->
      <div class="pending-count" v-if="reviewList.length">
        待复习单词数量: {{ pendingCount }}
      </div>

      <!-- 单词卡片 -->
      <div class="card-container" v-if="reviewList.length">

        <div class="card-wrapper">
          <div class="card" :class="{ flipped: isFlipped }">

            <div class="card-face card-front">
              <!-- 正面不显示通过次数，如果需要，类似写这里 -->
              <h1>{{ currentWord.word }}</h1>
              <el-button @click.stop="speakWord(currentWord.word)">🔊 发音</el-button>
            </div>

            <div class="card-face card-back" style="position: relative;">
              <!-- 通过次数左上角绝对定位 -->
              <div class="score-badge">
                通过次数: {{ currentWord.score ?? 0 }}
              </div>
              <h1 style="margin-top: 40px;">{{ currentWord.word }}</h1>
              <p><strong>释义：</strong>{{ currentWord.definition }}</p>
              <el-button @click.stop="speakWord(currentWord.word)">🔊 发音</el-button>
            </div>

          </div>

          <div class="btn-group">
            <el-button type="danger" v-if="!isFlipped" @click="flipCard">忘记</el-button>
            <el-button type="success" v-if="!isFlipped" @click="flipCard">认识</el-button>
            <el-button type="danger" v-if="isFlipped" @click="submitReviewResult(false)">忘记</el-button>
            <el-button type="success" v-if="isFlipped" @click="submitReviewResult(true)">认识</el-button>
          </div>
        </div>
      </div>

      <!-- 无可复习单词提示 -->
      <div v-else style="text-align:center; margin-top: 50px; font-size: 20px; color: #999;">
        无可复习单词，复习完成！
      </div>

      <!-- 已选单词弹窗 -->
      <el-dialog v-model="selectedDialogVisible" title="已选单词列表" width="800px">
        <el-table
            :data="selectedWords"
            style="width: 100%;"
            border
            @selection-change="handleSelectedChange"
        >
          <el-table-column type="selection" width="55" />
          <el-table-column prop="id" label="ID" width="80" />
          <el-table-column prop="word" label="单词" />
          <el-table-column prop="definition" label="释义" />
          <el-table-column label="操作" width="100">
            <template #default="scope">
              <el-button type="danger" size="small" @click="removeWord(scope.row)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <template #footer>
          <span class="dialog-footer">
            <el-button @click="selectedDialogVisible = false">关闭</el-button>
            <el-button type="danger" :disabled="!selectedToDelete.length" @click="batchRemoveWords">
              批量删除（{{ selectedToDelete.length }}）
            </el-button>
          </span>
        </template>
      </el-dialog>

    </el-col>

    <!-- 右侧区域：长期记忆区域 -->
    <el-col :span="12" class="right-panel" style="border: 1px solid #eee; padding: 20px;">
      <h2>长期记忆区域</h2>
      <div v-if="longTermReviewList.length">

        <div class="card-container">

          <div class="card-wrapper">
            <div class="card" :class="{ flipped: longTermIsFlipped }">

              <div class="card-face card-front">
                <h1>{{ currentLongTermWord.word }}</h1>
                <el-button @click.stop="speakWord(currentLongTermWord.word)">🔊 发音</el-button>
              </div>

              <div class="card-face card-back" style="position: relative;">
                <div class="score-badge">
                  复习次数: {{ currentLongTermWord.reviewCount ?? 0 }} <br/>
                  间隔天数: {{ currentLongTermWord.intervalDays ?? 0 }}
                </div>
                <h1 style="margin-top: 40px;">{{ currentLongTermWord.word }}</h1>
                <p><strong>释义：</strong>{{ currentLongTermWord.definition }}</p>
                <el-button @click.stop="speakWord(currentLongTermWord.word)">🔊 发音</el-button>
              </div>

            </div>

            <div class="btn-group">
              <el-button type="danger" v-if="!longTermIsFlipped" @click="flipLongTermCard">忘记</el-button>
              <el-button type="success" v-if="!longTermIsFlipped" @click="flipLongTermCard">认识</el-button>
              <el-button type="danger" v-if="longTermIsFlipped" @click="submitLongTermReviewResult(false)">忘记</el-button>
              <el-button type="success" v-if="longTermIsFlipped" @click="submitLongTermReviewResult(true)">认识</el-button>
            </div>
          </div>
        </div>

      </div>

      <div v-else style="text-align:center; margin-top: 50px; font-size: 20px; color: #999;">
        无可复习的长期记忆单词，复习完成！
      </div>
    </el-col>

    <!-- 选择待学习单词弹窗 -->
    <el-dialog v-model="dialogVisible" title="选择待学习单词" width="600px">
      <el-table :data="pendingWords" style="width: 100%;" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" />
        <el-table-column prop="word" label="单词" />
        <el-table-column prop="definition" label="释义" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitSelectedWords">确认选择（{{ selectedToAdd.length }}）</el-button>
        </span>
      </template>
    </el-dialog>

  </el-row>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'

axios.defaults.baseURL = 'http://localhost:8080'

const selectedWords = ref([])
const pendingWords = ref([])
const selectedToAdd = ref([])
const selectedToDelete = ref([])
const dialogVisible = ref(false)
const selectedDialogVisible = ref(false)

const round = ref(1)
const reviewList = ref([])
const reviewIndex = ref(0)
const pendingCount = ref(0)

const isFlipped = ref(false)

const currentWord = computed(() => reviewList.value[reviewIndex.value] || {})

const utterance = new SpeechSynthesisUtterance()
utterance.lang = 'en-US'

// 长期记忆相关状态
const longTermReviewList = ref([])
const longTermReviewIndex = ref(0)
const longTermIsFlipped = ref(false)
const currentPage = ref(0)

const currentLongTermWord = computed(() => longTermReviewList.value[longTermReviewIndex.value] || {})

async function fetchPendingCount() {
  const userId = localStorage.getItem('userId')
  if (!userId) return ElMessage.warning('请先登录或设置 userId')
  try {
    const res = await axios.get('/study/pending-count', { params: { userId: Number(userId) } })
    pendingCount.value = res.data
  } catch {
    ElMessage.error('获取待复习单词数量失败')
  }
}

async function fetchSelectedWords() {
  const userId = localStorage.getItem('userId')
  if (!userId) return ElMessage.warning('请先登录或设置 userId')
  try {
    const res = await axios.get('/study/select-words', { params: { userId } })
    selectedWords.value = res.data
  } catch {
    ElMessage.error('获取已选单词失败')
  }
}

async function fetchReviewList() {
  const userId = localStorage.getItem('userId')
  if (!userId) return ElMessage.warning('请先登录或设置 userId')
  try {
    const res = await axios.get('/study/review-list', {
      params: { userId: Number(userId), round: round.value }
    })
    reviewList.value = res.data
    reviewIndex.value = 0
    isFlipped.value = false

    if (reviewList.value.length > 0) {
      speakWord(currentWord.value.word)
    }
  } catch (e) {
    ElMessage.error('加载复习单词失败')
    console.error(e)
  }
}

function flipCard() {
  if (!currentWord.value.word) return
  speakWord(currentWord.value.word)
  isFlipped.value = true
}

async function submitReviewResult(remembered) {
  const userId = localStorage.getItem('userId')
  if (!userId) {
    ElMessage.warning('请先登录或设置 userId')
    return
  }

  try {
    await axios.post('/study/review-result', {
      userId: Number(userId),
      wordId: currentWord.value.wordId || currentWord.value.id,
      round: round.value,
      remembered: remembered
    })

    reviewIndex.value++
    isFlipped.value = false

    await fetchPendingCount() // 刷新待复习数量

    if (reviewIndex.value >= reviewList.value.length) {
      round.value++
      await fetchReviewList()
    } else {
      speakWord(currentWord.value.word)
    }
  } catch (e) {
    ElMessage.error('提交复习结果失败')
    console.error(e)
  }
}

function speakWord(text) {
  if (!text) return
  if (speechSynthesis.speaking) speechSynthesis.cancel()
  utterance.text = text
  speechSynthesis.speak(utterance)
}

async function openSelectedDialog() {
  await fetchSelectedWords()
  selectedToDelete.value = []
  selectedDialogVisible.value = true
}

async function openDialog() {
  const userId = localStorage.getItem('userId')
  if (!userId) return ElMessage.warning('请先登录或设置 userId')
  try {
    const res = await axios.get('/study/pending-words', {
      params: { userId, limit: 100 }
    })
    pendingWords.value = res.data
    dialogVisible.value = true
  } catch {
    ElMessage.error('获取待学习单词失败')
  }
}

function handleSelectionChange(val) {
  selectedToAdd.value = val
}

function handleSelectedChange(val) {
  selectedToDelete.value = val
}

async function submitSelectedWords() {
  const userId = localStorage.getItem('userId')
  const wordIds = selectedToAdd.value.map(item => item.wordId || item.id)
  if (wordIds.length < 10) {
    ElMessage.warning('请至少选择10个单词')
    return
  }
  try {
    await axios.post('/study/select-words', { userId: Number(userId), wordIds })
    dialogVisible.value = false
    await fetchSelectedWords()
    await fetchPendingCount() // 选择后刷新数量
    await fetchReviewList()
    ElMessage.success('添加成功')
  } catch {
    ElMessage.error('添加单词失败')
  }
}

async function removeWord(row) {
  const userId = localStorage.getItem('userId')
  try {
    await axios.post('/study/remove-word', {
      userId: Number(userId),
      wordId: row.wordId || row.id
    })
    await fetchSelectedWords()
    await fetchPendingCount() // 删除后刷新数量
    await fetchReviewList()
    ElMessage.success('删除成功')
  } catch {
    ElMessage.error('删除失败')
  }
}

async function batchRemoveWords() {
  const userId = localStorage.getItem('userId')
  const wordIds = selectedToDelete.value.map(item => item.wordId || item.id)
  if (!wordIds.length) {
    ElMessage.warning('请选择要删除的单词')
    return
  }
  try {
    await axios.post('/study/remove-words', { userId: Number(userId), wordIds })
    await fetchSelectedWords()
    selectedToDelete.value = []
    await fetchPendingCount() // 批量删除后刷新数量
    await fetchReviewList()
    ElMessage.success('批量删除成功')
  } catch {
    ElMessage.error('批量删除失败')
  }
}

// 长期记忆相关方法

async function fetchLongTermReviewList() {
  const userId = localStorage.getItem('userId')
  if (!userId) return ElMessage.warning('请先登录或设置 userId')
  try {
    const res = await axios.get('/study/long-term-review-list', {
      params: {
        userId: Number(userId),
        page: currentPage.value,
        size: 3
      }
    })
    longTermReviewList.value = res.data || []
    longTermIsFlipped.value = false

    if (longTermReviewList.value.length > 0) {
      speakWord(currentLongTermWord.value.word)
    }
  } catch (e) {
    ElMessage.error('加载长期记忆复习列表失败')
    console.error(e)
  }
}

function flipLongTermCard() {
  if (!currentLongTermWord.value.word) return
  speakWord(currentLongTermWord.value.word)
  longTermIsFlipped.value = true
}

async function submitLongTermReviewResult(remembered) {
  const userId = localStorage.getItem('userId')
  if (!userId) {
    ElMessage.warning('请先登录或设置 userId')
    return
  }
  const ltmId = currentLongTermWord.value.id
  try {
    await axios.post('/study/long-term-review-result', {
      userId: Number(userId),
      id: ltmId,
      remembered
    })

    longTermReviewIndex.value++
    longTermIsFlipped.value = false

    if (longTermReviewIndex.value >= longTermReviewList.value.length) {
      // 当前页单词复习完毕，拉取下一页
      currentPage.value++
      await fetchLongTermReviewList()  // 重新请求下一页数据

      if (longTermReviewList.value.length === 0) {
        ElMessage.success('长期记忆阶段复习完成！')
      } else {
        longTermReviewIndex.value = 0  // 重置索引
        speakWord(currentLongTermWord.value.word)
      }
    } else {
      // 继续复习当前页剩余单词
      speakWord(currentLongTermWord.value.word)
    }
  } catch (e) {
    ElMessage.error('提交长期记忆复习结果失败')
    console.error(e)
  }
}
onMounted(async () => {
  speechSynthesis.speak(new SpeechSynthesisUtterance(' '))
  await fetchPendingCount()
  await fetchReviewList()
  await fetchLongTermReviewList()
})
</script>

<style scoped>
.left-panel {
  display: flex;
  flex-direction: column;
  border: 1px solid #eee;
  height: 100%;
}

.pending-count {
  font-size: 18px;
  font-weight: bold;
  color: #409EFF;
  padding: 10px 0 0 0;
  margin-bottom: 10px;
  user-select: none;
}

.card-container {
  flex-grow: 1;
  margin-top: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  perspective: 1000px;
  width: 100%;
}

.card {
  width: 100%;
  max-width: 550px;
  height: 500px;
  position: relative;
  transition: transform 0.6s;
  transform-style: preserve-3d;
  border-radius: 16px;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
  background-color: #fff;
}

.score-badge {
  position: absolute;
  top: 15px;
  left: 15px;
  background-color: #409EFF;
  color: white;
  font-weight: bold;
  padding: 6px 12px;
  border-radius: 12px;
  font-size: 14px;
  pointer-events: none;
  user-select: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 10;
}

.card.flipped {
  transform: rotateY(180deg);
}

.card-face {
  position: absolute;
  width: 100%;
  height: 100%;
  padding: 20px;
  box-sizing: border-box;
  backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.card-front {
  font-size: 1em;
  font-weight: bold;
}

.card-back {
  transform: rotateY(180deg);
  font-size: 1.2em;
  text-align: center;
  line-height: 1.6;
}

.card-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.btn-group {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 30px;
  margin-top: 30px;
}

.btn-group .el-button {
  padding: 18px 40px;
  font-size: 18px;
  border-radius: 10px;
  font-weight: bold;
  min-width: 140px;
  transition: all 0.2s ease-in-out;
}

.btn-group .el-button:hover {
  transform: scale(1.05);
}
</style>