فهرست منبع

后台股票管理

Zhangbw 3 ماه پیش
والد
کامیت
0ab1bff330

+ 2 - 2
.env.development

@@ -1,6 +1,6 @@
 # 页面标题
-VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
-VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
+VITE_APP_TITLE = 量化交易大师后台管理系统
+VITE_APP_LOGO_TITLE = 量化交易大师后台管理系统
 
 # 开发环境配置
 VITE_APP_ENV = 'development'

+ 2 - 2
.env.production

@@ -1,6 +1,6 @@
 # 页面标题
-VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
-VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
+VITE_APP_TITLE = 量化交易大师后台管理系统
+VITE_APP_LOGO_TITLE = 量化交易大师后台管理系统
 
 # 生产环境配置
 VITE_APP_ENV = 'production'

+ 33 - 0
src/api/miniapp/user/index.ts

@@ -0,0 +1,33 @@
+import request from '@/utils/request';
+
+/**
+ * 查询小程序用户列表
+ */
+export function listMiniappUser(query: any) {
+  return request({
+    url: '/miniapp/user/list',
+    method: 'get',
+    params: query
+  });
+}
+
+/**
+ * 查询小程序用户详情
+ */
+export function getMiniappUser(id: number) {
+  return request({
+    url: `/miniapp/user/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 修改用户状态
+ */
+export function changeUserStatus(id: number, status: number) {
+  return request({
+    url: '/miniapp/user/changeStatus',
+    method: 'put',
+    params: { id, status }
+  });
+}

+ 76 - 0
src/api/stock/info/index.ts

@@ -0,0 +1,76 @@
+import request from '@/utils/request';
+
+/**
+ * 查询股票信息列表
+ */
+export function listStockInfo(query: any) {
+  return request({
+    url: '/stock/info/list',
+    method: 'get',
+    params: query
+  });
+}
+
+/**
+ * 查询股票信息详情
+ */
+export function getStockInfo(id: number) {
+  return request({
+    url: `/stock/info/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 新增股票信息
+ */
+export function addStockInfo(data: any) {
+  return request({
+    url: '/stock/info',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 修改股票信息
+ */
+export function updateStockInfo(data: any) {
+  return request({
+    url: '/stock/info',
+    method: 'put',
+    data: data
+  });
+}
+
+/**
+ * 删除股票信息
+ */
+export function delStockInfo(ids: number | number[]) {
+  return request({
+    url: `/stock/info/${ids}`,
+    method: 'delete'
+  });
+}
+
+/**
+ * 添加股票到池
+ */
+export function addToPool(data: { stockCode: string; stockName: string; poolType: number; reason?: string }) {
+  return request({
+    url: '/stock/pool/add',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 从池中移除股票
+ */
+export function removeFromPool(stockCode: string, poolType: number) {
+  return request({
+    url: '/stock/pool/removeByCode',
+    method: 'delete',
+    params: { stockCode, poolType }
+  });
+}

+ 54 - 0
src/api/stock/pool/index.ts

@@ -0,0 +1,54 @@
+import request from '@/utils/request';
+
+/**
+ * 查询股票池列表(带实时数据)
+ */
+export function listStockPool(query: any) {
+  return request({
+    url: '/stock/pool/list',
+    method: 'get',
+    params: query
+  });
+}
+
+/**
+ * 查询股票池详情
+ */
+export function getStockPool(id: number) {
+  return request({
+    url: `/stock/pool/${id}`,
+    method: 'get'
+  });
+}
+
+/**
+ * 添加股票到指定池
+ */
+export function addToPool(data: any) {
+  return request({
+    url: '/stock/pool/add',
+    method: 'post',
+    data: data
+  });
+}
+
+/**
+ * 从股票池移除
+ */
+export function removeFromPool(ids: number | number[]) {
+  return request({
+    url: `/stock/pool/${ids}`,
+    method: 'delete'
+  });
+}
+
+/**
+ * 搜索股票(用于添加到池)
+ */
+export function searchStock(keyword: string) {
+  return request({
+    url: '/stock/info/search',
+    method: 'get',
+    params: { keyword }
+  });
+}

+ 27 - 41
src/layout/components/Sidebar/Logo.vue

@@ -4,20 +4,10 @@
     :class="{ collapse: collapse }"
     :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
   >
-    <transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
-      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo" />
-        <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
-          {{ title }}
-        </h1>
-      </router-link>
-      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo" />
-        <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
-          {{ title }}
-        </h1>
-      </router-link>
-    </transition>
+    <router-link class="sidebar-logo-link" to="/">
+      <img v-if="logo" :src="logo" class="sidebar-logo" />
+      <span v-if="!collapse" class="sidebar-title">{{ title }}</span>
+    </router-link>
   </div>
 </template>
 
@@ -25,7 +15,6 @@
 import variables from '@/assets/styles/variables.module.scss';
 import logo from '@/assets/logo/logo.png';
 import { useSettingsStore } from '@/store/modules/settings';
-const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 defineProps({
   collapse: {
@@ -34,21 +23,12 @@ defineProps({
   }
 });
 
-const title = import.meta.env.VITE_APP_LOGO_TITLE;
+const title = import.meta.env.VITE_APP_LOGO_TITLE || '量化交易大师后台管理系统';
 const settingsStore = useSettingsStore();
 const sideTheme = computed(() => settingsStore.sideTheme);
 </script>
 
 <style lang="scss" scoped>
-.sidebarLogoFade-enter-active {
-  transition: opacity 1.5s;
-}
-
-.sidebarLogoFade-enter,
-.sidebarLogoFade-leave-to {
-  opacity: 0;
-}
-
 .sidebar-logo-container {
   position: relative;
   width: 100%;
@@ -58,37 +38,43 @@ const sideTheme = computed(() => settingsStore.sideTheme);
   text-align: center;
   overflow: hidden;
 
-  & .sidebar-logo-link {
+  .sidebar-logo-link {
     height: 100%;
     width: 100%;
+    display: flex;
+    align-items: center;
+    padding: 0 15px;
+    text-decoration: none;
 
-    & .sidebar-logo {
+    .sidebar-logo {
       width: 32px;
       height: 32px;
-      vertical-align: middle;
       margin-right: 12px;
+      flex-shrink: 0;
     }
 
-    & .sidebar-title {
-      display: inline-block;
-      margin: 0;
+    .sidebar-title {
+      display: block;
       color: #fff;
       font-weight: 600;
-      line-height: 50px;
-      font-size: 14px;
-      font-family:
-        Avenir,
-        Helvetica Neue,
-        Arial,
-        Helvetica,
-        sans-serif;
-      vertical-align: middle;
+      font-size: 16px;
+      font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      flex: 1;
+      line-height: 1.5;
     }
   }
 
   &.collapse {
+    .sidebar-logo-link {
+      justify-content: center;
+      padding: 0;
+    }
+    
     .sidebar-logo {
-      margin-right: 0px;
+      margin-right: 0;
     }
   }
 }

+ 1 - 1
src/store/modules/app.ts

@@ -7,7 +7,7 @@ import { ref, reactive, computed } from 'vue';
 export const useAppStore = defineStore('app', () => {
   const sidebarStatus = useStorage('sidebarStatus', '1');
   const sidebar = reactive({
-    opened: sidebarStatus.value ? !!+sidebarStatus.value : true,
+    opened: sidebarStatus.value !== '0', // 只有明确设置为 '0' 才收起,否则默认展开
     withoutAnimation: false,
     hide: false
   });

+ 51 - 146
src/views/index.vue

@@ -1,165 +1,70 @@
 <template>
-  <div class="app-container home">
-    <el-row :gutter="20">
-      <el-col :sm="24" :lg="12" style="padding-left: 20px">
-        <h2>RuoYi-Vue-Plus多租户管理系统</h2>
-        <p>
-          RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
-          <br />
-          * 前端开发框架 Vue3、TS、Element Plus<br />
-          * 后端开发框架 Spring Boot<br />
-          * 容器框架 Undertow 基于 Netty 的高性能容器<br />
-          * 权限认证框架 Sa-Token 支持多终端认证系统<br />
-          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
-          * 缓存数据库 Redis 适配 6.X 最低 4.X<br />
-          * 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
-          * 数据库框架 p6spy 更强劲的 SQL 分析<br />
-          * 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
-          * 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
-          * Redis客户端 Redisson 性能强劲、API丰富<br />
-          * 分布式限流 Redisson 全局、请求IP、集群ID 多种限流<br />
-          * 分布式锁 Lock4j 注解锁、工具锁 多种多样<br />
-          * 分布式幂等 Lock4j 基于分布式锁实现<br />
-          * 分布式链路追踪 SkyWalking 支持链路追踪、网格分析、度量聚合、可视化<br />
-          * 分布式任务调度 SnailJob 高性能 高可靠 易扩展<br />
-          * 文件存储 Minio 本地存储<br />
-          * 文件存储 七牛、阿里、腾讯 云存储<br />
-          * 监控框架 SpringBoot-Admin 全方位服务监控<br />
-          * 校验框架 Validation 增强接口安全性 严谨性<br />
-          * Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
-          * 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释<br />
-          * 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性<br />
-          * 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码<br />
-          * 部署方式 Docker 容器编排 一键部署业务集群<br />
-          * 国际化 SpringMessage Spring标准国际化方案<br />
-        </p>
-        <p><b>当前版本:</b> <span>v5.5.1</span></p>
-        <p>
-          <el-tag type="danger">&yen;免费开源</el-tag>
-        </p>
-        <p>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
-            >更新日志</el-button
-          >
-        </p>
-      </el-col>
-
-      <el-col :sm="24" :lg="12" style="padding-left: 20px">
-        <h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
-        <p>
-          RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
-          <br />
-          * 前端开发框架 Vue3、TS、Element UI<br />
-          * 后端开发框架 Spring Boot<br />
-          * 微服务开发框架 Spring Cloud、Spring Cloud Alibaba<br />
-          * 容器框架 Undertow 基于 XNIO 的高性能容器<br />
-          * 权限认证框架 Sa-Token、Jwt 支持多终端认证系统<br />
-          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
-          * 关系数据库 Oracle 适配 11g 12c<br />
-          * 关系数据库 PostgreSQL 适配 13 14<br />
-          * 关系数据库 SQLServer 适配 2017 2019<br />
-          * 缓存数据库 Redis 适配 6.X 最低 5.X<br />
-          * 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
-          * 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
-          * 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
-          * 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
-          * RPC远程调用 Apache Dubbo 原生态使用体验、高性能<br />
-          * 分布式限流熔断 Alibaba Sentinel 无侵入、高扩展<br />
-          * 分布式事务 Alibaba Seata 无侵入、高扩展 支持 四种模式<br />
-          * 分布式消息队列 Apache Kafka 高性能高速度<br />
-          * 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
-          * 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
-          * 分布式搜索引擎 ElasticSearch 业界知名<br />
-          * 分布式链路追踪 Apache SkyWalking 链路追踪、网格分析、度量聚合、可视化<br />
-          * 分布式日志中心 ELK 业界成熟解决方案<br />
-          * 分布式监控 Prometheus、Grafana 全方位性能监控<br />
-          * 其余与 Vue 版本一致<br />
-        </p>
-        <p><b>当前版本:</b> <span>v2.5.1</span></p>
-        <p>
-          <el-tag type="danger">&yen;免费开源</el-tag>
-        </p>
-        <p>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
-          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
-            >更新日志</el-button
-          >
-        </p>
-      </el-col>
-    </el-row>
-    <el-divider />
+  <div class="welcome-container">
+    <div class="welcome-text">
+      <span v-for="(letter, index) in letters" :key="index" :style="getLetterStyle(index)" class="letter">
+        {{ letter }}
+      </span>
+    </div>
   </div>
 </template>
 
-<script setup name="Index" lang="ts">
-const goTarget = (url: string) => {
-  window.open(url, '__blank');
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+
+const letters = ref(['W', 'e', 'l', 'c', 'o', 'm', 'e']);
+
+const getLetterStyle = (index: number) => {
+  return {
+    animationDelay: `${index * 0.1}s`
+  };
 };
 </script>
 
 <style lang="scss" scoped>
-.home {
-  blockquote {
-    padding: 10px 20px;
-    margin: 0 0 20px;
-    font-size: 17.5px;
-    border-left: 5px solid #eee;
-  }
-  hr {
-    margin-top: 20px;
-    margin-bottom: 20px;
-    border: 0;
-    border-top: 1px solid #eee;
-  }
-  .col-item {
-    margin-bottom: 20px;
-  }
+.welcome-container {
+  width: 100%;
+  height: 100vh;
+  background: #ffffff;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
 
-  ul {
-    padding: 0;
-    margin: 0;
-  }
+.welcome-text {
+  display: flex;
+  gap: 10px;
+}
 
-  font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
-  font-size: 13px;
-  color: #676a6c;
-  overflow-x: hidden;
+.letter {
+  font-size: 80px;
+  font-weight: bold;
+  background: linear-gradient(45deg, #667eea, #764ba2, #f093fb, #4facfe);
+  background-size: 300% 300%;
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+  animation: wave 2s ease-in-out infinite, gradient 3s ease infinite;
+  display: inline-block;
+}
 
-  ul {
-    list-style-type: none;
+@keyframes wave {
+  0%, 100% {
+    transform: translateY(0) scale(1);
   }
-
-  h4 {
-    margin-top: 0px;
+  50% {
+    transform: translateY(-20px) scale(1.1);
   }
+}
 
-  h2 {
-    margin-top: 10px;
-    font-size: 26px;
-    font-weight: 100;
+@keyframes gradient {
+  0% {
+    background-position: 0% 50%;
   }
-
-  p {
-    margin-top: 10px;
-
-    b {
-      font-weight: 700;
-    }
+  50% {
+    background-position: 100% 50%;
   }
-
-  .update-log {
-    ol {
-      display: block;
-      list-style-type: decimal;
-      margin-block-start: 1em;
-      margin-block-end: 1em;
-      margin-inline-start: 0;
-      margin-inline-end: 0;
-      padding-inline-start: 40px;
-    }
+  100% {
+    background-position: 0% 50%;
   }
 }
 </style>

+ 177 - 0
src/views/miniapp/user/index.vue

@@ -0,0 +1,177 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="手机号" prop="phone">
+              <el-input v-model="queryParams.phone" placeholder="请输入手机号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="昵称" prop="nickname">
+              <el-input v-model="queryParams.nickname" placeholder="请输入昵称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="用户状态" clearable>
+                <el-option label="正常" :value="0" />
+                <el-option label="禁用" :value="1" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10">
+          <el-col :span="1.5">
+            <el-button v-has-permi="['miniapp:user:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="userList">
+        <el-table-column label="用户ID" align="center" prop="id" width="80" />
+        <el-table-column label="头像" align="center" width="80">
+          <template #default="scope">
+            <el-avatar :size="40" :src="scope.row.avatar || defaultAvatar" />
+          </template>
+        </el-table-column>
+        <el-table-column label="昵称" align="center" prop="nickname" :show-overflow-tooltip="true" />
+        <el-table-column label="手机号" align="center" prop="phone" width="120" />
+        <el-table-column label="状态" align="center" width="100">
+          <template #default="scope">
+            <el-switch
+              v-model="scope.row.status"
+              :active-value="0"
+              :inactive-value="1"
+              @change="handleStatusChange(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="注册时间" align="center" prop="createTime" width="180" />
+        <el-table-column label="操作" align="center" width="150">
+          <template #default="scope">
+            <el-button v-has-permi="['miniapp:user:query']" link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="total > 0"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        :total="total"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 用户详情对话框 -->
+    <el-dialog v-model="detailDialog.visible" title="用户详情" width="500px">
+      <el-descriptions :column="1" border>
+        <el-descriptions-item label="用户ID">{{ currentUser.id }}</el-descriptions-item>
+        <el-descriptions-item label="昵称">{{ currentUser.nickname }}</el-descriptions-item>
+        <el-descriptions-item label="手机号">{{ currentUser.phone }}</el-descriptions-item>
+        <el-descriptions-item label="OpenID">{{ currentUser.openid }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="currentUser.status === 0 ? 'success' : 'danger'">
+            {{ currentUser.status === 0 ? '正常' : '禁用' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="注册时间">{{ currentUser.createTime }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="MiniappUser" lang="ts">
+import { ref, onMounted, getCurrentInstance } from 'vue';
+import { listMiniappUser, getMiniappUser, changeUserStatus } from '@/api/miniapp/user';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const defaultAvatar = 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png';
+const userList = ref<any[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  phone: '',
+  nickname: '',
+  status: undefined as number | undefined
+});
+
+const detailDialog = ref({
+  visible: false
+});
+
+const currentUser = ref<any>({});
+
+/** 查询用户列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listMiniappUser(queryParams.value);
+    console.log('API响应:', res);
+    userList.value = res.rows || [];
+    total.value = res.total || 0;
+  } catch (error) {
+    console.error('获取用户列表失败:', error);
+    userList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.status = undefined;
+  handleQuery();
+};
+
+/** 用户状态修改 */
+const handleStatusChange = async (row: any) => {
+  const text = row.status === 0 ? '启用' : '禁用';
+  try {
+    await proxy?.$modal.confirm(`确认要${text}用户"${row.nickname}"吗?`);
+    await changeUserStatus(row.id, row.status);
+    proxy?.$modal.msgSuccess(`${text}成功`);
+  } catch {
+    row.status = row.status === 0 ? 1 : 0;
+  }
+};
+
+/** 查看详情 */
+const handleView = async (row: any) => {
+  const res = await getMiniappUser(row.id);
+  currentUser.value = res.data;
+  detailDialog.value.visible = true;
+};
+
+/** 导出 */
+const handleExport = () => {
+  proxy?.download('miniapp/user/export', { ...queryParams.value }, `miniapp_user_${new Date().getTime()}.xlsx`);
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 408 - 0
src/views/stock/info/index.vue

@@ -0,0 +1,408 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="股票代码" prop="stockCode">
+              <el-input v-model="queryParams.stockCode" placeholder="请输入股票代码" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="股票名称" prop="stockName">
+              <el-input v-model="queryParams.stockName" placeholder="请输入股票名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="市场" prop="market">
+              <el-select v-model="queryParams.market" placeholder="请选择市场" clearable>
+                <el-option label="上海" value="SH" />
+                <el-option label="深圳" value="SZ" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10">
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:info:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:info:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:info:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Refresh" @click="refreshQuotes" :loading="refreshing">刷新行情</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:info:export']" type="warning" plain icon="Download" @click="handleExport" :loading="exporting">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="stockList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="股票代码" align="center" prop="stockCode" />
+        <el-table-column label="股票名称" align="center" prop="stockName" />
+        <el-table-column label="市场" align="center" prop="market">
+          <template #default="scope">
+            <el-tag :type="scope.row.market === 'SH' ? 'danger' : 'success'" size="small">
+              {{ scope.row.market === 'SH' ? '沪' : '深' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="当前价" align="center" prop="currentPrice">
+          <template #default="scope">
+            <span :class="getPriceClass(scope.row)">{{ formatPrice(scope.row.currentPrice) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="涨跌幅" align="center" prop="changePercent">
+          <template #default="scope">
+            <span :class="getChangeClass(scope.row.changePercent)">{{ formatPercent(scope.row.changePercent) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="换手率" align="center" prop="turnoverRate">
+          <template #default="scope">{{ formatPercent(scope.row.turnoverRate) }}</template>
+        </el-table-column>
+        <el-table-column label="成交额(亿)" align="center" prop="tradeAmount">
+          <template #default="scope">{{ scope.row.tradeAmount || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="池状态" align="center">
+          <template #default="scope">
+            <el-tag v-if="scope.row.inShortPool" type="danger" size="small" class="mr-1">超短池</el-tag>
+            <el-tag v-if="scope.row.inStrongPool" type="warning" size="small">强势池</el-tag>
+            <span v-if="!scope.row.inShortPool && !scope.row.inStrongPool" class="text-gray-400">未入池</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="加入股票池" align="center" width="220">
+          <template #default="scope">
+            <el-button v-if="!scope.row.inShortPool" v-has-permi="['stock:pool:add']" type="danger" size="small" @click="handleAddToPool(scope.row, 1)">
+              加入超短池
+            </el-button>
+            <el-button v-else v-has-permi="['stock:pool:remove']" type="info" size="small" plain @click="handleRemoveFromPool(scope.row, 1)">
+              移出超短池
+            </el-button>
+            <el-button v-if="!scope.row.inStrongPool" v-has-permi="['stock:pool:add']" type="warning" size="small" @click="handleAddToPool(scope.row, 2)">
+              加入强势池
+            </el-button>
+            <el-button v-else v-has-permi="['stock:pool:remove']" type="info" size="small" plain @click="handleRemoveFromPool(scope.row, 2)">
+              移出强势池
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button v-has-permi="['stock:info:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button v-has-permi="['stock:info:remove']" link type="danger" icon="Delete" @click="handleDelete(scope.row)"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+    </el-card>
+
+    <!-- 添加或修改股票信息对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+      <el-form ref="stockFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="股票代码" prop="stockCode">
+          <el-input v-model="form.stockCode" placeholder="请输入股票代码" maxlength="10" />
+        </el-form-item>
+        <el-form-item label="股票名称" prop="stockName">
+          <el-input v-model="form.stockName" placeholder="请输入股票名称" maxlength="64" />
+        </el-form-item>
+        <el-form-item label="市场" prop="market">
+          <el-select v-model="form.market" placeholder="请选择市场">
+            <el-option label="上海" value="SH" />
+            <el-option label="深圳" value="SZ" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+
+<script setup name="StockInfo" lang="ts">
+import { ref, reactive, onMounted, onUnmounted, onActivated, getCurrentInstance } from 'vue';
+import { listStockInfo, getStockInfo, addStockInfo, updateStockInfo, delStockInfo, addToPool, removeFromPool } from '@/api/stock/info';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const stockList = ref<any[]>([]);
+const loading = ref(true);
+const refreshing = ref(false);
+const exporting = ref(false);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+let refreshTimer: ReturnType<typeof setTimeout> | null = null;
+
+const queryFormRef = ref<ElFormInstance>();
+const stockFormRef = ref<ElFormInstance>();
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  stockCode: '',
+  stockName: '',
+  market: undefined as string | undefined
+});
+
+const dialog = reactive({ visible: false, title: '' });
+const form = ref<any>({});
+
+const rules = reactive({
+  stockCode: [
+    { required: true, message: '股票代码不能为空', trigger: 'blur' },
+    { pattern: /^\d{6}$/, message: '股票代码必须是6位数字', trigger: 'blur' }
+  ],
+  stockName: [{ required: true, message: '股票名称不能为空', trigger: 'blur' }],
+  market: [{ required: true, message: '市场不能为空', trigger: 'change' }]
+});
+
+/** 获取随机刷新间隔 (2000-3000ms) */
+const getRandomInterval = () => 2000 + Math.random() * 1000;
+
+/** 启动自动刷新 */
+const startAutoRefresh = () => {
+  stopAutoRefresh();
+  refreshTimer = setTimeout(async () => {
+    await refreshQuotesOnly();
+    startAutoRefresh();
+  }, getRandomInterval());
+};
+
+/** 停止自动刷新 */
+const stopAutoRefresh = () => {
+  if (refreshTimer) {
+    clearTimeout(refreshTimer);
+    refreshTimer = null;
+  }
+};
+
+/** 查询股票信息列表(首次加载或翻页) */
+const getList = async () => {
+  loading.value = true;
+  stopAutoRefresh();
+  try {
+    const res = await listStockInfo(queryParams.value);
+    stockList.value = res.rows || [];
+    total.value = res.total || 0;
+    startAutoRefresh();
+  } catch (error) {
+    console.error('获取股票列表失败:', error);
+    stockList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 局部刷新行情数据(不显示loading) */
+const refreshQuotesOnly = async () => {
+  if (stockList.value.length === 0) return;
+  try {
+    const res = await listStockInfo(queryParams.value);
+    const newData = res.rows || [];
+    // 局部更新行情字段
+    stockList.value.forEach((item, index) => {
+      const newItem = newData.find((n: any) => n.id === item.id);
+      if (newItem) {
+        item.currentPrice = newItem.currentPrice;
+        item.changePercent = newItem.changePercent;
+        item.turnoverRate = newItem.turnoverRate;
+        item.tradeAmount = newItem.tradeAmount;
+        item.yesterdayClose = newItem.yesterdayClose;
+        item.inShortPool = newItem.inShortPool;
+        item.inStrongPool = newItem.inStrongPool;
+        item.poolStatus = newItem.poolStatus;
+      }
+    });
+  } catch (error) {
+    console.error('刷新行情失败:', error);
+  }
+};
+
+/** 手动刷新行情 */
+const refreshQuotes = async () => {
+  refreshing.value = true;
+  stopAutoRefresh();
+  try {
+    await refreshQuotesOnly();
+    startAutoRefresh();
+  } finally {
+    refreshing.value = false;
+  }
+};
+
+/** 导出(获取全表数据) */
+const handleExport = async () => {
+  exporting.value = true;
+  try {
+    proxy?.download('stock/info/export', {
+      stockCode: queryParams.value.stockCode,
+      stockName: queryParams.value.stockName,
+      market: queryParams.value.market
+    }, `stock_info_${new Date().getTime()}.xlsx`);
+  } finally {
+    setTimeout(() => { exporting.value = false; }, 1000);
+  }
+};
+
+/** 格式化价格 */
+const formatPrice = (val: any) => (val != null ? Number(val).toFixed(2) : '-');
+
+/** 格式化百分比 */
+const formatPercent = (val: any) => (val != null ? `${Number(val) >= 0 ? '+' : ''}${Number(val).toFixed(2)}%` : '-');
+
+/** 获取价格样式 */
+const getPriceClass = (row: any) => {
+  if (row.changePercent == null) return '';
+  return row.changePercent > 0 ? 'text-red-500' : row.changePercent < 0 ? 'text-green-500' : '';
+};
+
+/** 获取涨跌样式 */
+const getChangeClass = (val: any) => {
+  if (val == null) return '';
+  return val > 0 ? 'text-red-500 font-bold' : val < 0 ? 'text-green-500 font-bold' : '';
+};
+
+/** 加入股票池 */
+const handleAddToPool = async (row: any, poolType: number) => {
+  const poolName = poolType === 1 ? '超短池' : '强势池';
+  try {
+    await addToPool({ stockCode: row.stockCode, stockName: row.stockName, poolType: poolType });
+    proxy?.$modal.msgSuccess(`已加入${poolName}`);
+    await refreshQuotesOnly();
+  } catch (error) {
+    console.error('加入股票池失败:', error);
+  }
+};
+
+/** 从股票池移除 */
+const handleRemoveFromPool = async (row: any, poolType: number) => {
+  const poolName = poolType === 1 ? '超短池' : '强势池';
+  await proxy?.$modal.confirm(`确认将 ${row.stockName} 从${poolName}移除?`);
+  try {
+    await removeFromPool(row.stockCode, poolType);
+    proxy?.$modal.msgSuccess(`已从${poolName}移除`);
+    await refreshQuotesOnly();
+  } catch (error) {
+    console.error('移除失败:', error);
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  queryParams.value.market = undefined;
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: any[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length !== 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加股票信息';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: any) => {
+  reset();
+  const id = row?.id || ids.value[0];
+  const res = await getStockInfo(id);
+  form.value = res.data;
+  dialog.visible = true;
+  dialog.title = '修改股票信息';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  stockFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      if (form.value.id) {
+        await updateStockInfo(form.value);
+        proxy?.$modal.msgSuccess('修改成功');
+      } else {
+        await addStockInfo(form.value);
+        proxy?.$modal.msgSuccess('新增成功');
+      }
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: any) => {
+  const stockIds = row?.id ? [row.id] : ids.value;
+  await proxy?.$modal.confirm(`是否确认删除股票编号为"${stockIds}"的数据项?`);
+  await delStockInfo(stockIds);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  dialog.visible = false;
+  reset();
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { id: undefined, stockCode: '', stockName: '', market: undefined };
+  stockFormRef.value?.resetFields();
+};
+
+onMounted(() => {
+  getList();
+});
+
+onActivated(() => {
+  // 页面被激活时刷新数据
+  getList();
+});
+
+onUnmounted(() => {
+  stopAutoRefresh();
+});
+</script>
+
+<style scoped>
+.mr-1 { margin-right: 4px; }
+.text-gray-400 { color: #9ca3af; }
+.text-red-500 { color: #ef4444; }
+.text-green-500 { color: #22c55e; }
+.font-bold { font-weight: bold; }
+</style>

+ 380 - 0
src/views/stock/pool/index.vue

@@ -0,0 +1,380 @@
+<template>
+  <div class="p-2">
+    <!-- 搜索区域 -->
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="股票代码" prop="stockCode">
+              <el-input v-model="queryParams.stockCode" placeholder="请输入股票代码" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="股票名称" prop="stockName">
+              <el-input v-model="queryParams.stockName" placeholder="请输入股票名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="池类型" prop="poolType">
+              <el-select v-model="queryParams.poolType" placeholder="请选择池类型" clearable>
+                <el-option label="超短池" :value="1" />
+                <el-option label="强势池" :value="2" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="状态" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+                <el-option label="有效" :value="1" />
+                <el-option label="已移除" :value="0" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="入池时间" prop="dateRange">
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="YYYY-MM-DD"
+                style="width: 240px"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb-2">
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:info:add']" type="primary" plain icon="Plus" @click="handleAddStock">添加股票</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Refresh" @click="refreshQuotes" :loading="refreshing">刷新行情</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:pool:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">移除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button v-has-permi="['stock:pool:export']" type="warning" plain icon="Download" @click="handleExport" :loading="exporting">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="poolList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="股票代码" align="center" prop="stockCode" />
+        <el-table-column label="股票名称" align="center" prop="stockName" />
+        <el-table-column label="池类型" align="center" prop="poolTypeName">
+          <template #default="scope">
+            <el-tag :type="scope.row.poolType === 1 ? 'danger' : 'warning'">{{ scope.row.poolTypeName }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="当前价" align="center" prop="currentPrice">
+          <template #default="scope">
+            <span :class="getPriceClass(scope.row)">{{ formatPrice(scope.row.currentPrice) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="涨跌幅" align="center" prop="changePercent">
+          <template #default="scope">
+            <span :class="getChangeClass(scope.row.changePercent)">{{ formatPercent(scope.row.changePercent) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="入池价" align="center" prop="addPrice">
+          <template #default="scope">{{ formatPrice(scope.row.addPrice) }}</template>
+        </el-table-column>
+        <el-table-column label="入池收益" align="center" prop="profitPercent">
+          <template #default="scope">
+            <span :class="getChangeClass(scope.row.profitPercent)">{{ formatPercent(scope.row.profitPercent) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="换手率" align="center" prop="turnoverRate">
+          <template #default="scope">{{ formatPercent(scope.row.turnoverRate) }}</template>
+        </el-table-column>
+        <el-table-column label="成交额(亿)" align="center" prop="tradeAmount">
+          <template #default="scope">{{ scope.row.tradeAmount || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="入池日期" align="center" prop="addDate" />
+        <el-table-column label="状态" align="center" prop="status">
+          <template #default="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '有效' : '已移除' }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100">
+          <template #default="scope">
+            <el-tooltip content="移除" placement="top">
+              <el-button v-has-permi="['stock:pool:remove']" link type="danger" icon="Delete" @click="handleDelete(scope.row)"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+    </el-card>
+
+    <!-- 添加股票到stock_info对话框 -->
+    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="股票代码" prop="stockCode">
+          <el-input v-model="form.stockCode" placeholder="请输入6位股票代码" maxlength="6" />
+        </el-form-item>
+        <el-form-item label="股票名称" prop="stockName">
+          <el-input v-model="form.stockName" placeholder="请输入股票名称" maxlength="64" />
+        </el-form-item>
+        <el-form-item label="市场" prop="market">
+          <el-select v-model="form.market" placeholder="请选择市场">
+            <el-option label="上海" value="SH" />
+            <el-option label="深圳" value="SZ" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+
+<script setup name="StockPool" lang="ts">
+import { ref, reactive, onMounted, onUnmounted, onActivated, getCurrentInstance } from 'vue';
+import { listStockPool, removeFromPool } from '@/api/stock/pool';
+import { addStockInfo } from '@/api/stock/info';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const poolList = ref<any[]>([]);
+const loading = ref(true);
+const refreshing = ref(false);
+const exporting = ref(false);
+const showSearch = ref(true);
+const ids = ref<number[]>([]);
+const multiple = ref(true);
+const total = ref(0);
+
+let refreshTimer: ReturnType<typeof setTimeout> | null = null;
+
+const queryFormRef = ref<ElFormInstance>();
+const formRef = ref<ElFormInstance>();
+
+const dateRange = ref<string[]>([]);
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 20,
+  stockCode: '',
+  stockName: '',
+  poolType: undefined as number | undefined,
+  status: 1 as number | undefined,
+  startDate: undefined as string | undefined,
+  endDate: undefined as string | undefined
+});
+
+const dialog = reactive({ visible: false, title: '' });
+const form = ref<any>({});
+
+const rules = {
+  stockCode: [
+    { required: true, message: '股票代码不能为空', trigger: 'blur' },
+    { pattern: /^\d{6}$/, message: '股票代码必须是6位数字', trigger: 'blur' }
+  ],
+  stockName: [{ required: true, message: '股票名称不能为空', trigger: 'blur' }],
+  market: [{ required: true, message: '市场不能为空', trigger: 'change' }]
+};
+
+/** 获取随机刷新间隔 (2000-3000ms) */
+const getRandomInterval = () => 2000 + Math.random() * 1000;
+
+/** 启动自动刷新 */
+const startAutoRefresh = () => {
+  stopAutoRefresh();
+  refreshTimer = setTimeout(async () => {
+    await refreshQuotesOnly();
+    startAutoRefresh();
+  }, getRandomInterval());
+};
+
+/** 停止自动刷新 */
+const stopAutoRefresh = () => {
+  if (refreshTimer) {
+    clearTimeout(refreshTimer);
+    refreshTimer = null;
+  }
+};
+
+/** 查询列表(首次加载或翻页) */
+const getList = async () => {
+  loading.value = true;
+  stopAutoRefresh();
+  try {
+    const res = await listStockPool(queryParams.value);
+    poolList.value = res.rows || [];
+    total.value = res.total || 0;
+    startAutoRefresh();
+  } catch (error) {
+    console.error('获取股票池列表失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 局部刷新行情数据(不显示loading) */
+const refreshQuotesOnly = async () => {
+  if (poolList.value.length === 0) return;
+  try {
+    const res = await listStockPool(queryParams.value);
+    const newData = res.rows || [];
+    // 局部更新行情字段
+    poolList.value.forEach((item) => {
+      const newItem = newData.find((n: any) => n.id === item.id);
+      if (newItem) {
+        item.currentPrice = newItem.currentPrice;
+        item.changePercent = newItem.changePercent;
+        item.changeAmount = newItem.changeAmount;
+        item.turnoverRate = newItem.turnoverRate;
+        item.tradeAmount = newItem.tradeAmount;
+        item.yesterdayClose = newItem.yesterdayClose;
+        item.profitPercent = newItem.profitPercent;
+      }
+    });
+  } catch (error) {
+    console.error('刷新行情失败:', error);
+  }
+};
+
+/** 手动刷新行情 */
+const refreshQuotes = async () => {
+  refreshing.value = true;
+  stopAutoRefresh();
+  try {
+    await refreshQuotesOnly();
+    startAutoRefresh();
+  } finally {
+    refreshing.value = false;
+  }
+};
+
+/** 导出(获取全表数据) */
+const handleExport = async () => {
+  exporting.value = true;
+  try {
+    proxy?.download('stock/pool/export', {
+      stockCode: queryParams.value.stockCode,
+      stockName: queryParams.value.stockName,
+      poolType: queryParams.value.poolType,
+      status: queryParams.value.status
+    }, `stock_pool_${new Date().getTime()}.xlsx`);
+  } finally {
+    setTimeout(() => { exporting.value = false; }, 1000);
+  }
+};
+
+/** 搜索按钮 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  // 处理日期范围
+  if (dateRange.value && dateRange.value.length === 2) {
+    queryParams.value.startDate = dateRange.value[0];
+    queryParams.value.endDate = dateRange.value[1];
+  } else {
+    queryParams.value.startDate = undefined;
+    queryParams.value.endDate = undefined;
+  }
+  getList();
+};
+
+/** 重置按钮 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  dateRange.value = [];
+  queryParams.value.poolType = undefined;
+  queryParams.value.status = 1;
+  queryParams.value.startDate = undefined;
+  queryParams.value.endDate = undefined;
+  handleQuery();
+};
+
+/** 多选框选中 */
+const handleSelectionChange = (selection: any[]) => {
+  ids.value = selection.map(item => item.id);
+  multiple.value = !selection.length;
+};
+
+/** 添加股票按钮(添加到stock_info表) */
+const handleAddStock = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加股票信息';
+};
+
+/** 提交表单(添加股票到stock_info) */
+const submitForm = () => {
+  formRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      await addStockInfo(form.value);
+      proxy?.$modal.msgSuccess('添加成功');
+      dialog.visible = false;
+    }
+  });
+};
+
+/** 删除按钮 */
+const handleDelete = async (row?: any) => {
+  const poolIds = row?.id ? [row.id] : ids.value;
+  await proxy?.$modal.confirm(`是否确认移除选中的股票?`);
+  await removeFromPool(poolIds);
+  proxy?.$modal.msgSuccess('移除成功');
+  await getList();
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  dialog.visible = false;
+  reset();
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { stockCode: '', stockName: '', market: undefined };
+  formRef.value?.resetFields();
+};
+
+/** 格式化价格 */
+const formatPrice = (val: any) => (val != null ? Number(val).toFixed(2) : '-');
+
+/** 格式化百分比 */
+const formatPercent = (val: any) => (val != null ? `${Number(val) >= 0 ? '+' : ''}${Number(val).toFixed(2)}%` : '-');
+
+/** 获取价格样式 */
+const getPriceClass = (row: any) => {
+  if (row.changePercent == null) return '';
+  return row.changePercent > 0 ? 'text-red-500' : row.changePercent < 0 ? 'text-green-500' : '';
+};
+
+/** 获取涨跌样式 */
+const getChangeClass = (val: any) => {
+  if (val == null) return '';
+  return val > 0 ? 'text-red-500 font-bold' : val < 0 ? 'text-green-500 font-bold' : '';
+};
+
+onMounted(() => {
+  getList();
+});
+
+onActivated(() => {
+  // 页面被激活时(从其他页面切换回来)刷新数据
+  getList();
+});
+
+onUnmounted(() => {
+  stopAutoRefresh();
+});
+</script>
+
+<style scoped>
+.text-red-500 { color: #ef4444; }
+.text-green-500 { color: #22c55e; }
+.font-bold { font-weight: bold; }
+</style>