更改
This commit is contained in:
+8
-153
@@ -1,160 +1,15 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>📈 股票涨停回落分析工具</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="filter-bar">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
placeholder="请选择"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled-date="disabledStartDate"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
placeholder="请选择"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled-date="disabledEndDate"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleSearch"
|
||||
:loading="loading"
|
||||
icon="Search"
|
||||
>
|
||||
查询分析
|
||||
</el-button>
|
||||
<el-button @click="resetFilter">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="tableData"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
:default-sort="{ prop: 'drop_pct', order: 'descending' }"
|
||||
>
|
||||
<el-table-column prop="code" label="代码" width="100" />
|
||||
<el-table-column prop="name" label="名称" width="120" />
|
||||
<el-table-column prop="day_open" label="当日开盘" />
|
||||
<el-table-column prop="max_high" label="区间最高" sortable />
|
||||
<el-table-column prop="max_date" label="最高日期" width="120" />
|
||||
<el-table-column prop="post_min_low" label="见顶后最低" />
|
||||
|
||||
<el-table-column prop="drop_pct" label="最大回落幅%" sortable>
|
||||
<template #default="scope">
|
||||
<b style="color: #f56c6c">{{ scope.row.drop_pct?.toFixed(2) }}%</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="rebound_pct" label="低位反弹%" sortable>
|
||||
<template #default="scope">
|
||||
<span
|
||||
:style="{
|
||||
color: scope.row.rebound_pct > 0 ? '#67c23a' : '#909399',
|
||||
}"
|
||||
>
|
||||
{{ scope.row.rebound_pct?.toFixed(2) }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import axios from "axios";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
// 1. 将范围数组拆分为两个独立状态
|
||||
const startDate = ref("2024-09-24");
|
||||
const endDate = ref("2026-04-23");
|
||||
const tableData = ref([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 2. 逻辑约束:开始日期不能晚于结束日期
|
||||
const disabledStartDate = (time) => {
|
||||
if (!endDate.value) return false;
|
||||
return time.getTime() > new Date(endDate.value).getTime();
|
||||
};
|
||||
|
||||
// 3. 逻辑约束:结束日期不能早于开始日期
|
||||
const disabledEndDate = (time) => {
|
||||
if (!startDate.value) return false;
|
||||
return time.getTime() < new Date(startDate.value).getTime();
|
||||
};
|
||||
|
||||
// 重置功能
|
||||
const resetFilter = () => {
|
||||
startDate.value = "";
|
||||
endDate.value = "";
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
// 校验非空
|
||||
if (!startDate.value || !endDate.value) {
|
||||
ElMessage.warning("请完整选择开始和结束日期");
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await axios.get(
|
||||
"http://localhost:18080/api/v1/stock/analyze",
|
||||
{
|
||||
params: {
|
||||
start_date: startDate.value,
|
||||
end_date: endDate.value,
|
||||
},
|
||||
},
|
||||
);
|
||||
tableData.value = response.data;
|
||||
ElMessage.success(`查询成功,共计 ${tableData.value.length} 条记录`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ElMessage.error("获取数据失败,请检查后端服务");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
// 全局入口,无需额外逻辑
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 20px;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #f0f2f5;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.card-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.filter-bar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
/* 调整表单间距 */
|
||||
:deep(.el-form-item) {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const request = axios.create({
|
||||
baseURL: 'http://localhost:18080/api/v1', // 指向你的 Go 后端
|
||||
timeout: 10000
|
||||
})
|
||||
|
||||
// 获取涨停回落分析数据
|
||||
export const getAnalyzeData = (params) => {
|
||||
return request.get('/stock/analyze', { params })
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 496 B |
@@ -1,95 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import viteLogo from '../assets/vite.svg'
|
||||
import heroImg from '../assets/hero.png'
|
||||
import vueLogo from '../assets/vue.svg'
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="center">
|
||||
<div class="hero">
|
||||
<img :src="heroImg" class="base" width="170" height="179" alt="" />
|
||||
<img :src="vueLogo" class="framework" alt="Vue logo" />
|
||||
<img :src="viteLogo" class="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>Edit <code>src/App.vue</code> and save to test <code>HMR</code></p>
|
||||
</div>
|
||||
<button type="button" class="counter" @click="count++">
|
||||
Count is {{ count }}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img class="logo" :src="viteLogo" alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img class="button-icon" :src="vueLogo" alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</template>
|
||||
+10
-1
@@ -1,8 +1,17 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
// 引入中文语言包
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus)
|
||||
|
||||
app.use(router)
|
||||
// 全局配置 Element Plus 使用中文
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/stock/analyze'
|
||||
},
|
||||
{
|
||||
path: '/stock/analyze',
|
||||
name: 'StockAnalyze',
|
||||
component: () => import('../views/stock/Analyze.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="analyze-container">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="header-title">📊 涨停后回落分析</div>
|
||||
</template>
|
||||
|
||||
<div class="filter-section">
|
||||
<div class="filter-item">
|
||||
<span class="label-text">选定日期:</span>
|
||||
<el-date-picker
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
placeholder="请选择"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="filter-item">
|
||||
<span class="label-text">结束日期:</span>
|
||||
<el-date-picker
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
placeholder="请选择"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="fetchData"
|
||||
:loading="loading"
|
||||
style="margin-left: 12px"
|
||||
>
|
||||
查询数据
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
style="width: 100%; margin-top: 20px"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||
:default-sort="{ prop: 'drop_pct', order: 'descending' }"
|
||||
>
|
||||
<el-table-column prop="code" label="代码" width="100" />
|
||||
<el-table-column prop="name" label="名称" width="120" />
|
||||
<el-table-column prop="day_open" label="当日开盘" align="right" />
|
||||
<el-table-column prop="max_high" label="区间最高" sortable align="right" />
|
||||
<el-table-column prop="max_date" label="最高日期" width="120" align="center" />
|
||||
<el-table-column prop="post_min_low" label="见顶后最低" align="right" />
|
||||
|
||||
<el-table-column prop="drop_pct" label="最大回落幅度%" sortable align="right">
|
||||
<template #default="scope">
|
||||
<span class="text-success">{{ scope.row.drop_pct.toFixed(2) }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="rebound_pct" label="低位反弹%" sortable align="right">
|
||||
<template #default="scope">
|
||||
<span :class="scope.row.rebound_pct > 0 ? 'text-danger' : 'text-gray'">
|
||||
{{ scope.row.rebound_pct.toFixed(2) }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { getAnalyzeData } from '@/api/stock'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// 修改默认日期为 2026-04-22
|
||||
const startDate = ref('2026-04-22')
|
||||
const endDate = ref('2026-04-23')
|
||||
const tableData = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const fetchData = async () => {
|
||||
if (!startDate.value || !endDate.value) {
|
||||
return ElMessage.warning('请选择日期')
|
||||
}
|
||||
|
||||
if (new Date(startDate.value) > new Date(endDate.value)) {
|
||||
return ElMessage.warning('选定日期不能晚于结束日期')
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getAnalyzeData({
|
||||
start_date: startDate.value,
|
||||
end_date: endDate.value
|
||||
})
|
||||
tableData.value = res.data
|
||||
ElMessage.success(`加载成功: ${res.data.length} 条数据`)
|
||||
} catch (err) {
|
||||
ElMessage.error('数据获取失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.analyze-container {
|
||||
padding: 24px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 48px);
|
||||
}
|
||||
.header-title {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
}
|
||||
.filter-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.label-text {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #67c23a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
:deep(.el-table .cell) {
|
||||
padding: 0 12px;
|
||||
}
|
||||
</style>
|
||||
+10
-2
@@ -1,7 +1,15 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import path from 'path'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 5173
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user