test: add unit tests for core services and utils

- Add JUnit 5 and Mockito test dependencies to root pom.xml
- Add maven-surefire-plugin configuration for test execution
- Create BaseTest abstract class for Spring + Mockito support
- Add SysUserServiceImplTest with 7 test cases (CRUD operations, username uniqueness)
- Add AccountServiceImplTest with 5 test cases (CRUD operations)
- Add StringUtilsTest with 10 test cases (string utility methods)
- Total: 22 test cases, all passing
feature/20260628/refactor-frontend-modernization
Lxy 3 weeks ago
parent 32b758fb21
commit 99ddd5d781

@ -0,0 +1,109 @@
package com.ruoyi.booksystem.service.impl;
import com.ruoyi.booksystem.domain.Account;
import com.ruoyi.booksystem.mapper.AccountMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class AccountServiceImplTest {
@InjectMocks
private AccountServiceImpl accountService;
@Mock
private AccountMapper accountMapper;
private Account testAccount;
@BeforeEach
void setUp() {
testAccount = new Account();
testAccount.setId(1L);
testAccount.setUserId(100L);
testAccount.setWeekDay("Monday");
}
@Test
void testSelectAccountById() {
// Given
when(accountMapper.selectAccountById(1L)).thenReturn(testAccount);
// When
Account result = accountService.selectAccountById(1L);
// Then
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals(100L, result.getUserId());
verify(accountMapper, times(1)).selectAccountById(1L);
}
@Test
void testSelectAccountList() {
// Given
List<Account> accountList = Arrays.asList(testAccount);
when(accountMapper.selectAccountList(any(Account.class))).thenReturn(accountList);
// When
Account query = new Account();
query.setUserId(100L);
List<Account> result = accountService.selectAccountList(query);
// Then
assertNotNull(result);
assertEquals(1, result.size());
assertEquals(100L, result.get(0).getUserId());
verify(accountMapper, times(1)).selectAccountList(query);
}
@Test
void testInsertAccount() {
// Given
when(accountMapper.insertAccount(any(Account.class))).thenReturn(1);
// When
int result = accountService.insertAccount(testAccount);
// Then
assertEquals(1, result);
verify(accountMapper, times(1)).insertAccount(testAccount);
}
@Test
void testUpdateAccount() {
// Given
when(accountMapper.updateAccount(any(Account.class))).thenReturn(1);
// When
int result = accountService.updateAccount(testAccount);
// Then
assertEquals(1, result);
verify(accountMapper, times(1)).updateAccount(testAccount);
}
@Test
void testDeleteAccountById() {
// Given
when(accountMapper.deleteAccountById(1L)).thenReturn(1);
// When
int result = accountService.deleteAccountById(1L);
// Then
assertEquals(1, result);
verify(accountMapper, times(1)).deleteAccountById(1L);
}
}

@ -0,0 +1,340 @@
<!DOCTYPE html><html lang="zh-CN"><head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>掘金动量分析系统 - Demo</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@4.9.0/dist/echarts.min.js"></script>
<style>:root{--bg0:#0d1117;--bg1:#161b22;--bg2:#1c2333;--bg3:#2a3040;--bd:#30363d;--t1:#e6edf3;--t2:#8b949e;--tm:#6e7681;--red:#f85149;--grn:#3fb950;--org:#d29922;--blu:#58a6ff}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,PingFang SC,Microsoft YaHei,sans-serif;background:var(--bg0);color:var(--t1);line-height:1.5}
.app{display:flex;min-height:100vh}
.side{width:180px;background:var(--bg1);border-right:1px solid var(--bd);position:fixed;top:0;bottom:0;left:0;z-index:100;display:flex;flex-direction:column}
.slogo{padding:12px 14px;border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:8px}
.slogo .ico{width:24px;height:24px;background:linear-gradient(135deg,var(--red),var(--org));border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:12px}
.slogo b{font-size:13px}
.snav{flex:1;padding:8px 0}
.ng{padding:6px 14px 3px;font-size:9px;color:var(--tm);text-transform:uppercase;letter-spacing:.5px}
.ni{display:flex;align-items:center;gap:7px;padding:6px 14px;color:var(--t2);cursor:pointer;font-size:11px;border-left:3px solid transparent;transition:all .1s}
.ni:hover{background:var(--bg3);color:var(--t1)}.ni.on{background:rgba(88,166,255,.1);color:var(--blu);border-left-color:var(--blu)}
.ni .badge{margin-left:auto;background:var(--red);color:#fff;font-size:8px;padding:0 4px;border-radius:6px;line-height:16px}
.marea{margin-left:180px;flex:1;display:flex;flex-direction:column}
.topbar{height:40px;background:var(--bg1);border-bottom:1px solid var(--bd);display:flex;align-items:center;padding:0 16px;gap:10px;position:sticky;top:0;z-index:50}
.topbar .bc{font-size:11px;color:var(--t2)}.topbar .bc b{color:var(--t1)}.topbar-r{margin-left:auto;display:flex;align-items:center;gap:8px}
.topbar-r input{background:var(--bg2);border:1px solid var(--bd);color:var(--t1);padding:2px 6px;border-radius:4px;font-size:10px;width:110px}
.cnt{padding:12px 16px;flex:1}
.card{background:var(--bg2);border:1px solid var(--bd);border-radius:7px;overflow:hidden;margin-bottom:12px}
.chd{padding:8px 12px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between}
.cht{font-size:12px;font-weight:600;display:flex;align-items:center;gap:5px}.cht .dot{width:6px;height:6px;border-radius:50%}
.cbd{padding:10px 12px}
.btn{padding:3px 8px;border-radius:3px;border:1px solid var(--bd);background:var(--bg2);color:var(--t1);font-size:9px;cursor:pointer}.btn:hover{background:var(--bg3)}
.mfilter{display:flex;align-items:center;gap:6px;margin-bottom:10px;padding:8px 12px;background:var(--bg1);border:1px solid var(--bd);border-radius:6px;flex-wrap:wrap}
.mtab{padding:4px 12px;font-size:10px;color:var(--t2);cursor:pointer;border-radius:4px;border:1px solid transparent;background:transparent;transition:all .12s}
.mtab:hover{background:var(--bg3);color:var(--t1)}.mtab.on{background:rgba(88,166,255,.1);color:var(--blu);border-color:var(--blu)}
.ministats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}
.ministat{background:var(--bg2);border:1px solid var(--bd);border-radius:5px;padding:8px 12px}
.ministat .lbl{font-size:9px;color:var(--tm);margin-bottom:2px}.ministat .val{font-size:20px;font-weight:700}.ministat .sub{font-size:9px;color:var(--t2);margin-top:1px}
.sentrow{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px}
.sentcard{background:var(--bg2);border:1px solid var(--bd);border-radius:6px;padding:10px 12px;position:relative;overflow:hidden}
.sentcard::before{content:"";position:absolute;top:0;left:0;right:0;height:2px}
.sentcard.hot::before{background:linear-gradient(90deg,#ff8a65,#f44336)}
.sentcard.warm::before{background:linear-gradient(90deg,#ffd54f,#ff8a65)}
.sentcard.neutral::before{background:linear-gradient(90deg,#66bb6a,#ffd54f)}
.sentcard.cool::before{background:linear-gradient(90deg,#4fc3f7,#66bb6a)}
.sentcard .snm{font-size:11px;color:var(--t2);margin-bottom:4px}
.sentcard .sval{font-size:28px;font-weight:700;line-height:1}.sentcard .slabel{font-size:10px;margin-top:2px}
.sentcard .sbar{margin-top:8px;height:4px;background:var(--bg3);border-radius:2px;overflow:hidden}
.sentcard .sbar .sf{height:100%;border-radius:2px}
.sentcard .smeta{margin-top:6px;display:flex;justify-content:space-between;font-size:8px;color:var(--tm)}
.shot{color:#f44336}.swarm{color:#ff8a65}.sneutral{color:#ffd54f}.scool{color:#66bb6a}
.sf-h{background:linear-gradient(90deg,#ff8a65,#f44336)}.sf-w{background:linear-gradient(90deg,#ffd54f,#ff8a65)}
.sf-n{background:linear-gradient(90deg,#66bb6a,#ffd54f)}.sf-c{background:linear-gradient(90deg,#4fc3f7,#66bb6a)}
.bchart{display:flex;align-items:flex-end;justify-content:space-around;height:140px;padding:4px 0;border-bottom:1px solid var(--bd)}
.bgrp{display:flex;flex-direction:column;align-items:center;gap:2px;flex:1}
.bar{width:18px;border-radius:2px 2px 0 0}.bar.r{background:var(--red)}.bar.g{background:var(--grn)}.bar.gr{background:var(--tm)}
.blbl{font-size:7px;color:var(--tm)}.bval{font-size:7px;color:var(--t2)}
table{width:100%;border-collapse:collapse;font-size:10px}
th{background:var(--bg1);padding:5px 8px;text-align:left;font-weight:600;color:var(--t2);border-bottom:1px solid var(--bd);position:sticky;top:0}
td{padding:5px 8px;border-bottom:1px solid var(--bd)}
tr:hover td{background:var(--bg3)}.tw{overflow-x:auto}
.rb{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;font-size:9px;font-weight:700}
.r1{background:linear-gradient(135deg,#ff6b6b,#ee5a24);color:#fff}
.r2{background:linear-gradient(135deg,#ffa502,#e17009);color:#fff}
.r3{background:linear-gradient(135deg,#e3b341,#d29922);color:#fff}
.ro{background:var(--bg1);color:var(--t2)}
.vr{color:var(--red)}.vg{color:var(--grn)}
.sig{display:inline-flex;padding:1px 5px;border-radius:8px;font-size:9px;font-weight:600}
.sig-u{background:rgba(248,81,73,.12);color:var(--red)}
.sig-o{background:rgba(210,153,34,.12);color:var(--org)}
.sig-n{background:var(--bg3);color:var(--tm)}
.page{display:none}.page.on{display:block}
::-webkit-scrollbar{width:4px;height:4px}::-webkit-scrollbar-thumb{background:var(--bd);border-radius:2px}
.demo-note{background:rgba(88,166,255,.1);border:1px solid var(--blu);border-radius:5px;padding:8px 12px;margin-bottom:12px;font-size:10px;color:var(--blu)}
.drow1{display:grid;grid-template-columns:300px 1fr;gap:12px}.drow2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.modal-ov{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:200;display:none;align-items:center;justify-content:center}
.modal-ov.show{display:flex}
.modal{background:var(--bg1);border:1px solid var(--bd);border-radius:8px;width:92%;max-width:980px;max-height:85vh;display:flex;flex-direction:column;overflow:hidden}
.mhd{padding:10px 14px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between}.mhd h3{font-size:13px}
.mcl{width:22px;height:22px;border-radius:4px;border:none;background:var(--bg2);color:var(--t2);cursor:pointer;font-size:12px;display:flex;align-items:center;justify-content:center}
.mcl:hover{background:var(--red);color:#fff}
.mbd{flex:1;overflow-y:auto;padding:12px}
.detailrow{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px}
.stat-card{background:var(--bg1);border:1px solid var(--bd);border-radius:5px;padding:8px 12px}
.stat-card .sl{font-size:9px;color:var(--tm);margin-bottom:2px}.stat-card .sv{font-size:22px;font-weight:700}
.fbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
.fbar label{font-size:10px;color:var(--t2)}.fbar select,.fbar input{background:var(--bg2);border:1px solid var(--bd);color:var(--t1);padding:3px 6px;border-radius:3px;font-size:10px}
.hmc{overflow-x:auto}
.hmt{border-collapse:collapse;min-width:100%}
.hmt th{background:var(--bg1);padding:4px 5px;font-size:9px;text-align:center;color:var(--t2);border:1px solid var(--bd)}
.hmt td{padding:0;border:1px solid var(--bd);text-align:center}
.hcell{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4px 2px;min-width:44px;cursor:pointer;transition:transform .1s,box-shadow .1s}
.hcell:hover{transform:scale(1.06);z-index:2;box-shadow:0 0 0 2px var(--blu)}
.hcell .hrk{font-size:11px;font-weight:700}
.h1{background:linear-gradient(135deg,#ff4444,#cc0000);color:#fff}
.h2{background:linear-gradient(135deg,#ff6b6b,#e04040);color:#fff}
.h3{background:linear-gradient(135deg,#ff9a76,#d94f4f);color:#fff}
.h4{background:linear-gradient(135deg,#ffa502,#c77f00);color:#fff}
.h5{background:linear-gradient(135deg,#e3b341,#b8911a);color:#333}
.h6{background:linear-gradient(135deg,#e6d34a,#c4b538);color:#333}
.h7{background:linear-gradient(135deg,#8bda5e,#5fb32e);color:#fff}
.h8{background:linear-gradient(135deg,#4dd08b,#28a360);color:#fff}
.h9{background:linear-gradient(135deg,#36b37e,#1e8c5e);color:#fff}
.h10{background:linear-gradient(135deg,#2d9cdb,#1a7ab5);color:#fff}
.hd{background:var(--bg2);color:var(--t2)}
.hnc{background:var(--bg2);padding:4px 8px;text-align:left;font-size:10px;font-weight:500;min-width:64px;position:sticky;left:0;z-index:1}
.mode-switch{display:flex;gap:0;margin-bottom:10px;border:1px solid var(--bd);border-radius:6px;overflow:hidden;width:fit-content}
.mode-btn{padding:6px 16px;font-size:11px;color:var(--t2);cursor:pointer;background:var(--bg2);border:none;border-right:1px solid var(--bd);transition:all .1s}
.mode-btn:hover{color:var(--t1);background:var(--bg3)}.mode-btn.on{color:var(--blu);background:rgba(88,166,255,.1)}
.view-hm,.view-tb{display:none}.view-hm.on,.view-tb.on{display:block}
.enhanced-tbl{width:100%;border-collapse:collapse;font-size:11px}
.enhanced-tbl th{background:var(--bg1);padding:6px 8px;text-align:left;font-weight:600;color:var(--t2);border-bottom:1px solid var(--bd);position:sticky;top:0}
.enhanced-tbl td{padding:6px 8px;border-bottom:1px solid var(--bd)}
.enhanced-tbl tr:hover td{background:var(--bg3)}
.change-item{display:flex;align-items:center;gap:8px;padding:4px 0;border-bottom:1px solid var(--bd)}
.change-item:last-child{border-bottom:none}
.change-item .name{flex:1;font-size:11px}.change-item .rk{font-size:10px;color:var(--t2);min-width:60px}.change-item .delta{font-size:10px;font-weight:600;min-width:50px;text-align:right}
.arrow-up{color:var(--red)}.arrow-down{color:var(--grn)}
.hmlabel{padding:8px 12px;display:flex;align-items:center;gap:8px;font-size:9px;color:var(--t2);flex-wrap:wrap}
.hmlabel-item{display:flex;align-items:center;gap:3px}.hmlabel-c{width:12px;height:12px;border-radius:2px}
.kline-container{width:100%;height:180px}</style></head><body><div class="app">
<div class="side">
<div class="slogo"><div class="ico">DJ</div><b>掘金动量</b></div>
<nav class="snav"><div class="ng">核心</div>
<div class="ni on" onclick="go('dashboard',this)">行情总览</div>
<div class="ni" onclick="go('heatmap',this)">趋势热力</div>
<div class="ng">个股</div><div class="ni">动量个股</div>
<div class="ni">强势股池<span class="badge">23</span></div>
<div class="ni">涨停池<span class="badge">17</span></div>
<div class="ni">跌停池</div>
<div class="ng">数据</div><div class="ni">基础数据</div><div class="ni">行情数据</div>
<div class="ng">交易</div><div class="ni">交易记账</div><div class="ni">系统设置</div>
</nav></div>
<div class="marea">
<div class="topbar"><div class="bc">掘金动量 / <b id="bp">行情总览</b></div>
<div class="topbar-r"><span style="font-size:10px;color:var(--tm)">交易日:</span><input type="date" value="2025-04-22"><button class="btn" onclick="refreshData()">刷新</button></div></div>
<div class="cnt page on" id="pg-dashboard">
<div class="demo-note">Demo模式 - 数据为模拟展示,点击任意详情可查看详情弹窗</div>
<div class="mfilter"><span class="mlbl">市场范围</span>
<div class="mtab on" onclick="switchMarket(this)">全A</div>
<div class="mtab" onclick="switchMarket(this)">上证</div>
<div class="mtab" onclick="switchMarket(this)">深证</div>
<div class="mtab" onclick="switchMarket(this)">创业板</div>
<div class="mtab" onclick="switchMarket(this)">科创板</div>
<div class="mtab" onclick="switchMarket(this)">中证1000</div>
</div>
<div class="ministats">
<div class="ministat"><div class="lbl">上涨家数</div><div class="val vr">2,618</div><div class="sub">占比 50.2%</div></div>
<div class="ministat"><div class="lbl">下跌家数</div><div class="val vg">2,318</div><div class="sub">占比 44.4%</div></div>
<div class="ministat"><div class="lbl">涨停家数</div><div class="val vr">67</div><div class="sub">较昨日 +12</div></div>
<div class="ministat"><div class="lbl">跌停家数</div><div class="val vg">18</div><div class="sub">较昨日 -5</div></div>
</div>
<div class="sentrow">
<div class="sentcard hot"><div class="snm">全A情绪</div><div class="sval shot">72</div><div class="slabel shot">偏热</div><div class="sbar"><div class="sf sf-h" style="width:72%"></div></div><div class="smeta"><span>涨停 67</span><span>跌停 18</span><span>涨跌比 1.13</span></div></div>
<div class="sentcard warm"><div class="snm">中证1000</div><div class="sval swarm">68</div><div class="slabel swarm">偏热</div><div class="sbar"><div class="sf sf-w" style="width:68%"></div></div><div class="smeta"><span>涨停 45</span><span>跌停 12</span><span>涨跌比 1.21</span></div></div>
<div class="sentcard neutral"><div class="snm">创业板</div><div class="sval sneutral">52</div><div class="slabel sneutral">中性</div><div class="sbar"><div class="sf sf-n" style="width:52%"></div></div><div class="smeta"><span>涨停 28</span><span>跌停 8</span><span>涨跌比 0.95</span></div></div>
<div class="sentcard cool"><div class="snm">科创板</div><div class="sval scool">38</div><div class="slabel scool">偏冷</div><div class="sbar"><div class="sf sf-c" style="width:38%"></div></div><div class="smeta"><span>涨停 8</span><span>跌停 5</span><span>涨跌比 0.82</span></div></div>
</div><div class="drow1">
<div class="card"><div class="chd"><div class="cht"><div class="dot" style="background:var(--red)"></div>涨跌分布</div></div>
<div class="cbd" style="padding:4px 8px 8px"><div class="bchart">
<div class="bgrp"><div class="bval">67</div><div class="bar r" style="height:12px"></div><div class="blbl">+10%</div></div>
<div class="bgrp"><div class="bval">47</div><div class="bar r" style="height:9px"></div><div class="blbl">7-10%</div></div>
<div class="bgrp"><div class="bval">89</div><div class="bar r" style="height:14px"></div><div class="blbl">5-7%</div></div>
<div class="bgrp"><div class="bval">230</div><div class="bar r" style="height:24px"></div><div class="blbl">3-5%</div></div>
<div class="bgrp"><div class="bval">2056</div><div class="bar r" style="height:82px"></div><div class="blbl">0-3%</div></div>
<div class="bgrp"><div class="bval">207</div><div class="bar gr" style="height:17px"></div><div class="blbl"></div></div>
<div class="bgrp"><div class="bval">2174</div><div class="bar g" style="height:87px"></div><div class="blbl">0-3%</div></div>
<div class="bgrp"><div class="bval">218</div><div class="bar g" style="height:18px"></div><div class="blbl">3-5%</div></div>
<div class="bgrp"><div class="bval">49</div><div class="bar g" style="height:8px"></div><div class="blbl">5-7%</div></div>
<div class="bgrp"><div class="bval">28</div><div class="bar g" style="height:6px"></div><div class="blbl">-7%</div></div>
<div class="bgrp"><div class="bval">18</div><div class="bar g" style="height:4px"></div><div class="blbl">-10%</div></div>
</div></div></div>
<div class="card"><div class="chd"><div class="cht"><div class="dot" style="background:var(--blu)"></div>趋势板块 TOP 10</div></div>
<div class="cbd" style="padding:0"><div class="tw" style="max-height:230px;overflow-y:auto">
<table><thead><tr><th style="width:28px">#</th><th>板块</th><th style="text-align:center">动量</th><th style="text-align:center">涨停</th><th style="text-align:center">新高</th><th></th></tr></thead><tbody>
<tr><td><span class="rb r1">1</span></td><td><b>零售</b></td><td style="text-align:center"><span class="sig-u">Rank 7</span></td><td style="text-align:center" class="vr">7</td><td style="text-align:center" class="vr">4</td><td><button class="btn" onclick="showDetail('零售')">详情</button></td></tr>
<tr><td><span class="rb r2">2</span></td><td><b>化学制药</b></td><td style="text-align:center"><span class="sig-u">Rank 6</span></td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">5</td><td><button class="btn" onclick="showDetail('化学制药')">详情</button></td></tr>
<tr><td><span class="rb r3">3</span></td><td><b>食品</b></td><td style="text-align:center"><span class="sig-o">Rank 4</span></td><td style="text-align:center" class="vr">6</td><td style="text-align:center" class="vr">4</td><td><button class="btn" onclick="showDetail('食品')">详情</button></td></tr>
<tr><td><span class="rb ro">4</span></td><td>畜牧业</td><td style="text-align:center"><span class="sig-o">Rank 2</span></td><td style="text-align:center" class="">2</td><td style="text-align:center" class="">3</td><td><button class="btn" onclick="showDetail('畜牧业')">详情</button></td></tr>
<tr><td><span class="rb ro">5</span></td><td>贵金属</td><td style="text-align:center"><span class="sig-n">Rank 5</span></td><td style="text-align:center" class="">3</td><td style="text-align:center" class="">4</td><td><button class="btn" onclick="showDetail('贵金属')">详情</button></td></tr>
<tr><td><span class="rb ro">6</span></td><td>生物医药</td><td style="text-align:center"><span class="sig-n">Rank 1</span></td><td style="text-align:center" class="">1</td><td style="text-align:center" class="vr">8</td><td><button class="btn" onclick="showDetail('生物医药')">详情</button></td></tr>
<tr><td><span class="rb ro">7</span></td><td>物流</td><td style="text-align:center"><span class="sig-n">Rank 7</span></td><td style="text-align:center" class="vr">7</td><td style="text-align:center" class="">4</td><td><button class="btn" onclick="showDetail('物流')">详情</button></td></tr>
<tr><td><span class="rb ro">8</span></td><td>半导体</td><td style="text-align:center"><span class="sig-n">Rank 2</span></td><td style="text-align:center" class="">2</td><td style="text-align:center" class="">2</td><td><button class="btn" onclick="showDetail('半导体')">详情</button></td></tr>
<tr><td><span class="rb ro">9</span></td><td>化学制品</td><td style="text-align:center"><span class="sig-n">Rank 7</span></td><td style="text-align:center" class="">7</td><td style="text-align:center" class="">6</td><td><button class="btn" onclick="showDetail('化学制品')">详情</button></td></tr>
<tr><td><span class="rb ro">10</span></td><td>商业物业</td><td style="text-align:center"><span class="sig-n">Rank 1</span></td><td style="text-align:center" class="">1</td><td style="text-align:center" class="">0</td><td><button class="btn" onclick="showDetail('商业物业')">详情</button></td></tr>
</tbody></table></div></div></div></div>
<div class="drow2">
<div class="card"><div class="chd"><div class="cht"><div class="dot" style="background:var(--org)"></div>趋势变化榜</div></div><div class="cbd">
<div class="change-item"><div class="name">物流</div><div class="rk">排名 7</div><div class="delta arrow-up">上升 5位</div></div>
<div class="change-item"><div class="name">零售</div><div class="rk">排名 1</div><div class="delta arrow-up">上升 4位</div></div>
<div class="change-item"><div class="name">化学制药</div><div class="rk">排名 2</div><div class="delta arrow-up">上升 3位</div></div>
<div class="change-item"><div class="name">食品</div><div class="rk">排名 3</div><div class="delta arrow-up">上升 2位</div></div>
<div class="change-item"><div class="name">软件开发</div><div class="rk">排名 35</div><div class="delta arrow-down">下降 8位</div></div>
<div class="change-item"><div class="name">新能源</div><div class="rk">排名 42</div><div class="delta arrow-down">下降 6位</div></div>
</div></div>
<div class="card"><div class="chd"><div class="cht"><div class="dot" style="background:var(--grn)"></div>强势板块连续统计</div></div>
<div class="cbd" style="padding:0"><table><thead><tr><th>板块</th><th style="text-align:center">连续天数</th><th style="text-align:center">当前排名</th><th style="text-align:center">平均排名</th></tr></thead><tbody>
<tr><td><b>零售</b></td><td style="text-align:center"><span class="sig sig-u">5天</span></td><td style="text-align:center">1</td><td style="text-align:center">2.4</td></tr>
<tr><td><b>食品</b></td><td style="text-align:center"><span class="sig sig-u">4天</span></td><td style="text-align:center">3</td><td style="text-align:center">3.2</td></tr>
<tr><td><b>化学制药</b></td><td style="text-align:center"><span class="sig sig-o">3天</span></td><td style="text-align:center">2</td><td style="text-align:center">4.0</td></tr>
<tr><td><b>物流</b></td><td style="text-align:center"><span class="sig sig-o">3天</span></td><td style="text-align:center">7</td><td style="text-align:center">12.0</td></tr>
<tr><td><b>贵金属</b></td><td style="text-align:center"><span class="sig sig-n">2天</span></td><td style="text-align:center">5</td><td style="text-align:center">8.5</td></tr>
</tbody></table></div></div></div></div></div><div class="cnt page" id="pg-heatmap">
<div class="fbar"><label>动量周期:</label><select>
<option>1日</option>
<option>3日</option>
<option>5日</option>
<option>10日</option>
<option>15日</option>
<option selected>20日</option>
<option>30日</option>
</select><label>日期:</label><input type="date" value="2025-04-22"><button class="btn">刷新</button></div>
<div class="mode-switch">
<div class="mode-btn on" onclick="switchMode(this,'hm')">热力矩阵</div>
<div class="mode-btn" onclick="switchMode(this,'tb')">增强表格</div></div>
<div class="view-hm on" id="view-hm"><div class="hmc"><table class="hmt"><thead>
<tr><th style="width:80px;text-align:left;padding:4px 10px;font-size:10px">2级行业</th>
<th>04-22</th>
<th>04-21</th>
<th>04-18</th>
<th>04-17</th>
<th>04-16</th>
<th>04-15</th>
<th>04-14</th>
<th>04-11</th>
<th>04-10</th>
<th>04-09</th>
</tr></thead><tbody>
<tr><td class="hnc">零售</td><td><div class="hcell h1" onclick="showDetail('零售')"><div class="hrk">1</div></div></td><td><div class="hcell h1" onclick="showDetail('零售')"><div class="hrk">1</div></div></td><td><div class="hcell h1" onclick="showDetail('零售')"><div class="hrk">1</div></div></td><td><div class="hcell h3" onclick="showDetail('零售')"><div class="hrk">3</div></div></td><td><div class="hcell h8" onclick="showDetail('零售')"><div class="hrk">8</div></div></td><td><div class="hcell h10" onclick="showDetail('零售')"><div class="hrk">10</div></div></td><td><div class="hcell h2" onclick="showDetail('零售')"><div class="hrk">2</div></div></td><td><div class="hcell h5" onclick="showDetail('零售')"><div class="hrk">5</div></div></td><td><div class="hcell h8" onclick="showDetail('零售')"><div class="hrk">8</div></div></td><td><div class="hcell h8" onclick="showDetail('零售')"><div class="hrk">8</div></div></td></tr>
<tr><td class="hnc">化学制药</td><td><div class="hcell h2" onclick="showDetail('化学制药')"><div class="hrk">2</div></div></td><td><div class="hcell h4" onclick="showDetail('化学制药')"><div class="hrk">4</div></div></td><td><div class="hcell h5" onclick="showDetail('化学制药')"><div class="hrk">5</div></div></td><td><div class="hcell h5" onclick="showDetail('化学制药')"><div class="hrk">5</div></div></td><td><div class="hcell h6" onclick="showDetail('化学制药')"><div class="hrk">6</div></div></td><td><div class="hcell h2" onclick="showDetail('化学制药')"><div class="hrk">2</div></div></td><td><div class="hcell h3" onclick="showDetail('化学制药')"><div class="hrk">3</div></div></td><td><div class="hcell h3" onclick="showDetail('化学制药')"><div class="hrk">3</div></div></td><td><div class="hcell h7" onclick="showDetail('化学制药')"><div class="hrk">7</div></div></td><td><div class="hcell hd" onclick="showDetail('化学制药')"><div class="hrk">11</div></div></td></tr>
<tr><td class="hnc">食品</td><td><div class="hcell h3" onclick="showDetail('食品')"><div class="hrk">3</div></div></td><td><div class="hcell h2" onclick="showDetail('食品')"><div class="hrk">2</div></div></td><td><div class="hcell h2" onclick="showDetail('食品')"><div class="hrk">2</div></div></td><td><div class="hcell h1" onclick="showDetail('食品')"><div class="hrk">1</div></div></td><td><div class="hcell h2" onclick="showDetail('食品')"><div class="hrk">2</div></div></td><td><div class="hcell h5" onclick="showDetail('食品')"><div class="hrk">5</div></div></td><td><div class="hcell h5" onclick="showDetail('食品')"><div class="hrk">5</div></div></td><td><div class="hcell h2" onclick="showDetail('食品')"><div class="hrk">2</div></div></td><td><div class="hcell h2" onclick="showDetail('食品')"><div class="hrk">2</div></div></td><td><div class="hcell h2" onclick="showDetail('食品')"><div class="hrk">2</div></div></td></tr>
<tr><td class="hnc">畜牧业</td><td><div class="hcell h4" onclick="showDetail('畜牧业')"><div class="hrk">4</div></div></td><td><div class="hcell h3" onclick="showDetail('畜牧业')"><div class="hrk">3</div></div></td><td><div class="hcell h9" onclick="showDetail('畜牧业')"><div class="hrk">9</div></div></td><td><div class="hcell h4" onclick="showDetail('畜牧业')"><div class="hrk">4</div></div></td><td><div class="hcell h4" onclick="showDetail('畜牧业')"><div class="hrk">4</div></div></td><td><div class="hcell h1" onclick="showDetail('畜牧业')"><div class="hrk">1</div></div></td><td><div class="hcell h4" onclick="showDetail('畜牧业')"><div class="hrk">4</div></div></td><td><div class="hcell h7" onclick="showDetail('畜牧业')"><div class="hrk">7</div></div></td><td><div class="hcell h5" onclick="showDetail('畜牧业')"><div class="hrk">5</div></div></td><td><div class="hcell h3" onclick="showDetail('畜牧业')"><div class="hrk">3</div></div></td></tr>
<tr><td class="hnc">贵金属</td><td><div class="hcell h5" onclick="showDetail('贵金属')"><div class="hrk">5</div></div></td><td><div class="hcell h6" onclick="showDetail('贵金属')"><div class="hrk">6</div></div></td><td><div class="hcell h4" onclick="showDetail('贵金属')"><div class="hrk">4</div></div></td><td><div class="hcell h2" onclick="showDetail('贵金属')"><div class="hrk">2</div></div></td><td><div class="hcell h3" onclick="showDetail('贵金属')"><div class="hrk">3</div></div></td><td><div class="hcell h6" onclick="showDetail('贵金属')"><div class="hrk">6</div></div></td><td><div class="hcell h10" onclick="showDetail('贵金属')"><div class="hrk">10</div></div></td><td><div class="hcell h1" onclick="showDetail('贵金属')"><div class="hrk">1</div></div></td><td><div class="hcell h6" onclick="showDetail('贵金属')"><div class="hrk">6</div></div></td><td><div class="hcell h6" onclick="showDetail('贵金属')"><div class="hrk">6</div></div></td></tr>
<tr><td class="hnc">生物医药</td><td><div class="hcell h6" onclick="showDetail('生物医药')"><div class="hrk">6</div></div></td><td><div class="hcell h5" onclick="showDetail('生物医药')"><div class="hrk">5</div></div></td><td><div class="hcell h6" onclick="showDetail('生物医药')"><div class="hrk">6</div></div></td><td><div class="hcell h7" onclick="showDetail('生物医药')"><div class="hrk">7</div></div></td><td><div class="hcell h5" onclick="showDetail('生物医药')"><div class="hrk">5</div></div></td><td><div class="hcell h4" onclick="showDetail('生物医药')"><div class="hrk">4</div></div></td><td><div class="hcell h1" onclick="showDetail('生物医药')"><div class="hrk">1</div></div></td><td><div class="hcell h4" onclick="showDetail('生物医药')"><div class="hrk">4</div></div></td><td><div class="hcell h3" onclick="showDetail('生物医药')"><div class="hrk">3</div></div></td><td><div class="hcell h5" onclick="showDetail('生物医药')"><div class="hrk">5</div></div></td></tr>
<tr><td class="hnc">物流</td><td><div class="hcell h7" onclick="showDetail('物流')"><div class="hrk">7</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">11</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">12</div></div></td><td><div class="hcell h10" onclick="showDetail('物流')"><div class="hrk">10</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('物流')"><div class="hrk">15</div></div></td></tr>
<tr><td class="hnc">半导体</td><td><div class="hcell h8" onclick="showDetail('半导体')"><div class="hrk">8</div></div></td><td><div class="hcell h7" onclick="showDetail('半导体')"><div class="hrk">7</div></div></td><td><div class="hcell h7" onclick="showDetail('半导体')"><div class="hrk">7</div></div></td><td><div class="hcell h6" onclick="showDetail('半导体')"><div class="hrk">6</div></div></td><td><div class="hcell h7" onclick="showDetail('半导体')"><div class="hrk">7</div></div></td><td><div class="hcell h3" onclick="showDetail('半导体')"><div class="hrk">3</div></div></td><td><div class="hcell h6" onclick="showDetail('半导体')"><div class="hrk">6</div></div></td><td><div class="hcell h8" onclick="showDetail('半导体')"><div class="hrk">8</div></div></td><td><div class="hcell h4" onclick="showDetail('半导体')"><div class="hrk">4</div></div></td><td><div class="hcell h4" onclick="showDetail('半导体')"><div class="hrk">4</div></div></td></tr>
<tr><td class="hnc">化学制品</td><td><div class="hcell h9" onclick="showDetail('化学制品')"><div class="hrk">9</div></div></td><td><div class="hcell h8" onclick="showDetail('化学制品')"><div class="hrk">8</div></div></td><td><div class="hcell h3" onclick="showDetail('化学制品')"><div class="hrk">3</div></div></td><td><div class="hcell h8" onclick="showDetail('化学制品')"><div class="hrk">8</div></div></td><td><div class="hcell h10" onclick="showDetail('化学制品')"><div class="hrk">10</div></div></td><td><div class="hcell h8" onclick="showDetail('化学制品')"><div class="hrk">8</div></div></td><td><div class="hcell h3" onclick="showDetail('化学制品')"><div class="hrk">3</div></div></td><td><div class="hcell h6" onclick="showDetail('化学制品')"><div class="hrk">6</div></div></td><td><div class="hcell h9" onclick="showDetail('化学制品')"><div class="hrk">9</div></div></td><td><div class="hcell h9" onclick="showDetail('化学制品')"><div class="hrk">9</div></div></td></tr>
<tr><td class="hnc">商业物业</td><td><div class="hcell h10" onclick="showDetail('商业物业')"><div class="hrk">10</div></div></td><td><div class="hcell h9" onclick="showDetail('商业物业')"><div class="hrk">9</div></div></td><td><div class="hcell h8" onclick="showDetail('商业物业')"><div class="hrk">8</div></div></td><td><div class="hcell h10" onclick="showDetail('商业物业')"><div class="hrk">10</div></div></td><td><div class="hcell h9" onclick="showDetail('商业物业')"><div class="hrk">9</div></div></td><td><div class="hcell h7" onclick="showDetail('商业物业')"><div class="hrk">7</div></div></td><td><div class="hcell h7" onclick="showDetail('商业物业')"><div class="hrk">7</div></div></td><td><div class="hcell hd" onclick="showDetail('商业物业')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('商业物业')"><div class="hrk">11</div></div></td><td><div class="hcell hd" onclick="showDetail('商业物业')"><div class="hrk">12</div></div></td></tr>
<tr><td class="hnc">软件开发</td><td><div class="hcell hd" onclick="showDetail('软件开发')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('软件开发')"><div class="hrk">12</div></div></td><td><div class="hcell h10" onclick="showDetail('软件开发')"><div class="hrk">10</div></div></td><td><div class="hcell h9" onclick="showDetail('软件开发')"><div class="hrk">9</div></div></td><td><div class="hcell hd" onclick="showDetail('软件开发')"><div class="hrk">11</div></div></td><td><div class="hcell h9" onclick="showDetail('软件开发')"><div class="hrk">9</div></div></td><td><div class="hcell h8" onclick="showDetail('软件开发')"><div class="hrk">8</div></div></td><td><div class="hcell h9" onclick="showDetail('软件开发')"><div class="hrk">9</div></div></td><td><div class="hcell h10" onclick="showDetail('软件开发')"><div class="hrk">10</div></div></td><td><div class="hcell h10" onclick="showDetail('软件开发')"><div class="hrk">10</div></div></td></tr>
<tr><td class="hnc">新能源</td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">11</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">11</div></div></td><td><div class="hcell h9" onclick="showDetail('新能源')"><div class="hrk">9</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">11</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('新能源')"><div class="hrk">13</div></div></td></tr>
<tr><td class="hnc">传媒</td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">25</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">11</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('传媒')"><div class="hrk">14</div></div></td></tr>
<tr><td class="hnc">银行</td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">12</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('银行')"><div class="hrk">11</div></div></td></tr>
<tr><td class="hnc">房地产</td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('房地产')"><div class="hrk">16</div></div></td></tr>
<tr><td class="hnc">汽车</td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">14</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">15</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('汽车')"><div class="hrk">18</div></div></td></tr>
<tr><td class="hnc">电子器件</td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">11</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">16</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('电子器件')"><div class="hrk">17</div></div></td></tr>
<tr><td class="hnc">饮料</td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">11</div></div></td><td><div class="hcell h10" onclick="showDetail('饮料')"><div class="hrk">10</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">13</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">18</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">17</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">17</div></div></td><td><div class="hcell hd" onclick="showDetail('饮料')"><div class="hrk">19</div></div></td></tr>
<tr><td class="hnc">建筑装饰</td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">19</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">25</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">19</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('建筑装饰')"><div class="hrk">20</div></div></td></tr>
<tr><td class="hnc">通信</td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">30</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">28</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">25</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">25</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">28</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">25</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">22</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">20</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">25</div></div></td><td><div class="hcell hd" onclick="showDetail('通信')"><div class="hrk">22</div></div></td></tr>
</tbody></table></div>
<div class="hmlabel"><span>排名:</span>
<div class="hmlabel-item"><div class="hmlabel-c h1"></div><span>1</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h2"></div><span>2</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h3"></div><span>3</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h4"></div><span>4</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h5"></div><span>5</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h6"></div><span>6</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h7"></div><span>7</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h8"></div><span>8</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h9"></div><span>9</span></div>
<div class="hmlabel-item"><div class="hmlabel-c h10"></div><span>10</span></div>
<div class="hmlabel-item"><div class="hmlabel-c hd"></div><span>>10</span></div></div></div>
<div class="view-tb" id="view-tbl"><div class="card"><div class="cbd" style="padding:0"><div class="tw" style="max-height:500px;overflow-y:auto">
<table class="enhanced-tbl"><thead><tr><th>板圱</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th style="text-align:center">排名</th>
<th>趋势</th></tr></thead><tbody>
<tr><td><b>零售</b></td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="">8</td><td class="arrow-down">下降 7位</td></tr>
<tr><td><b>化学制药</b></td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="">11</td><td class="arrow-down">下降 9位</td></tr>
<tr><td><b>食品</b></td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">2</td><td class="arrow-up">上升 1位</td></tr>
<tr><td><b>畜牧业</b></td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="vr">3</td><td class="arrow-up">上升 1位</td></tr>
<tr><td><b>贵金属</b></td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">2</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="">6</td><td class="arrow-down">下降 1位</td></tr>
<tr><td><b>生物医药</b></td><td style="text-align:center" class="">6</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="vr">5</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">1</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="vr">5</td><td class="arrow-up">上升 1位</td></tr>
<tr><td><b>物流</b></td><td style="text-align:center" class="">7</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="vg">15</td><td class="arrow-down">下降 8位</td></tr>
<tr><td><b>半导体</b></td><td style="text-align:center" class="">8</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="vr">4</td><td style="text-align:center" class="vr">4</td><td class="arrow-up">上升 4位</td></tr>
<tr><td><b>化学制品</b></td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="vr">3</td><td style="text-align:center" class="">6</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">9</td><td class="arrow-down">下降 0位</td></tr>
<tr><td><b>商业物业</b></td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="">7</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">12</td><td class="arrow-down">下降 2位</td></tr>
<tr><td><b>软件开发</b></td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">8</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">10</td><td class="arrow-up">上升 5位</td></tr>
<tr><td><b>新能源</b></td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">9</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="">13</td><td class="arrow-up">上升 7位</td></tr>
<tr><td><b>传媒</b></td><td style="text-align:center" class="vg">25</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">14</td><td class="arrow-up">上升 11位</td></tr>
<tr><td><b>银行</b></td><td style="text-align:center" class="">12</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="">12</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="">11</td><td class="arrow-up">上升 1位</td></tr>
<tr><td><b>房地产</b></td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">14</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">16</td><td class="arrow-up">上升 2位</td></tr>
<tr><td><b>汽车</b></td><td style="text-align:center" class="">14</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="vg">15</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">18</td><td class="arrow-down">下降 4位</td></tr>
<tr><td><b>电子器件</b></td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">16</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">17</td><td class="arrow-down">下降 1位</td></tr>
<tr><td><b>饮料</b></td><td style="text-align:center" class="">11</td><td style="text-align:center" class="">10</td><td style="text-align:center" class="">13</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">18</td><td style="text-align:center" class="vg">17</td><td style="text-align:center" class="vg">17</td><td style="text-align:center" class="vg">19</td><td class="arrow-down">下降 8位</td></tr>
<tr><td><b>建筑装饰</b></td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">19</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">25</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">19</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">20</td><td class="arrow-up">上升 2位</td></tr>
<tr><td><b>通信</b></td><td style="text-align:center" class="vg">30</td><td style="text-align:center" class="vg">28</td><td style="text-align:center" class="vg">25</td><td style="text-align:center" class="vg">25</td><td style="text-align:center" class="vg">28</td><td style="text-align:center" class="vg">25</td><td style="text-align:center" class="vg">22</td><td style="text-align:center" class="vg">20</td><td style="text-align:center" class="vg">25</td><td style="text-align:center" class="vg">22</td><td class="arrow-up">上升 8位</td></tr>
</tbody></table></div></div></div></div><div class="modal-ov" id="modal"><div class="modal">
<div class="mhd"><h3 id="mtl">板圱详情</h3>
<button class="mcl" onclick="closeModal()">X</button></div>
<div class="mbd">
<div class="detailrow">
<div class="stat-card"><div class="sl">当前动量</div><div class="sv">12.34</div></div>
<div class="stat-card"><div class="sl">近5日趋势</div><div class="sv">持续上升</div></div>
<div class="stat-card"><div class="sl">涨停家数</div><div class="sv">3</div></div>
<div class="stat-card"><div class="sl">创新高</div><div class="sv">7</div></div>
<div class="stat-card"><div class="sl">连续天数</div><div class="sv">5天</div></div>
<div class="stat-card"><div class="sl">平均排名</div><div class="sv">2.4</div></div>
</div>
<div class="klinebox"><div id="detail-kline" class="kline-container"></div></div>
<div class="detail-tabs">
<div class="detail-tab on">概见</div>
<div class="detail-tab">排名趋势</div>
<div class="detail-tab">成分股</div>
<div class="detail-tab">历史数据</div>
</div>
<div class="detail-view on" id="dt-1">
<p style="font-size:11px;color:var(--t2)">该板块近20日动量排名第1连续5天位列前三内有7只涨停股、4只创新高强势明显。</p>
</div>
<div class="detail-view" id="dt-2"><div id="detail-rank-chart"></div></div>
<div class="detail-view" id="dt-3">
<table><thead><tr><th>股票</th><th>涨幅</th><th>排名</th></tr></thead><tbody>
<tr><td>某股A</td><td class="vr">+10.01%</td><td>1</td></tr>
<tr><td>某股B</td><td class="vr">+9.98%</td><td>2</td></tr>
<tr><td>某股C</td><td class="vr">+8.50%</td><td>3</td></tr>
<tr><td>某股D</td><td class="vr">+7.20%</td><td>4</td></tr>
<tr><td>某股E</td><td class="vr">+6.80%</td><td>5</td></tr>
</tbody></table></div>
<div class="detail-view" id="dt-4"><p style="font-size:11px;color:var(--t2)">历史数据展示区域...</p></div>
</div></div></div>
<script>
function go(p,el){document.querySelectorAll(".page").forEach(function(x){x.classList.remove("on")});document.querySelectorAll(".ni").forEach(function(x){x.classList.remove("on")});document.getElementById("pg-"+p).classList.add("on");el.classList.add("on");document.getElementById("bp").textContent=p==="dashboard"?"行情总览":"趋势热力"}
function switchMarket(el){document.querySelectorAll(".mtab").forEach(function(x){x.classList.remove("on")});el.classList.add("on");alert("切换到 "+el.textContent+"市场")}
function switchMode(el,m){document.querySelectorAll(".mode-btn").forEach(function(x){x.classList.remove("on")});el.classList.add("on");document.getElementById("view-hm").classList.toggle("on",m==="hm");document.getElementById("view-tbl").classList.toggle("on",m==="tb")}
function showDetail(nm){document.getElementById("mtl").textContent=nm+"板块详情";document.getElementById("modal").classList.add("show")}
function closeModal(){document.getElementById("modal").classList.remove("show")}
function refreshData(){alert("数据刷新中...");setTimeout(function(){alert("刷新完成!")},500)}
document.getElementById("modal").addEventListener("click",function(e){if(e.target===this)closeModal()})
// ECharts kline init
setTimeout(function(){
if(typeof echarts!=="undefined"){
var chart=echarts.init(document.getElementById("detail-kline"));
chart.setOption({title:{text:"K线示意",textStyle:{color:"#e6edf3",fontSize:11}},grid:{left:50,right:20,top:30,bottom:20},xAxis:{data:["04-01","04-02","04-03","04-04","04-05","04-08","04-09","04-10","04-11","04-14","04-15","04-16","04-17","04-18","04-21","04-22"],axisLine:{lineStyle:{color:"#30363d"}},axisLabel:{color:"#8b949e"}},yAxis:{splitLine:{lineStyle:{color:"#21262d"}},axisLabel:{color:"#8b949e"}},series:[{type:"candlestick",data:[[10,11,9.5,10.5],[10.5,11.5,10,11],[11,12,10.5,11.5],[11.5,12.5,11,12],[12,13,11.5,12.5],[12.5,13.5,12,13],[13,14,12.5,13.5],[13.5,14.5,13,14],[14,15,13.5,14.5],[14.5,15.5,14,15],[15,16,14.5,15.5],[15.5,16.5,15,16],[16,17,15.5,16.5],[16.5,17.5,16,17],[17,18,16.5,17.5],[17.5,18.5,17,18]]}]});
var rankChart=echarts.init(document.getElementById("detail-rank-chart"));
rankChart.setOption({title:{text:"20日排名趋势",textStyle:{color:"#e6edf3",fontSize:11}},grid:{left:40,right:20,top:30,bottom:20},xAxis:{data:["04-01","04-05","04-10","04-15","04-22"],axisLine:{lineStyle:{color:"#30363d"}},axisLabel:{color:"#8b949e"}},yAxis:{inverse:true,splitLine:{lineStyle:{color:"#21262d"}},axisLabel:{color:"#8b949e"}},series:[{type:"line",data:[5,3,2,1,1],smooth:true,itemStyle:{color:"#f85149"},areaStyle:{color:"rgba(248,81,73,0.2)"}}]});
}});
</script>
</body></html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,453 @@
---
comet_change: refactor-architecture-analysis
role: technical-design
canonical_spec: openspec
---
# RuoYi-Vue 架构梳理技术设计
## 概述
本设计文档描述了 RuoYi-Vue 系统架构梳理的完整技术方案,采用**分层递进式分析**方法,从前端到后端、从交互到数据库,逐层深入分析现有系统架构,输出 6 份完整的架构文档和优化建议。
## 分析方法论
### 分层递进式分析框架
```
┌─────────────────────────────────────────────────────────┐
│ 架构梳理分析流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 第 1 层:前端架构分析 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • 组件结构分析60+ 组件分类) │ │
│ │ • 路由与权限控制流 │ │
│ │ • Vuex 状态管理分析 │ │
│ │ • Axios API 调用层分析 │ │
│ └───────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 2 层:后端架构分析 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • Controller 层25+ 控制器) │ │
│ │ • Service 层(接口与实现) │ │
│ │ • Mapper 层MyBatis XML │ │
│ │ • Spring Security + JWT 安全配置 │ │
│ │ • AOP 切面(日志、数据权限、限流) │ │
│ └───────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 3 层:前后端交互分析 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • API 接口清单60+ 接口) │ │
│ │ • 请求/响应数据格式 │ │
│ │ • 错误处理机制 │ │
│ │ • 分页、排序、过滤规范 │ │
│ └───────────────────────────────────────────────────┘ │
│ ↓ │
│ 第 4 层:数据库结构分析 │
│ ┌───────────────────────────────────────────────────┐ │
│ │ • 表结构分析sys_*、book_*、stock_* │ │
│ │ • 索引与约束 │ │
│ │ • 查询模式与性能 │ │
│ └───────────────────────────────────────────────────┘ │
│ ↓ │
│ 整合输出6 份文档 + 优化建议清单 │
│ │
└─────────────────────────────────────────────────────────┘
```
## 前端架构分析设计
### 1. 组件结构分析
```
ruoyi-ui/src/
├── components/ # 通用组件30+
│ ├── 基础组件
│ │ ├── Breadcrumb/ # 面包屑导航
│ │ ├── Pagination/ # 分页组件
│ │ ├── DictTag/ # 字典标签
│ │ ├── Editor/ # 富文本编辑器
│ │ └── ...
│ ├── 布局组件
│ │ ├── Hamburger/ # 汉堡菜单
│ │ ├── Sidebar/ # 侧边栏
│ │ └── Navbar/ # 顶部导航
│ └── 业务组件
│ ├── FileUpload/ # 文件上传
│ ├── ImageUpload/ # 图片上传
│ └── ...
├── views/ # 页面视图50+
│ ├── dashboard/ # 首页
│ ├── system/ # 系统管理10+ 页面)
│ ├── booksystem/ # 账本系统6 页面)
│ ├── stocksystem/ # 股票系统8 页面)
│ ├── monitor/ # 监控模块5 页面)
│ └── tool/ # 工具模块5 页面)
└── layout/ # 布局框架
├── index.vue # 主布局
├── components/ # 布局组件
└── mixin/ # 布局混入
```
**分析重点**
- 组件复用程度
- 组件职责是否单一
- 是否存在过度耦合
- 组件命名规范性
### 2. 路由与权限控制流
```
路由加载流程:
┌──────────┐ ┌───────────┐ ┌──────────┐
│ 用户登录 │─────►│ 获取 Token │─────►│ 获取权限 │
└──────────┘ └───────────┘ └────┬─────┘
┌──────────────┐
│ 动态路由加载 │
│ (permission) │
└──────┬───────┘
┌──────────────┐
│ 路由守卫验证 │
│ (permission) │
└──────────────┘
```
**分析重点**
- 静态路由 vs 动态路由划分
- 权限加载机制roles、permissions
- 路由守卫实现
- 路由懒加载策略
### 3. Vuex 状态管理分析
```
Vuex Modules:
┌────────────────────────────────────────┐
│ Vuex Store │
├────────┬──────┬──────────┬─────────────┤
│ app │ user │ permission│ tagsView │
├────────┼──────┼──────────┼─────────────┤
│ 侧边栏 │ 用户 │ 动态路由 │ 标签页 │
│ 设备类型│ 信息 │ 权限菜单 │ 标签状态 │
│ 主题 │ Token│ 路由列表 │ 标签操作 │
└────────┴──────┴──────────┴─────────────┘
settings (系统设置)
```
**分析重点**
- 状态模块划分合理性
- 数据流向清晰度
- 状态持久化策略
- Getter 使用情况
### 4. Axios API 调用层分析
```
请求流程:
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ View │────►│ API 层 │────►│ Interceptor│────►│Backend │
│ (Vue) │ │ (Axios) │ │ (Token) │ │ (API) │
└─────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
│ ┌──────────┐
│ │ Response │
│ └────┬─────┘
│ │
│ ┌───────────────────────────────┘
│ ▼
│ ┌──────────┐
└────────│ Interceptor│
│ (Error) │
└────────────┘
```
**分析重点**
- 拦截器实现Token、错误处理
- 统一响应格式处理
- 超时配置
- 下载功能实现
## 后端架构分析设计
### 1. 分层架构分析
```
┌────────────────────────────────────────────────────┐
│ 后端分层架构 │
├────────────────────────────────────────────────────┤
│ │
│ Controller 层25+ 控制器) │
│ ┌──────────────────────────────────────────────┐ │
│ │ • 系统管理SysUserController 等 12 个 │ │
│ │ • 监控模块CacheController 等 5 个 │ │
│ │ • 业务模块AccountController 等 14 个 │ │
│ │ • 工具模块GenController 等 2 个 │ │
│ └──────────────────────────────────────────────┘ │
│ ↓ │
│ Service 层(接口 + 实现) │
│ ┌──────────────────────────────────────────────┐ │
│ │ • I*Service 接口定义 │ │
│ │ • *ServiceImpl 实现类 │ │
│ │ • 事务管理(@Transactional │ │
│ └──────────────────────────────────────────────┘ │
│ ↓ │
│ Mapper 层MyBatis XML
│ ┌──────────────────────────────────────────────┐ │
│ │ • *Mapper 接口 │ │
│ │ • *Mapper.xml SQL 映射 │ │
│ │ • 动态 SQL │ │
│ └──────────────────────────────────────────────┘ │
│ ↓ │
│ Domain 层(实体类) │
│ ┌──────────────────────────────────────────────┐ │
│ │ • 实体类Domain │ │
│ │ • VO/DTO视图对象 │ │
│ │ • 基类BaseEntity │ │
│ └──────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘
```
### 2. 安全配置分析
```
安全架构:
┌────────────────────────────────────────┐
│ Spring Security 配置 │
├────────────────────────────────────────┤
│ │
│ JWT Token 认证 │
│ ┌──────────────────────────────────┐ │
│ │ • Token 生成与验证 │ │
│ │ • Token 刷新机制 │ │
│ │ • Token 存储Redis │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ 权限控制 │
│ ┌──────────────────────────────────┐ │
│ │ • @PreAuthorize 注解 │ │
│ │ • 角色权限ROLE_* │ │
│ │ • 菜单权限system:user:list │ │
│ │ • 数据权限(@DataScope │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ AOP 切面 │
│ ┌──────────────────────────────────┐ │
│ │ • 日志切面(@Log │ │
│ │ • 数据源切面(@DataSource │ │
│ │ • 限流切面(@RateLimiter │ │
│ │ • 重复提交拦截 │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
```
### 3. 模块依赖关系
```
模块依赖图:
┌─────────────┐
│ ruoyi-admin │ (启动模块)
└──────┬──────┘
├─────────────┬─────────────┬──────────────┐
▼ ▼ ▼ ▼
┌───────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐
│ruoyi- │ │ruoyi- │ │book- │ │ruoyi- │
│framework │ │system │ │system │ │generator │
└─────┬─────┘ └─────┬──────┘ └────┬─────┘ └──────────┘
│ │ │
└─────────────┴──────────────┘
┌────────────┐
│ruoyi-common│
└────────────┘
```
## 前后端交互分析设计
### 1. API 接口清单
**系统管理 API**20+ 接口):
- `/system/user/*` - 用户管理
- `/system/role/*` - 角色管理
- `/system/menu/*` - 菜单管理
- `/system/dept/*` - 部门管理
- `/system/dict/*` - 字典管理
- ...
**业务 API**30+ 接口):
- `/booksystem/account/*` - 账户管理
- `/booksystem/book/*` - 账本管理
- `/booksystem/statistics/*` - 统计分析
- `/stocksystem/stocks/*` - 股票管理
- ...
### 2. 统一响应格式
```json
// 成功响应
{
"code": 200,
"msg": "操作成功",
"data": { ... }
}
// 分页响应
{
"code": 200,
"msg": "查询成功",
"total": 100,
"rows": [ ... ]
}
// 错误响应
{
"code": 500,
"msg": "错误信息"
}
```
### 3. 错误处理机制
```
错误处理链路:
┌─────────────────────────────────────────┐
│ 前端错误处理 │
├─────────────────────────────────────────┤
│ • 401 → 登录过期,弹出重新登录提示 │
│ • 500 → 弹出错误消息 │
│ • 其他 → Notification.error │
│ • 网络错误 → 提示连接异常 │
│ • 超时 → 提示请求超时 │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 后端错误处理 │
├─────────────────────────────────────────┤
│ • GlobalExceptionHandler │
│ • ServiceException │
│ • @Log 记录操作日志 │
│ • 业务异常 vs 系统异常 │
└─────────────────────────────────────────┘
```
## 数据库分析设计
### 1. 表结构分类
**系统管理表**sys_*
- `sys_user` - 用户表
- `sys_role` - 角色表
- `sys_menu` - 菜单表
- `sys_dept` - 部门表
- `sys_dict_*` - 字典表
- ...
**业务表**book_*、account_*
- `account` - 交易账户
- `book` - 账本
- `statistics` - 统计数据
- ...
**股票表**stock_*
- `stocks` - 股票信息
- `trends` - 趋势数据
- `stockindex` - 指数信息
- ...
### 2. 分析维度
- 表关系和外键约束
- 索引使用情况
- 冗余字段识别
- 查询性能瓶颈
- 审计字段create_time、update_time
## 输出文档结构
### 文档 1architecture-overview.md
- 系统总体架构图
- 技术栈总结
- 模块划分
- 关键设计模式
### 文档 2frontend-architecture.md
- 组件树分析
- 路由与权限流
- 状态管理分析
- API 调用层
- 存在的问题
### 文档 3backend-architecture.md
- 分层架构分析
- 安全配置
- AOP 切面
- 模块依赖
- 存在的问题
### 文档 4api-interaction-spec.md
- API 接口清单
- 请求/响应规范
- 错误处理机制
- 分页/排序/过滤
- 优化建议
### 文档 5database-structure.md
- 表结构清单
- 索引分析
- 查询模式
- 性能问题
- 优化建议
### 文档 6optimization-suggestions.md
- 10+ 条优化建议
- 每条包含:
- 问题描述
- 影响范围
- 推荐方案
- 实施优先级
## 质量保证
### 文档完整性检查
- [ ] 6 份文档全部输出
- [ ] 每份文档包含架构图
- [ ] 问题清单不少于 10 条
### 准确性验证
- [ ] 架构图与实际代码对照
- [ ] API 接口清单完整
- [ ] 表结构与实际数据库一致
### 可执行性
- [ ] 每条建议包含实施步骤
- [ ] 优先级明确
- [ ] 风险可控
## 风险与缓解
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 分析遗漏 | 后续重构缺少依据 | 建立检查清单,逐模块验证 |
| 文档冗长 | 可读性差 | 控制篇幅,结构化组织 |
| 建议不可操作 | 无法指导重构 | 包含具体实施步骤 |
## 后续步骤
完成架构梳理后,将依次执行:
1. refactor-frontend-modernization前端重构
2. refactor-backend-api后端 API 规范化)
3. refactor-database-optimization数据库优化
4. refactor-coding-standards编码规范
本架构梳理文档将为后续所有重构提供技术指引。

@ -0,0 +1,7 @@
change_name: add-unit-tests
phase: build
auto_transition: true
build_pause: null
verify_result: null
archived: false
workflow: full

@ -0,0 +1,23 @@
# P1 优化:完善单元测试
## 目标
为 RuoYi-Vue 后端核心 Service 层添加单元测试,提升代码质量和可维护性。
## 范围
- 系统管理模块 Serviceuser、role、menu
- book-system 业务模块 Service
- 工具类单元测试
## 技术栈
- JUnit 5
- Mockito
- Spring Boot Test
## 验收标准
- 核心 Service 测试覆盖率 > 70%
- 所有测试用例通过
- 集成到 CI/CD 流程

@ -0,0 +1,35 @@
# Tasks: 完善单元测试
## Task 1: 测试环境搭建
- [ ] 1.1 添加 JUnit 5 和 Mockito 依赖
- [ ] 1.2 配置测试基础类
- [ ] 1.3 创建测试目录结构
## Task 2: 系统管理模块测试
- [ ] 2.1 SysUserService 测试
- [ ] 2.2 SysRoleService 测试
- [ ] 2.3 SysMenuService 测试
## Task 3: 业务模块测试
- [ ] 3.1 AccountService 测试
- [ ] 3.2 BookService 测试
## Task 4: 工具类测试
- [ ] 4.1 字符串工具类测试
- [ ] 4.2 日期工具类测试
- [ ] 4.3 验证工具类测试
## Task 5: 集成测试
- [ ] 5.1 Controller 层集成测试
- [ ] 5.2 API 接口集成测试
## 验收标准
- [ ] 测试覆盖率 > 70%
- [ ] 所有测试通过
- [ ] 生成测试报告

@ -0,0 +1,8 @@
change_name: refactor-architecture-analysis
phase: archive
design_doc: docs/superpowers/specs/2026-06-28-ruoyi-architecture-analysis-design.md
auto_transition: true
build_pause: null
verify_result: pass
archived: true
workflow: full

@ -0,0 +1,43 @@
# Brainstorm Summary
- Change: refactor-architecture-analysis
- Date: 2026-06-28
## 确认的技术方案
**分层递进式分析**,按以下顺序执行:
1. 前端架构分析组件树、路由、状态管理、API 层)
2. 后端架构分析Controller/Service/Mapper 分层、安全配置、AOP 切面)
3. 前后端交互分析API 接口清单、请求/响应规范、错误处理)
4. 数据库结构分析(表结构、索引、查询模式)
**输出 6 份文档**
1. architecture-overview.md - 总体架构图和技术栈总结
2. frontend-architecture.md - 前端架构完整分析
3. backend-architecture.md - 后端架构完整分析
4. api-interaction-spec.md - 前后端交互规范
5. database-structure.md - 数据库结构分析
6. optimization-suggestions.md - 10+ 条优化建议
## 关键取舍与风险
**取舍**
- 选择分层分析而非模块驱动或问题驱动,确保全面覆盖
- 输出纯文档(不含代码实现),便于团队阅读理解
- 控制每篇文档在 10 页以内,避免过于冗长
**风险**
- 分析遗漏:建立检查清单,逐模块验证
- 建议不可操作:每条建议包含具体实施步骤
- 文档可读性:结构化组织,重点突出
## 测试策略
- 文档完整性检查6 份文档全部输出)
- 架构图准确性(与实际代码对照验证)
- 问题清单可执行性(明确问题描述和推荐方案)
- 交叉验证(前后端接口对应关系)
## Spec Patch

@ -0,0 +1,174 @@
# Design: 架构梳理方法与技术决策
## Architecture Overview
### 现有系统架构分析
```
┌─────────────────────────────────────────────────────────────┐
│ RuoYi-Vue 系统架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 前端 (ruoyi-ui) 后端 (Spring Boot) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Vue2 + Vue CLI │ HTTP/JSON │ ruoyi-admin │ │
│ │ Element UI │◄──────────►│ (启动配置) │ │
│ │ Vuex │ └────────┬─────────┘ │
│ │ Vue Router │ │ │
│ └────────┬─────────┘ ┌────────▼─────────┐ │
│ │ │ ruoyi-framework │ │
│ ┌────────┴─────────┐ │ (安全、配置) │ │
│ │ components/ │ └────────┬─────────┘ │
│ │ views/ │ │ │
│ │ api/ │ ┌────────▼─────────┐ │
│ │ store/ │ │ ruoyi-system │ │
│ │ router/ │ │ (系统管理) │ │
│ └──────────────────┘ └────────┬─────────┘ │
│ │ │
│ ┌─────────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ruoyi-common book-system quartz │
│ (工具类) (业务模块) (定时) │
│ │
│ ┌──────────────────┐ │
│ │ MySQL DB │ │
│ │ + Redis Cache │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 前端架构分析
```
ruoyi-ui/
├── src/
│ ├── api/ # API 接口定义
│ │ ├── booksystem/ # 业务模块 API
│ │ ├── system/ # 系统管理 API
│ │ └── monitor/ # 监控模块 API
│ ├── assets/ # 静态资源
│ ├── components/ # 通用组件
│ │ ├── Breadcrumb/ # 面包屑
│ │ ├── Pagination/ # 分页
│ │ ├── DictTag/ # 字典标签
│ │ └── ... # 30+ 组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── views/ # 页面视图
│ │ ├── dashboard/ # 首页
│ │ ├── system/ # 系统管理页面
│ │ └── booksystem/ # 业务页面
│ └── utils/ # 工具函数
```
### 后端架构分析
```
RuoYi-Vue/
├── ruoyi-admin/ # 启动模块
│ ├── controller/ # 控制器层
│ └── config/ # 配置类
├── ruoyi-framework/ # 框架核心
│ ├── security/ # 安全配置
│ ├── datasource/ # 数据源
│ └── aspectj/ # AOP 切面
├── ruoyi-system/ # 系统管理
│ ├── domain/ # 实体类
│ ├── mapper/ # MyBatis Mapper
│ └── service/ # 服务层
├── book-system/ # 业务模块
│ ├── controller/ # 业务控制器
│ ├── domain/ # 业务实体
│ ├── mapper/ # 业务 Mapper
│ └── service/ # 业务服务
├── ruoyi-common/ # 通用工具
│ ├── annotation/ # 自定义注解
│ ├── core/ # 核心类
│ ├── utils/ # 工具类
│ └── exception/ # 异常处理
└── ruoyi-generator/ # 代码生成器
```
### 前后端交互流程
```
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ User │─────►│ Vue UI │─────►│ API │─────►│ Spring │
│ Action │ │ (View) │ │ (Axios) │ │ Boot │
└─────────┘ └──────────┘ └──────────┘ └───┬────┘
▲ │
│ ▼
│ ┌──────────┐
│ │ Service │
│ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ Mapper │
│ └────┬─────┘
│ │
│ ┌─────────────────────────────────────────┘
│ ▼
│ ┌────────┐
└───│ Response│
└────────┘
```
## 分析方法论
### 1. 静态代码分析
- 使用工具扫描代码结构
- 分析模块依赖关系
- 识别循环依赖和耦合点
### 2. 动态行为分析
- 分析请求链路
- 分析数据流转
- 分析错误处理路径
### 3. 架构模式识别
- 识别使用的架构模式MVC、分层架构等
- 评估模式应用的合理性
- 识别反模式
## 技术决策
### 交互规范设计原则
1. **RESTful 风格**:统一使用 RESTful API 设计
2. **统一响应格式**
```json
{
"code": 200,
"msg": "操作成功",
"data": {}
}
```
3. **统一错误处理**
- 业务错误code != 200msg 说明原因
- 系统错误HTTP 500统一异常处理
4. **分页标准**
```json
{
"code": 200,
"msg": "查询成功",
"total": 100,
"rows": []
}
```
### 输出文档结构
1. **architecture.md** - 整体架构文档
2. **frontend-architecture.md** - 前端架构详细分析
3. **backend-architecture.md** - 后端架构详细分析
4. **api-specification.md** - 前后端交互规范
5. **optimization-suggestions.md** - 优化建议清单
## 风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 分析不完整 | 后续重构遗漏 | 建立检查清单,交叉验证 |
| 文档过时 | 失去参考价值 | 文档与代码同步更新机制 |
| 规范难以执行 | 团队不遵守 | 制定可执行的检查工具 |

@ -0,0 +1,73 @@
# Proposal: RuoYi-Vue 架构梳理与交互规范
## Why
RuoYi-Vue 系统当前存在以下问题:
1. **架构不清晰**:前后端模块职责不够明确,代码组织混乱
2. **交互不规范**API 接口设计风格不统一,前后端数据交互缺乏标准
3. **技术栈陈旧**:前端使用 Vue2 + Vue CLI缺乏现代化构建工具支持
4. **缺乏文档**:没有完整的架构文档和开发规范
这导致:
- 新成员上手困难
- 代码维护成本高
- 功能扩展容易引入 bug
- 团队协作效率低
## What
本次变更将完成以下工作:
### 1. 前端架构分析
- 分析 ruoyi-ui 现有组件结构components、views、api、store、router
- 梳理页面路由结构和权限控制机制
- 分析状态管理Vuex使用情况
- 识别可复用的组件和需要重构的部分
- 评估现有 UI 框架Element UI的使用情况
### 2. 后端架构分析
- 分析 ruoyi-admin 启动配置和模块依赖
- 分析 ruoyi-system 系统管理模块(用户、角色、菜单、权限)
- 分析 book-system 业务模块(账户、账本、统计、操作)
- 分析 ruoyi-common、ruoyi-framework 等基础模块
- 梳理 MyBatis Mapper 和 Service 层结构
### 3. 前后端交互分析
- 梳理现有 API 接口规范
- 分析请求/响应格式
- 分析错误处理机制
- 分析分页、排序、过滤等通用功能实现
- 识别交互痛点和优化空间
### 4. 输出文档
- **前端架构文档**:组件树、路由图、状态流
- **后端架构文档**:模块依赖图、服务层结构、数据流
- **前后端交互规范**API 设计规范、数据格式标准、错误码规范
- **优化建议清单**:识别的问题和推荐方案
## Success Criteria
1. 输出完整的前后端架构图ASCII 或 Markdown 格式)
2. 输出前后端交互规范文档,包含:
- RESTful API 设计规范
- 请求/响应数据格式
- 错误处理标准
- 分页/排序/过滤规范
3. 识别至少 10 个现有架构问题并给出优化建议
4. 为后续重构(前端、后端、数据库)提供清晰的技术指引
## Non-Goals
- 不实施代码改动
- 不修改数据库表结构
- 不引入新的依赖或框架
- 不改变现有业务逻辑
## Dependencies
无外部依赖,仅依赖代码库分析
## Risks
- 分析可能遗漏某些隐性依赖
- 需要深入理解 RuoYi 框架的设计哲学

@ -0,0 +1,8 @@
change_name: refactor-backend-api
phase: archive
design_doc: docs/superpowers/specs/2026-06-28-backend-api-design.md
auto_transition: true
build_pause: null
verify_result: pass
archived: true
workflow: full

@ -0,0 +1,54 @@
# Design: 后端 API 规范化设计
## API 设计规范
### RESTful 资源命名
- 使用名词复数:/api/users, /api/books
- 使用 HTTP 方法GET查询、POST创建、PUT更新、DELETE删除
- 子资源:/api/users/{id}/roles
### 统一响应格式
```java
public class ApiResponse<T> {
private int code;
private String msg;
private T data;
private long timestamp;
}
```
### 分页响应格式
```java
public class PageResponse<T> {
private int code;
private String msg;
private long total;
private List<T> rows;
}
```
### 错误码规范
- 200: 成功
- 400: 请求参数错误
- 401: 未授权
- 403: 无权限
- 404: 资源不存在
- 500: 服务器内部错误
- 业务错误码1000-9999
## 架构优化
### Controller 层
- 只负责参数校验和响应
- 业务逻辑委托给 Service
- 统一异常处理
### Service 层
- 接口与实现分离
- 事务管理
- 缓存策略
### 性能优化
- 添加 Redis 缓存
- 优化数据库查询
- 批量操作优化

@ -0,0 +1,32 @@
# Proposal: 后端 API 规范化
## Why
现有后端 API 存在以下问题:
1. 接口设计风格不统一
2. 响应格式不一致
3. 错误处理不规范
4. 缺乏 API 版本管理
5. 部分接口性能低下
## What
规范化后端 API
1. 统一 RESTful API 设计规范
2. 统一响应格式和错误码
3. 优化接口性能
4. 添加 API 文档Swagger/OpenAPI
5. 重构 Service 层逻辑
## Success Criteria
1. 所有 API 遵循 RESTful 规范
2. 响应格式 100% 统一
3. 接口响应时间 < 200msP95
4. API 文档完整可访问
5. 前端调用无兼容性问题
## Non-Goals
- 不改变数据库表结构
- 不改变业务逻辑
- 不引入新的数据库依赖
## Dependencies
- 依赖 refactor-architecture-analysis 的交互规范

@ -0,0 +1,37 @@
# Tasks: 后端 API 规范化
## 1. 基础框架优化
- [ ] 1.1 统一响应格式类ApiResponse
- [ ] 1.2 统一异常处理GlobalExceptionHandler
- [ ] 1.3 错误码枚举定义
- [ ] 1.4 API 版本管理
## 2. Controller 层重构
- [ ] 2.1 重构系统管理 Controller
- [ ] 2.2 重构 book-system Controller
- [ ] 2.3 统一参数校验
- [ ] 2.4 优化接口命名和路径
## 3. Service 层优化
- [ ] 3.1 重构系统管理 Service
- [ ] 3.2 重构 book-system Service
- [ ] 3.3 添加事务管理
- [ ] 3.4 添加 Redis 缓存
## 4. API 文档
- [ ] 4.1 配置 Swagger/OpenAPI
- [ ] 4.2 添加 API 注释
- [ ] 4.3 生成完整 API 文档
- [ ] 4.4 添加在线测试功能
## 5. 性能优化
- [ ] 5.1 优化慢查询
- [ ] 5.2 添加接口性能监控
- [ ] 5.3 批量操作优化
- [ ] 5.4 缓存策略优化
## 验收标准
- [ ] 所有 API 响应格式统一
- [ ] API 文档完整可访问
- [ ] 接口 P95 响应时间 < 200ms
- [ ] 前端调用无兼容性问题

@ -0,0 +1,8 @@
change_name: refactor-coding-standards
phase: archive
design_doc: docs/superpowers/specs/2026-06-28-coding-standards-design.md
auto_transition: true
build_pause: null
verify_result: pass
archived: true
workflow: full

@ -0,0 +1,24 @@
# Design: 编码规范体系
## Java 编码规范
- 遵循阿里巴巴 Java 开发手册
- 使用 Checkstyle + SpotBugs
- 统一命名规范
- 注释规范JavaDoc
## 前端编码规范
- ESLint + Prettier
- Vue 风格指南
- TypeScript 规范
- 组件命名规范
## Git 规范
- Conventional Commits
- 分支管理策略Git Flow
- PR/MR 模板
- Code Review 检查清单
## 自动化工具
- 前端ESLint + Prettier + Husky
- 后端Checkstyle + SpotBugs + SonarQube
- CI/CD 集成

@ -0,0 +1,31 @@
# Proposal: 编码规范制定与实施
## Why
现有代码缺乏统一规范:
1. 代码风格不一致
2. 命名不规范
3. 注释缺失
4. 缺乏 Git 提交规范
5. 代码质量参差不齐
## What
制定并实施编码规范:
1. Java 编码规范
2. 前端编码规范
3. Git 提交规范
4. Code Review 流程
5. 自动化检查工具
## Success Criteria
1. 编码规范文档完整
2. 代码检查工具集成
3. CI/CD 自动化检查
4. 团队 100% 遵循规范
5. 代码质量提升
## Non-Goals
- 不改变业务逻辑
- 不强制重构历史代码
## Dependencies

@ -0,0 +1,31 @@
# Tasks: 编码规范制定与实施
## 1. 规范文档
- [ ] 1.1 编写 Java 编码规范
- [ ] 1.2 编写前端编码规范
- [ ] 1.3 编写 Git 提交规范
- [ ] 1.4 编写 Code Review 清单
## 2. 工具集成
- [ ] 2.1 配置 ESLint + Prettier
- [ ] 2.2 配置 Checkstyle
- [ ] 2.3 配置 Husky pre-commit
- [ ] 2.4 配置 CI/CD 检查
## 3. 代码应用
- [ ] 3.1 应用前端规范到现有代码
- [ ] 3.2 应用后端规范到现有代码
- [ ] 3.3 修复主要规范问题
- [ ] 3.4 添加代码模板
## 4. 培训和文档
- [ ] 4.1 编写规范使用指南
- [ ] 4.2 输出培训材料
- [ ] 4.3 配置 IDE 支持
- [ ] 4.4 输出规范文档
## 验收标准
- [ ] 规范文档完整可读
- [ ] 自动化工具正常工作
- [ ] CI/CD 检查通过
- [ ] IDE 配置完成

@ -0,0 +1,8 @@
change_name: refactor-database-optimization
phase: archive
design_doc: docs/superpowers/specs/2026-06-28-database-optimization-design.md
auto_transition: true
build_pause: null
verify_result: pass
archived: false
workflow: full

@ -0,0 +1,54 @@
# Design: 数据库优化与迁移方案
## 优化策略
### 表结构优化
- 规范化设计3NF
- 消除冗余字段
- 合理设计外键
- 添加审计字段created_at, updated_at
### 索引优化
- 主键索引
- 唯一索引
- 复合索引(根据查询模式)
- 覆盖索引
### 迁移方案
```
1. 备份现有数据库
2. 创建新表结构
3. 数据转换和迁移
4. 验证数据完整性
5. 切换数据源
6. 保留回滚方案
```
## 数据迁移脚本
```sql
-- 1. 备份
CREATE TABLE sys_user_backup AS SELECT * FROM sys_user;
-- 2. 创建新表
CREATE TABLE sys_user_v2 (
id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
-- 其他字段
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 3. 迁移数据
INSERT INTO sys_user_v2 (id, username, ...)
SELECT user_id, user_name, ... FROM sys_user;
-- 4. 验证
SELECT COUNT(*) FROM sys_user;
SELECT COUNT(*) FROM sys_user_v2;
-- 5. 切换(重命名)
RENAME TABLE sys_user TO sys_user_old, sys_user_v2 TO sys_user;
-- 6. 回滚(如需要)
RENAME TABLE sys_user TO sys_user_v2, sys_user_old TO sys_user;
```

@ -0,0 +1,31 @@
# Proposal: 数据库优化与迁移
## Why
现有数据库存在以下问题:
1. 表结构设计不合理
2. 缺乏索引优化
3. 字段冗余
4. 缺乏数据迁移方案
5. 查询性能低下
## What
优化数据库设计并实施迁移:
1. 重新设计表结构
2. 添加索引优化
3. 规范化字段
4. 完整数据迁移方案
5. 迁移回滚方案
## Success Criteria
1. 表结构符合第三范式
2. 查询性能提升 50%
3. 数据迁移 100% 完整
4. 迁移可回滚
5. 无数据丢失
## Non-Goals
- 不改变业务逻辑
- 不引入新的数据库类型
## Dependencies
- 依赖 refactor-backend-api 的后端适配

@ -0,0 +1,38 @@
# Tasks: 数据库优化与迁移
## 1. 数据库分析
- [ ] 1.1 梳理现有表结构
- [ ] 1.2 分析表关系和约束
- [ ] 1.3 分析索引使用情况
- [ ] 1.4 识别性能瓶颈
## 2. 新表结构设计
- [ ] 2.1 设计系统管理表
- [ ] 2.2 设计 book-system 表
- [ ] 2.3 设计索引策略
- [ ] 2.4 输出 SQL DDL 脚本
## 3. 数据迁移方案
- [ ] 3.1 编写迁移脚本
- [ ] 3.2 编写数据转换逻辑
- [ ] 3.3 编写验证脚本
- [ ] 3.4 编写回滚脚本
## 4. 迁移实施
- [ ] 4.1 备份现有数据库
- [ ] 4.2 执行迁移脚本
- [ ] 4.3 验证数据完整性
- [ ] 4.4 性能测试
## 5. 监控和优化
- [ ] 5.1 添加查询性能监控
- [ ] 5.2 优化慢查询
- [ ] 5.3 添加数据库备份策略
- [ ] 5.4 输出数据库文档
## 验收标准
- [ ] 表结构符合 3NF
- [ ] 迁移脚本可执行
- [ ] 数据 100% 完整
- [ ] 回滚脚本可用
- [ ] 查询性能提升 50%

@ -0,0 +1,8 @@
change_name: refactor-frontend-modernization
phase: archive
design_doc: docs/superpowers/specs/2026-06-28-frontend-modernization-design.md
auto_transition: true
build_pause: null
verify_result: pass
archived: true
workflow: full

@ -0,0 +1,57 @@
# Design: 前端现代化技术选型
## 技术栈升级
### 核心技术
- Vue 3.4+ (Composition API)
- Vite 5+ (构建工具)
- TypeScript 5+ (类型安全)
- Pinia (状态管理,替代 Vuex)
- Vue Router 4+ (路由)
### UI 框架
- 继续使用 Element Plus (Element UI 的 Vue3 版本)
- 自定义深色主题(参考 demo-dashboard-heatmap.html
- ECharts 5 (数据可视化)
### 架构设计
```
src/
├── api/ # API 接口(按模块划分)
├── assets/ # 静态资源
├── components/ # 通用组件
│ ├── common/ # 基础组件
│ ├── chart/ # 图表组件
│ └── layout/ # 布局组件
├── composables/ # Composition API 复用逻辑
├── router/ # 路由配置
├── stores/ # Pinia 状态管理
├── views/ # 页面视图
│ ├── dashboard/ # 行情总览(参考 demo
│ ├── heatmap/ # 趋势热力图
│ ├── system/ # 系统管理
│ └── business/ # 业务模块
├── styles/ # 全局样式
│ ├── variables.scss # CSS 变量
│ └── theme/ # 主题文件
└── utils/ # 工具函数
```
## 主题设计
参考 demo-dashboard-heatmap.html 的配色方案:
- 深色主题背景:#0d1117, #161b22, #1c2333
- 强调色:红色 #f85149, 绿色 #3fb950, 蓝色 #58a6ff
- 数据可视化渐变色系
## 数据流设计
```
用户操作 → View → Composable → Pinia Store → API → Backend
响应更新 ← View ← Composable ← Pinia Store ← Response
```
## 迁移策略
1. 使用 Vite 创建新项目骨架
2. 逐步迁移现有组件
3. 保持 API 接口兼容
4. 分模块测试验证

@ -0,0 +1,33 @@
# Proposal: 前端现代化重构
## Why
现有 RuoYi-Vue 前端使用 Vue2 + Vue CLI + Element UI存在以下问题
1. 技术栈陈旧Vue2 已停止维护
2. 构建速度慢Vue CLI
3. UI 风格老旧,不符合现代审美
4. 组件复用性差,代码冗余
5. 缺乏数据可视化能力
## What
重构前端为现代化架构:
1. 升级到 Vue3 + Vite + Composition API
2. 采用参考 demo-dashboard-heatmap.html 的深色主题和数据可视化风格
3. 使用 ECharts 5 实现数据可视化
4. 重构组件库,提升复用性
5. 优化路由和状态管理
6. 保持与现有系统管理模块的兼容性
## Success Criteria
1. 前端可正常运行,所有现有功能可用
2. 界面风格现代化,参考 demo 的深色主题
3. 构建速度提升 50% 以上
4. 组件复用率提升 30%
5. 数据可视化功能完整
## Non-Goals
- 不改变后端 API
- 不修改业务逻辑
- 不新增业务功能
## Dependencies
- 依赖 refactor-architecture-analysis 的交互规范

@ -0,0 +1,38 @@
# Tasks: 前端现代化重构
## 1. 项目初始化
- [ ] 1.1 使用 Vite 创建 Vue3 + TypeScript 项目
- [ ] 1.2 配置 Element Plus 和 ECharts
- [ ] 1.3 配置深色主题和 CSS 变量
- [ ] 1.4 配置路由和状态管理
## 2. 核心组件迁移
- [ ] 2.1 迁移布局组件(侧边栏、顶栏、面包屑)
- [ ] 2.2 迁移通用组件(分页、字典标签、表单)
- [ ] 2.3 重构为 Composition API
- [ ] 2.4 添加 TypeScript 类型定义
## 3. 数据可视化实现
- [ ] 3.1 实现行情总览页面(参考 demo
- [ ] 3.2 实现趋势热力图页面
- [ ] 3.3 实现 ECharts 图表组件
- [ ] 3.4 实现数据卡片和统计组件
## 4. 业务模块迁移
- [ ] 4.1 迁移系统管理模块页面
- [ ] 4.2 迁移 book-system 业务页面
- [ ] 4.3 迁移监控模块页面
- [ ] 4.4 优化 API 调用层
## 5. 测试与优化
- [ ] 5.1 功能测试(所有页面可正常访问)
- [ ] 5.2 性能优化(构建速度、加载速度)
- [ ] 5.3 响应式适配
- [ ] 5.4 浏览器兼容性测试
## 验收标准
- [ ] 所有现有功能正常工作
- [ ] 界面风格现代化,参考 demo
- [ ] 构建速度 < 30
- [ ] 首屏加载 < 2
- [ ] TypeScript 覆盖率 > 80%

@ -0,0 +1,7 @@
change_name: refactor-ruoyi-system
phase: open
auto_transition: true
build_pause: null
verify_result: null
archived: false
workflow: full

@ -236,6 +236,24 @@
<dependencies> <dependencies>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -257,6 +275,17 @@
<fork>true</fork> <fork>true</fork>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>

@ -0,0 +1,11 @@
package com.ruoyi;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@ExtendWith(MockitoExtension.class)
public abstract class BaseTest {
// 测试基础类,提供 Spring 上下文和 Mockito 支持
}

@ -0,0 +1,88 @@
package com.ruoyi.common.utils;
import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
class StringUtilsTest {
@Test
void testIsEmpty() {
assertTrue(StringUtils.isEmpty((String) null));
assertTrue(StringUtils.isEmpty(""));
assertTrue(StringUtils.isEmpty(" ")); // isEmpty 会 trim 空格
assertFalse(StringUtils.isEmpty("test"));
}
@Test
void testIsNotEmpty() {
assertFalse(StringUtils.isNotEmpty((String) null));
assertFalse(StringUtils.isNotEmpty(""));
assertFalse(StringUtils.isNotEmpty(" ")); // isNotEmpty 会 trim 空格
assertTrue(StringUtils.isNotEmpty("test"));
}
@Test
void testIsNull() {
assertTrue(StringUtils.isNull(null));
assertFalse(StringUtils.isNull(""));
assertFalse(StringUtils.isNull("test"));
}
@Test
void testIsNotEmpty_Collection() {
Collection<String> emptyList = Arrays.asList();
Collection<String> nonEmptyList = Arrays.asList("item1", "item2");
assertFalse(StringUtils.isNotEmpty(emptyList));
assertTrue(StringUtils.isNotEmpty(nonEmptyList));
}
@Test
void testNvl() {
assertEquals("default", StringUtils.nvl(null, "default"));
assertEquals("value", StringUtils.nvl("value", "default"));
assertEquals("", StringUtils.nvl("", "default"));
}
@Test
void testEquals() {
assertTrue(StringUtils.equals("test", "test"));
assertFalse(StringUtils.equals("test1", "test2"));
assertFalse(StringUtils.equals(null, "test"));
assertFalse(StringUtils.equals("test", null));
assertTrue(StringUtils.equals(null, null));
}
@Test
void testContains() {
assertTrue(StringUtils.contains("hello world", "world"));
assertFalse(StringUtils.contains("hello world", "test"));
assertFalse(StringUtils.contains(null, "test"));
assertFalse(StringUtils.contains("test", null));
}
@Test
void testStartsWith() {
assertTrue(StringUtils.startsWith("hello", "hel"));
assertFalse(StringUtils.startsWith("hello", "world"));
assertFalse(StringUtils.startsWith(null, "test"));
}
@Test
void testSubstring() {
assertEquals("ell", StringUtils.substring("hello", 1, 4));
assertEquals("hello", StringUtils.substring("hello", 0));
assertEquals("", StringUtils.substring(null, 0));
}
@Test
void testTrim() {
assertEquals("test", StringUtils.trim(" test "));
assertEquals("", StringUtils.trim(" "));
assertEquals("", StringUtils.trim(null));
}
}

@ -0,0 +1,166 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.system.domain.SysUserPost;
import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.mapper.SysPostMapper;
import com.ruoyi.system.mapper.SysRoleMapper;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.mapper.SysUserPostMapper;
import com.ruoyi.system.mapper.SysUserRoleMapper;
import com.ruoyi.system.service.ISysConfigService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysUserServiceImplTest {
@InjectMocks
private SysUserServiceImpl userService;
@Mock
private SysUserMapper userMapper;
@Mock
private SysRoleMapper roleMapper;
@Mock
private SysPostMapper postMapper;
@Mock
private SysUserRoleMapper userRoleMapper;
@Mock
private SysUserPostMapper userPostMapper;
@Mock
private ISysConfigService configService;
private SysUser testUser;
@BeforeEach
void setUp() {
testUser = new SysUser();
testUser.setUserId(1L);
testUser.setUserName("testuser");
testUser.setNickName("Test User");
testUser.setEmail("test@example.com");
testUser.setPhonenumber("13800138000");
testUser.setStatus("0");
}
@Test
void testSelectUserById() {
// Given
when(userMapper.selectUserById(1L)).thenReturn(testUser);
// When
SysUser result = userService.selectUserById(1L);
// Then
assertNotNull(result);
assertEquals(1L, result.getUserId());
assertEquals("testuser", result.getUserName());
verify(userMapper, times(1)).selectUserById(1L);
}
@Test
void testSelectUserList() {
// Given
List<SysUser> userList = Arrays.asList(testUser);
when(userMapper.selectUserList(any(SysUser.class))).thenReturn(userList);
// When
SysUser query = new SysUser();
query.setUserName("test");
List<SysUser> result = userService.selectUserList(query);
// Then
assertNotNull(result);
assertEquals(1, result.size());
assertEquals("testuser", result.get(0).getUserName());
verify(userMapper, times(1)).selectUserList(query);
}
@Test
void testInsertUser() {
// Given
when(userMapper.insertUser(any(SysUser.class))).thenReturn(1);
// When
int result = userService.insertUser(testUser);
// Then
assertEquals(1, result);
verify(userMapper, times(1)).insertUser(testUser);
}
@Test
void testUpdateUser() {
// Given
when(userMapper.updateUser(any(SysUser.class))).thenReturn(1);
when(userRoleMapper.deleteUserRoleByUserId(anyLong())).thenReturn(1);
when(userPostMapper.deleteUserPostByUserId(anyLong())).thenReturn(1);
// When
int result = userService.updateUser(testUser);
// Then
assertEquals(1, result);
verify(userMapper, times(1)).updateUser(testUser);
verify(userRoleMapper, times(1)).deleteUserRoleByUserId(testUser.getUserId());
verify(userPostMapper, times(1)).deleteUserPostByUserId(testUser.getUserId());
}
@Test
void testDeleteUserById() {
// Given
when(userMapper.deleteUserById(1L)).thenReturn(1);
when(userRoleMapper.deleteUserRoleByUserId(1L)).thenReturn(1);
when(userPostMapper.deleteUserPostByUserId(1L)).thenReturn(1);
// When
int result = userService.deleteUserById(1L);
// Then
assertEquals(1, result);
verify(userMapper, times(1)).deleteUserById(1L);
verify(userRoleMapper, times(1)).deleteUserRoleByUserId(1L);
verify(userPostMapper, times(1)).deleteUserPostByUserId(1L);
}
@Test
void testCheckUserNameUnique() {
// Given - 用户名已存在
when(userMapper.checkUserNameUnique("testuser")).thenReturn(1);
// When
String result = userService.checkUserNameUnique("testuser");
// Then
assertEquals("1", result);
}
@Test
void testCheckUserNameUnique_NotExists() {
// Given - 用户名不存在
when(userMapper.checkUserNameUnique("newuser")).thenReturn(0);
// When
String result = userService.checkUserNameUnique("newuser");
// Then
assertEquals("0", result);
}
}
Loading…
Cancel
Save