修改
This commit is contained in:
Generated
+32
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"axios": "^1.15.2",
|
"axios": "^1.15.2",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
"element-plus": "^2.13.7",
|
"element-plus": "^2.13.7",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4"
|
||||||
@@ -765,6 +766,22 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/echarts": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/echarts/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/element-plus": {
|
"node_modules/element-plus": {
|
||||||
"version": "2.13.7",
|
"version": "2.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.7.tgz",
|
||||||
@@ -1642,6 +1659,21 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.5.0"
|
"vue": "^3.5.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zrender": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zrender/node_modules/tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||||
|
"license": "0BSD"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.2",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"axios": "^1.15.2",
|
"axios": "^1.15.2",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
"element-plus": "^2.13.7",
|
"element-plus": "^2.13.7",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4"
|
||||||
|
|||||||
+1
-9
@@ -2,14 +2,6 @@
|
|||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
|
||||||
// 全局入口,无需额外逻辑
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body { margin: 0; background-color: #f5f7fa; }
|
||||||
margin: 0;
|
|
||||||
background-color: #f0f2f5;
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
export function getTradeCalendar() {
|
||||||
|
return request({ url: '/common/trade-calendar', method: 'get' })
|
||||||
|
}
|
||||||
+14
-8
@@ -1,11 +1,17 @@
|
|||||||
import axios from 'axios'
|
import request from '@/utils/request'
|
||||||
|
|
||||||
const request = axios.create({
|
export function getAnalyzeData(data) {
|
||||||
baseURL: 'http://localhost:18080/api/v1', // 指向你的 Go 后端
|
return request({
|
||||||
timeout: 10000
|
url: '/stock/analyze',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:获取历史K线
|
||||||
|
export function getKlineData(code) {
|
||||||
|
return request({
|
||||||
|
url: `/stock/kline/${code}`,
|
||||||
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取涨停回落分析数据
|
|
||||||
export const getAnalyzeData = (params) => {
|
|
||||||
return request.get('/stock/analyze', { params })
|
|
||||||
}
|
}
|
||||||
+13
-2
@@ -3,13 +3,24 @@ import App from './App.vue'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
// 引入中文语言包
|
|
||||||
|
// 引入 Element Plus 中文语言包
|
||||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||||
|
|
||||||
|
// 引入图标
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// 全局注册 Element Plus 图标组件
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册路由
|
||||||
app.use(router)
|
app.use(router)
|
||||||
// 全局配置 Element Plus 使用中文
|
|
||||||
|
// 注册 Element Plus 并配置中文国际化
|
||||||
app.use(ElementPlus, {
|
app.use(ElementPlus, {
|
||||||
locale: zhCn,
|
locale: zhCn,
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,8 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/stock/analyze',
|
path: '/stock/analyze',
|
||||||
name: 'StockAnalyze',
|
name: 'StockAnalyze',
|
||||||
component: () => import('../views/stock/Analyze.vue')
|
component: () => import('@/views/stock/Analyze.vue'),
|
||||||
|
meta: { title: '涨停回落分析' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: '/api/v1',
|
||||||
|
timeout: 15000
|
||||||
|
})
|
||||||
|
|
||||||
|
service.interceptors.response.use(
|
||||||
|
response => response.data,
|
||||||
|
error => {
|
||||||
|
ElMessage.error(error.response?.data?.error || '请求失败')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
+281
-128
@@ -1,178 +1,331 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="analyze-container">
|
<div class="analyze-container">
|
||||||
<el-card shadow="never">
|
<el-row :gutter="15" class="top-row">
|
||||||
<template #header>
|
<el-col :span="12">
|
||||||
<div class="header-title">📊 涨停后回落分析</div>
|
<div class="glass-card filter-container">
|
||||||
</template>
|
<el-form :inline="true" class="pure-form">
|
||||||
|
<el-form-item label="起算">
|
||||||
<div class="filter-section">
|
|
||||||
<div class="filter-item">
|
|
||||||
<span class="label-text">选定日期:</span>
|
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="startDate"
|
v-model="startDate"
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="请选择"
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
|
:disabled-date="disableDates"
|
||||||
|
:editable="false"
|
||||||
|
placeholder="点击选择"
|
||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</el-form-item>
|
||||||
|
<el-form-item label="截止">
|
||||||
<div class="filter-item">
|
|
||||||
<span class="label-text">结束日期:</span>
|
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="endDate"
|
v-model="endDate"
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="请选择"
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
|
:disabled-date="disableDates"
|
||||||
|
:editable="false"
|
||||||
|
placeholder="点击选择"
|
||||||
style="width: 140px"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery" :loading="loading" plain>开始分析</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
</el-col>
|
||||||
<el-button
|
<el-col :span="12">
|
||||||
type="primary"
|
<div class="glass-card info-container">
|
||||||
@click="fetchData"
|
<div class="stat-group">
|
||||||
:loading="loading"
|
<div class="stat-box">
|
||||||
style="margin-left: 12px"
|
<span class="label">样本总数</span>
|
||||||
>
|
<span class="value">{{ tableData.length }}</span>
|
||||||
查询数据
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<span class="label">强势占比</span>
|
||||||
|
<span class="value" :style="{ color: parseFloat(strongPercent) > 50 ? '#f56c6c' : '#409EFF' }">
|
||||||
|
{{ strongPercent }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-card class="main-card" shadow="never">
|
||||||
<el-table
|
<el-table
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
stripe
|
stripe
|
||||||
style="width: 100%; margin-top: 20px"
|
border
|
||||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
height="calc(100vh - 180px)"
|
||||||
:default-sort="{ prop: 'drop_pct', order: 'descending' }"
|
style="width: 100%"
|
||||||
|
:header-cell-style="{ background: '#f8f9fb', color: '#444', textAlign: 'center' }"
|
||||||
>
|
>
|
||||||
<el-table-column prop="code" label="代码" width="100" />
|
<el-table-column type="index" label="ID" width="55" fixed="left" align="center" />
|
||||||
<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
|
<el-table-column label="股票详情 / 7日趋势" width="260" fixed="left">
|
||||||
prop="drop_pct"
|
<template #default="{ row }">
|
||||||
label="最大回落幅度%"
|
<div class="stock-combined-box">
|
||||||
sortable
|
<div class="meta-info clickable" @click="openKlineDialog(row)" title="点击查看历史K线">
|
||||||
align="right"
|
<span class="c-code">{{ row.code.split('.')[0] }}</span>
|
||||||
>
|
<span class="c-name">{{ row.name }}</span>
|
||||||
<template #default="scope">
|
</div>
|
||||||
<span v-if="scope.row.drop_pct !== null" class="text-success">
|
<div class="sparkline-container">
|
||||||
{{ scope.row.drop_pct.toFixed(2) }}%
|
<svg width="110" height="34" class="sparkline-svg">
|
||||||
</span>
|
<path
|
||||||
<span v-else class="text-gray">--</span>
|
fill="none"
|
||||||
|
stroke="#409EFF"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
:d="generateSparklinePath(row.close_prices)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column
|
<el-table-column label="价格参考" align="center">
|
||||||
prop="rebound_pct"
|
<el-table-column prop="day_high" label="涨停价" width="100" align="center">
|
||||||
label="低位反弹%"
|
<template #default="{row}">
|
||||||
sortable
|
<span class="num-text">{{ row.day_high.toFixed(2) }}</span>
|
||||||
align="right"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
|
||||||
<span
|
|
||||||
v-if="scope.row.rebound_pct !== null"
|
|
||||||
:class="scope.row.rebound_pct > 0 ? 'text-danger' : 'text-gray'"
|
|
||||||
>
|
|
||||||
{{ scope.row.rebound_pct.toFixed(2) }}%
|
|
||||||
</span>
|
|
||||||
<span v-else class="text-gray">--</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="overall_max_high" label="区间最高" width="100" align="center">
|
||||||
|
<template #default="{row}">
|
||||||
|
<span class="num-text bold">{{ row.overall_max_high.toFixed(2) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="overall_max_date" label="最高日期" width="110" align="center" />
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="形态核心分析" align="center">
|
||||||
|
<el-table-column prop="drop_pct" label="最大回落%" sortable width="130" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div v-if="row.drop_pct !== null" class="status-tag tag-green">
|
||||||
|
{{ row.drop_pct.toFixed(2) }}%
|
||||||
|
</div>
|
||||||
|
<div v-else class="status-tag tag-blue">强势不破</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="rebound_pct" label="涨幅 / 反弹%" sortable min-width="130" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div :class="['status-tag', row.drop_pct === null ? 'tag-orange' : 'tag-red']">
|
||||||
|
{{ row.rebound_pct.toFixed(2) }}%
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
width="85%"
|
||||||
|
top="5vh"
|
||||||
|
destroy-on-close
|
||||||
|
draggable
|
||||||
|
>
|
||||||
|
<div id="kline-chart" style="width: 100%; height: 550px;"></div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||||
import { getAnalyzeData } from "@/api/stock";
|
import * as echarts from 'echarts'
|
||||||
import { ElMessage } from "element-plus";
|
import dayjs from 'dayjs'
|
||||||
|
import { getTradeCalendar } from '@/api/common'
|
||||||
|
import { getAnalyzeData, getKlineData } from '@/api/stock'
|
||||||
|
|
||||||
const startDate = ref("2026-03-02");
|
// 响应式数据
|
||||||
const endDate = ref("2026-04-23");
|
const startDate = ref('2026-04-22')
|
||||||
const tableData = ref([]);
|
const endDate = ref('')
|
||||||
const loading = ref(false);
|
const tableData = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const openDatesSet = ref(new Set())
|
||||||
|
const maxTradeDate = ref('')
|
||||||
|
|
||||||
const fetchData = async () => {
|
// 弹窗与图表变量
|
||||||
if (!startDate.value || !endDate.value) {
|
const dialogVisible = ref(false)
|
||||||
return ElMessage.warning("请选择日期");
|
const dialogTitle = ref('')
|
||||||
}
|
let chartInstance = null
|
||||||
|
|
||||||
if (new Date(startDate.value) > new Date(endDate.value)) {
|
// 计算属性:统计强势占比
|
||||||
return ElMessage.warning("选定日期不能晚于结束日期");
|
const strongPercent = computed(() => {
|
||||||
}
|
if (!tableData.value.length) return '0.0'
|
||||||
|
const count = tableData.value.filter(i => i.drop_pct === null).length
|
||||||
|
return ((count / tableData.value.length) * 100).toFixed(1)
|
||||||
|
})
|
||||||
|
|
||||||
loading.value = true;
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAnalyzeData({
|
const res = await getTradeCalendar()
|
||||||
start_date: startDate.value,
|
openDatesSet.value = new Set(res.open_dates)
|
||||||
end_date: endDate.value,
|
maxTradeDate.value = res.max_date
|
||||||
});
|
if (!endDate.value) endDate.value = res.max_date
|
||||||
tableData.value = res.data;
|
|
||||||
ElMessage.success(`加载成功: ${res.data.length} 条数据`);
|
// 局域网访问提示:建议 axios baseURL 使用 `http://${window.location.hostname}:8080/api/v1`
|
||||||
|
console.log("当前主机访问地址:", window.location.hostname)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ElMessage.error("数据获取失败");
|
console.error("初始化数据失败")
|
||||||
} finally {
|
}
|
||||||
loading.value = false;
|
})
|
||||||
|
|
||||||
|
// 日期禁用逻辑
|
||||||
|
const disableDates = (time) => {
|
||||||
|
const d = dayjs(time).format('YYYY-MM-DD')
|
||||||
|
if (maxTradeDate.value && d > maxTradeDate.value) return true
|
||||||
|
return openDatesSet.value.size > 0 ? !openDatesSet.value.has(d) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分析查询
|
||||||
|
const handleQuery = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getAnalyzeData({ start_date: startDate.value, end_date: endDate.value })
|
||||||
|
tableData.value = res
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 Sparkline 路径
|
||||||
|
const generateSparklinePath = (prices) => {
|
||||||
|
if (!prices || prices.length < 2) return ""
|
||||||
|
const min = Math.min(...prices), max = Math.max(...prices), range = max - min || 1
|
||||||
|
const w = 110, h = 34, p = 4
|
||||||
|
const points = prices.map((val, i) => ({
|
||||||
|
x: (i / (prices.length - 1)) * w,
|
||||||
|
y: h - p - ((val - min) / range) * (h - 2 * p)
|
||||||
|
}))
|
||||||
|
return `M ${points[0].x} ${points[0].y} ` + points.slice(1).map(pt => `L ${pt.x} ${pt.y}`).join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染 K 线图
|
||||||
|
const openKlineDialog = async (row) => {
|
||||||
|
dialogTitle.value = `${row.name} (${row.code}) - 自 2024-09-24 以来走势`
|
||||||
|
dialogVisible.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawData = await getKlineData(row.code)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
const chartDom = document.getElementById('kline-chart')
|
||||||
|
if (!chartDom) return
|
||||||
|
|
||||||
|
if (chartInstance) chartInstance.dispose()
|
||||||
|
chartInstance = echarts.init(chartDom)
|
||||||
|
|
||||||
|
// 数据提取 (匹配后端 JSON 标签)
|
||||||
|
const dates = rawData.map(d => dayjs(d.trade_date).format('YYYY-MM-DD'))
|
||||||
|
const values = rawData.map(d => [
|
||||||
|
d.open,
|
||||||
|
d.close,
|
||||||
|
d.low,
|
||||||
|
d.high,
|
||||||
|
d.pct_chg // 传递给 Tooltip
|
||||||
|
])
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
animation: false,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'cross' },
|
||||||
|
formatter: function (params) {
|
||||||
|
const d = params[0]
|
||||||
|
const pct = d.value[5] || 0
|
||||||
|
const color = pct >= 0 ? '#f56c6c' : '#67c23a'
|
||||||
|
return `
|
||||||
|
<div style="font-size:12px; min-width:120px;">
|
||||||
|
<b>${d.name}</b><hr style="margin:5px 0;border:none;border-top:1px solid #eee"/>
|
||||||
|
涨跌幅: <span style="color:${color};font-weight:bold;">${pct > 0 ? '+' : ''}${pct.toFixed(2)}%</span><br/>
|
||||||
|
开盘价: ${d.value[1].toFixed(2)}<br/>
|
||||||
|
收盘价: ${d.value[2].toFixed(2)}<br/>
|
||||||
|
最低价: ${d.value[3].toFixed(2)}<br/>
|
||||||
|
最高价: ${d.value[4].toFixed(2)}
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: { left: '60', right: '30', bottom: '10%', top: '40' },
|
||||||
|
xAxis: { type: 'category', data: dates, scale: true, boundaryGap: false },
|
||||||
|
yAxis: { scale: true, splitArea: { show: true } },
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', start: 80, end: 100 },
|
||||||
|
{ show: true, type: 'slider', top: '92%' }
|
||||||
|
],
|
||||||
|
series: [{
|
||||||
|
name: '日K',
|
||||||
|
type: 'candlestick',
|
||||||
|
data: values,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#f56c6c', color0: '#67c23a',
|
||||||
|
borderColor: '#f56c6c', borderColor0: '#67c23a'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
chartInstance.setOption(option)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("加载K线详情失败", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.analyze-container {
|
.analyze-container { padding: 20px; background-color: #f4f7f9; min-height: 100vh; }
|
||||||
padding: 24px;
|
|
||||||
background-color: #f0f2f5;
|
.top-row { margin-bottom: 20px; }
|
||||||
min-height: calc(100vh - 48px);
|
.glass-card {
|
||||||
}
|
background: #fff;
|
||||||
.header-title {
|
height: 70px;
|
||||||
font-weight: 600;
|
border-radius: 12px;
|
||||||
font-size: 18px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
.filter-section {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
padding: 0 20px;
|
||||||
gap: 20px;
|
box-shadow: 0 4px 12px rgba(0,0,0,0.03);
|
||||||
}
|
}
|
||||||
.filter-item {
|
|
||||||
display: flex;
|
.stat-group { display: flex; width: 100%; justify-content: space-around; align-items: center; }
|
||||||
align-items: center;
|
.stat-box { display: flex; flex-direction: column; align-items: center; }
|
||||||
}
|
.stat-box .label { font-size: 12px; color: #999; margin-bottom: 4px; }
|
||||||
.label-text {
|
.stat-box .value { font-size: 24px; font-weight: 800; color: #333; }
|
||||||
font-size: 14px;
|
.divider { width: 1px; height: 30px; background: #eee; }
|
||||||
color: #606266;
|
|
||||||
margin-right: 8px;
|
.stock-combined-box { display: flex; align-items: center; justify-content: space-between; width: 100%; }
|
||||||
}
|
.meta-info { display: flex; flex-direction: column; }
|
||||||
.text-success {
|
.meta-info.clickable { cursor: pointer; }
|
||||||
color: #67c23a;
|
/* 鼠标滑过代码名称时,给出视觉反馈 */
|
||||||
|
.meta-info.clickable:hover .c-name { color: #409EFF; text-decoration: underline; }
|
||||||
|
|
||||||
|
.c-code { font-family: 'JetBrains Mono', monospace; font-weight: bold; color: #409EFF; font-size: 14px; }
|
||||||
|
.c-name { font-size: 13px; color: #666; transition: color 0.2s; }
|
||||||
|
.sparkline-container { border-left: 1px dashed #eee; padding-left: 10px; margin-left: 10px; }
|
||||||
|
|
||||||
|
/* 日历光标强制为手型 */
|
||||||
|
:deep(.el-input__wrapper) { cursor: pointer !important; }
|
||||||
|
:deep(.el-input__inner) { cursor: pointer !important; user-select: none; }
|
||||||
|
|
||||||
|
/* 弹窗标题栏可移动提示 */
|
||||||
|
:deep(.el-dialog__header) { cursor: move; margin-right: 0; padding-bottom: 15px; border-bottom: 1px solid #f5f5f5; }
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 85px;
|
||||||
}
|
}
|
||||||
.text-danger {
|
.tag-green { background: #f0f9eb; color: #67c23a; }
|
||||||
color: #f56c6c;
|
.tag-blue { background: #ecf5ff; color: #409EFF; }
|
||||||
font-weight: bold;
|
.tag-orange { background: #fdf6ec; color: #e6a23c; }
|
||||||
}
|
.tag-red { background: #fef0f0; color: #f56c6c; }
|
||||||
.text-gray {
|
|
||||||
color: #909399;
|
.num-text { font-family: 'JetBrains Mono', monospace; }
|
||||||
}
|
.bold { font-weight: bold; }
|
||||||
:deep(.el-table .cell) {
|
.main-card { border-radius: 12px; border: none; }
|
||||||
padding: 0 12px;
|
|
||||||
}
|
:deep(.el-form-item) { margin-bottom: 0; }
|
||||||
</style>
|
</style>
|
||||||
+9
-4
@@ -5,11 +5,16 @@ import path from 'path'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: { '@': path.resolve(__dirname, 'src') }
|
||||||
'@': path.resolve(__dirname, './src')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 5173
|
host: '0.0.0.0', // 监听所有地址,包括局域网 IP
|
||||||
|
port: 5173, // 你的端口号
|
||||||
|
proxy: {
|
||||||
|
'/api/v1': {
|
||||||
|
target: 'http://localhost:18080',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user