修改
This commit is contained in:
+179
-3
@@ -1,7 +1,183 @@
|
||||
<template>
|
||||
<router-view />
|
||||
<div class="app-shell">
|
||||
<aside :class="['sidebar', { collapsed: isSidebarCollapsed }]">
|
||||
<div class="sidebar-top">
|
||||
<button class="sidebar-toggle" type="button" @click="toggleSidebar">
|
||||
<el-icon><component :is="isSidebarCollapsed ? Expand : Fold" /></el-icon>
|
||||
</button>
|
||||
<div v-if="!isSidebarCollapsed" class="brand-block">
|
||||
<span class="brand-title">Stock Lab</span>
|
||||
<span class="brand-subtitle">页面导航</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
:collapse="isSidebarCollapsed"
|
||||
:collapse-transition="false"
|
||||
class="nav-menu"
|
||||
router
|
||||
>
|
||||
<el-menu-item
|
||||
v-for="item in menuItems"
|
||||
:key="item.path"
|
||||
:index="item.path"
|
||||
@click="navigateTo(item.path)"
|
||||
>
|
||||
<el-icon><DataAnalysis /></el-icon>
|
||||
<template #title>{{ item.title }}</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</aside>
|
||||
|
||||
<main class="page-main">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body { margin: 0; background-color: #f5f7fa; }
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { DataAnalysis, Expand, Fold } from '@element-plus/icons-vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const isSidebarCollapsed = ref(false)
|
||||
|
||||
const menuItems = computed(() =>
|
||||
router.options.routes
|
||||
.filter((item) => item.path && item.meta?.title && !item.redirect)
|
||||
.map((item) => ({
|
||||
path: item.path,
|
||||
title: item.meta.title
|
||||
}))
|
||||
)
|
||||
|
||||
const activeMenu = computed(() => route.path)
|
||||
|
||||
const toggleSidebar = () => {
|
||||
isSidebarCollapsed.value = !isSidebarCollapsed.value
|
||||
}
|
||||
|
||||
const navigateTo = (path) => {
|
||||
if (route.path === path) return
|
||||
router.push(path)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
linear-gradient(180deg, #f8fbff 0%, #f2f6fb 100%);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 236px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 18px 14px;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #e5edf5;
|
||||
transition: width 0.25s ease, padding 0.25s ease;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 76px;
|
||||
padding-inline: 10px;
|
||||
}
|
||||
|
||||
.sidebar-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 52px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid #dbe5f0;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f7fbff;
|
||||
color: #245d9c;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-toggle:hover {
|
||||
background: #eef6ff;
|
||||
}
|
||||
|
||||
.brand-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
color: #1f2a37;
|
||||
}
|
||||
|
||||
.brand-subtitle {
|
||||
font-size: 12px;
|
||||
color: #7c8aa5;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.page-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.nav-menu .el-menu-item) {
|
||||
height: 46px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 12px;
|
||||
color: #41536d;
|
||||
}
|
||||
|
||||
:deep(.nav-menu .el-menu-item .el-icon) {
|
||||
color: #5f7ea5;
|
||||
}
|
||||
|
||||
:deep(.nav-menu .el-menu-item:hover) {
|
||||
background: #eef6ff;
|
||||
color: #1f5fa8;
|
||||
}
|
||||
|
||||
:deep(.nav-menu .el-menu-item.is-active) {
|
||||
background: #e3f0ff;
|
||||
color: #13559d;
|
||||
}
|
||||
|
||||
:deep(.nav-menu.el-menu--collapse .el-menu-item) {
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.sidebar {
|
||||
width: 88px;
|
||||
padding-inline: 10px;
|
||||
}
|
||||
|
||||
.brand-block {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+247
-113
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="analyze-container">
|
||||
<el-row :gutter="15" class="top-row">
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="12">
|
||||
<div class="glass-card filter-container">
|
||||
<el-form :inline="true" class="pure-form">
|
||||
<el-form-item label="起算">
|
||||
@@ -27,12 +27,12 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading" plain>开始分析</el-button>
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading" :disabled="!startDate || !endDate" plain>开始分析</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="12">
|
||||
<div class="glass-card info-container">
|
||||
<div class="stat-group">
|
||||
<div class="stat-box">
|
||||
@@ -52,161 +52,227 @@
|
||||
</el-row>
|
||||
|
||||
<el-card class="main-card" shadow="never">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
border
|
||||
height="calc(100vh - 180px)"
|
||||
style="width: 100%"
|
||||
:header-cell-style="{ background: '#f8f9fb', color: '#444', textAlign: 'center' }"
|
||||
>
|
||||
<el-table-column type="index" label="ID" width="55" fixed="left" align="center" />
|
||||
<div class="table-shell">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
class="analyze-table"
|
||||
stripe
|
||||
border
|
||||
:height="tableHeight"
|
||||
style="width: 100%; min-width: 980px"
|
||||
:header-cell-style="{ background: '#f8f9fb', color: '#444', textAlign: 'center' }"
|
||||
>
|
||||
<el-table-column type="index" label="ID" width="55" fixed="left" align="center" />
|
||||
|
||||
<el-table-column label="股票详情 / 7日趋势" width="260" fixed="left">
|
||||
<template #default="{ row }">
|
||||
<div class="stock-combined-box">
|
||||
<div class="meta-info clickable" @click="openKlineDialog(row)" title="点击查看历史K线">
|
||||
<span class="c-code">{{ row.code.split('.')[0] }}</span>
|
||||
<span class="c-name">{{ row.name }}</span>
|
||||
</div>
|
||||
<div class="sparkline-container">
|
||||
<svg width="110" height="34" class="sparkline-svg">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#409EFF"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
:d="generateSparklinePath(row.close_prices)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="价格参考" align="center">
|
||||
<el-table-column prop="day_high" label="涨停价" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<span class="num-text">{{ row.day_high.toFixed(2) }}</span>
|
||||
</template>
|
||||
</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">
|
||||
<el-table-column label="股票详情 / 7日趋势" width="260" fixed="left">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.drop_pct !== null" class="status-tag tag-green">
|
||||
{{ row.drop_pct.toFixed(2) }}%
|
||||
<div class="stock-combined-box">
|
||||
<div class="meta-info clickable" @click="openKlineDialog(row)" title="点击查看历史K线">
|
||||
<span class="c-code">{{ getCodePrefix(row.code) }}</span>
|
||||
<span class="c-name">{{ row.name }}</span>
|
||||
</div>
|
||||
<div class="sparkline-container">
|
||||
<svg width="110" height="34" class="sparkline-svg">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#409EFF"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
:d="generateSparklinePath(row.close_prices)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</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 label="价格参考" align="center">
|
||||
<el-table-column prop="day_high" label="涨停价" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<span class="num-text">{{ formatPrice(row.day_high) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="overall_max_high" label="区间最高" width="100" align="center">
|
||||
<template #default="{row}">
|
||||
<span class="num-text bold">{{ formatPrice(row.overall_max_high) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="overall_max_date" label="最高日期" width="110" align="center" />
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<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">
|
||||
{{ formatPrice(row.drop_pct) }}%
|
||||
</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']">
|
||||
{{ formatPrice(row.rebound_pct) }}%
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="85%"
|
||||
class="kline-dialog"
|
||||
:width="dialogWidth"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
draggable
|
||||
overflow
|
||||
@closed="disposeChart"
|
||||
>
|
||||
<div id="kline-chart" style="width: 100%; height: 550px;"></div>
|
||||
<div id="kline-chart" :style="{ width: '100%', height: `${chartHeight}px` }"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||
import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import dayjs from 'dayjs'
|
||||
import { getTradeCalendar } from '@/api/common'
|
||||
import { getAnalyzeData, getKlineData } from '@/api/stock'
|
||||
|
||||
// 响应式数据
|
||||
const startDate = ref('2026-04-22')
|
||||
const DEFAULT_START_DATE = '2026-04-22'
|
||||
const DESKTOP_TABLE_HEIGHT = 'calc(100vh - 214px)'
|
||||
const TABLET_TABLE_HEIGHT = 'calc(100vh - 290px)'
|
||||
const MOBILE_TABLE_HEIGHT = 'calc(100vh - 332px)'
|
||||
const SPARKLINE_WIDTH = 110
|
||||
const SPARKLINE_HEIGHT = 34
|
||||
const SPARKLINE_PADDING = 4
|
||||
|
||||
const viewportWidth = ref(window.innerWidth)
|
||||
const endDate = ref('')
|
||||
const startDate = ref(DEFAULT_START_DATE)
|
||||
const tableData = ref([])
|
||||
const loading = ref(false)
|
||||
const openDatesSet = ref(new Set())
|
||||
const maxTradeDate = ref('')
|
||||
|
||||
// 弹窗与图表变量
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
let chartInstance = null
|
||||
|
||||
// 计算属性:统计强势占比
|
||||
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)
|
||||
const strongCount = tableData.value.filter((item) => item.drop_pct === null).length
|
||||
return ((strongCount / tableData.value.length) * 100).toFixed(1)
|
||||
})
|
||||
|
||||
const isTabletViewport = computed(() => viewportWidth.value <= 1024)
|
||||
const isPhoneViewport = computed(() => viewportWidth.value <= 820)
|
||||
|
||||
const tableHeight = computed(() => {
|
||||
if (isPhoneViewport.value) return MOBILE_TABLE_HEIGHT
|
||||
if (isTabletViewport.value) return TABLET_TABLE_HEIGHT
|
||||
return DESKTOP_TABLE_HEIGHT
|
||||
})
|
||||
|
||||
const dialogWidth = computed(() => {
|
||||
if (isPhoneViewport.value) return '96%'
|
||||
if (isTabletViewport.value) return '92%'
|
||||
return '85%'
|
||||
})
|
||||
|
||||
const chartHeight = computed(() => {
|
||||
if (isPhoneViewport.value) return 360
|
||||
if (isTabletViewport.value) return 440
|
||||
return 550
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
window.addEventListener('resize', syncViewport)
|
||||
|
||||
try {
|
||||
const res = await getTradeCalendar()
|
||||
openDatesSet.value = new Set(res.open_dates)
|
||||
maxTradeDate.value = res.max_date
|
||||
if (!endDate.value) endDate.value = res.max_date
|
||||
const { open_dates: openDates = [], max_date: maxDate = '' } = res || {}
|
||||
|
||||
// 局域网访问提示:建议 axios baseURL 使用 `http://${window.location.hostname}:8080/api/v1`
|
||||
console.log("当前主机访问地址:", window.location.hostname)
|
||||
openDatesSet.value = new Set(openDates)
|
||||
maxTradeDate.value = maxDate
|
||||
endDate.value = endDate.value || maxDate
|
||||
startDate.value = clampToMaxDate(startDate.value)
|
||||
} catch (err) {
|
||||
console.error("初始化数据失败")
|
||||
console.error('初始化分析页失败', err)
|
||||
}
|
||||
})
|
||||
|
||||
// 日期禁用逻辑
|
||||
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
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', syncViewport)
|
||||
disposeChart()
|
||||
})
|
||||
|
||||
const syncViewport = () => {
|
||||
viewportWidth.value = window.innerWidth
|
||||
chartInstance?.resize()
|
||||
}
|
||||
|
||||
const clampToMaxDate = (date) => {
|
||||
if (!date || !maxTradeDate.value || date <= maxTradeDate.value) return date
|
||||
return maxTradeDate.value
|
||||
}
|
||||
|
||||
const disableDates = (time) => {
|
||||
const date = dayjs(time).format('YYYY-MM-DD')
|
||||
if (maxTradeDate.value && date > maxTradeDate.value) return true
|
||||
return openDatesSet.value.size > 0 ? !openDatesSet.value.has(date) : false
|
||||
}
|
||||
|
||||
const formatPrice = (value) => Number(value ?? 0).toFixed(2)
|
||||
|
||||
const getCodePrefix = (code = '') => code.split('.')[0]
|
||||
|
||||
const disposeChart = () => {
|
||||
if (!chartInstance) return
|
||||
chartInstance.dispose()
|
||||
chartInstance = null
|
||||
}
|
||||
|
||||
// 分析查询
|
||||
const handleQuery = async () => {
|
||||
if (!startDate.value || !endDate.value) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getAnalyzeData({ start_date: startDate.value, end_date: endDate.value })
|
||||
tableData.value = res
|
||||
tableData.value = Array.isArray(res) ? res : []
|
||||
} catch (err) {
|
||||
console.error('加载分析数据失败', err)
|
||||
} 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)
|
||||
if (!Array.isArray(prices) || prices.length < 2) return ''
|
||||
|
||||
const min = Math.min(...prices)
|
||||
const max = Math.max(...prices)
|
||||
const range = max - min || 1
|
||||
const points = prices.map((price, index) => ({
|
||||
x: (index / (prices.length - 1)) * SPARKLINE_WIDTH,
|
||||
y: SPARKLINE_HEIGHT - SPARKLINE_PADDING - ((price - min) / range) * (SPARKLINE_HEIGHT - 2 * SPARKLINE_PADDING)
|
||||
}))
|
||||
return `M ${points[0].x} ${points[0].y} ` + points.slice(1).map(pt => `L ${pt.x} ${pt.y}`).join(" ")
|
||||
|
||||
return `M ${points[0].x} ${points[0].y} ${points
|
||||
.slice(1)
|
||||
.map((point) => `L ${point.x} ${point.y}`)
|
||||
.join(' ')}`
|
||||
}
|
||||
|
||||
// 渲染 K 线图
|
||||
const openKlineDialog = async (row) => {
|
||||
dialogTitle.value = `${row.name} (${row.code}) - 自 2024-09-24 以来走势`
|
||||
dialogTitle.value = `${row.name} (${row.code}) - 区间走势`
|
||||
dialogVisible.value = true
|
||||
|
||||
try {
|
||||
@@ -216,17 +282,16 @@ const openKlineDialog = async (row) => {
|
||||
const chartDom = document.getElementById('kline-chart')
|
||||
if (!chartDom) return
|
||||
|
||||
if (chartInstance) chartInstance.dispose()
|
||||
disposeChart()
|
||||
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 dates = rawData.map((item) => dayjs(item.trade_date).format('YYYY-MM-DD'))
|
||||
const values = rawData.map((item) => [
|
||||
item.open,
|
||||
item.close,
|
||||
item.low,
|
||||
item.high,
|
||||
item.pct_chg
|
||||
])
|
||||
|
||||
const option = {
|
||||
@@ -234,18 +299,18 @@ const openKlineDialog = async (row) => {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' },
|
||||
formatter: function (params) {
|
||||
const d = params[0]
|
||||
const pct = d.value[5] || 0
|
||||
formatter(params) {
|
||||
const point = params[0]
|
||||
const pct = point.value[4] || 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"/>
|
||||
<b>${point.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)}
|
||||
开盘价: ${formatPrice(point.value[0])}<br/>
|
||||
收盘价: ${formatPrice(point.value[1])}<br/>
|
||||
最低价: ${formatPrice(point.value[2])}<br/>
|
||||
最高价: ${formatPrice(point.value[3])}
|
||||
</div>`
|
||||
}
|
||||
},
|
||||
@@ -267,14 +332,20 @@ const openKlineDialog = async (row) => {
|
||||
}]
|
||||
}
|
||||
chartInstance.setOption(option)
|
||||
chartInstance.resize()
|
||||
} catch (err) {
|
||||
console.error("加载K线详情失败", err)
|
||||
console.error('加载K线详情失败', err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.analyze-container { padding: 20px; background-color: #f4f7f9; min-height: 100vh; }
|
||||
.analyze-container {
|
||||
padding: 20px;
|
||||
background-color: #f4f7f9;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top-row { margin-bottom: 20px; }
|
||||
.glass-card {
|
||||
@@ -292,6 +363,7 @@ const openKlineDialog = async (row) => {
|
||||
.stat-box .label { font-size: 12px; color: #999; margin-bottom: 4px; }
|
||||
.stat-box .value { font-size: 24px; font-weight: 800; color: #333; }
|
||||
.divider { width: 1px; height: 30px; background: #eee; }
|
||||
.table-shell { width: 100%; overflow-x: auto; overflow-y: hidden; }
|
||||
|
||||
.stock-combined-box { display: flex; align-items: center; justify-content: space-between; width: 100%; }
|
||||
.meta-info { display: flex; flex-direction: column; }
|
||||
@@ -306,9 +378,13 @@ const openKlineDialog = async (row) => {
|
||||
/* 日历光标强制为手型 */
|
||||
:deep(.el-input__wrapper) { cursor: pointer !important; }
|
||||
:deep(.el-input__inner) { cursor: pointer !important; user-select: none; }
|
||||
:deep(.pure-form) { display: flex; flex-wrap: wrap; gap: 8px 0; }
|
||||
:deep(.pure-form .el-form-item__label) { color: #5b6577; }
|
||||
|
||||
/* 弹窗标题栏可移动提示 */
|
||||
:deep(.el-dialog__header) { cursor: move; margin-right: 0; padding-bottom: 15px; border-bottom: 1px solid #f5f5f5; }
|
||||
:deep(.kline-dialog) { margin: 0 !important; max-width: 1200px; }
|
||||
:deep(.kline-dialog .el-dialog) { border-radius: 18px; overflow: hidden; }
|
||||
|
||||
.status-tag {
|
||||
display: inline-block;
|
||||
@@ -328,4 +404,62 @@ const openKlineDialog = async (row) => {
|
||||
.main-card { border-radius: 12px; border: none; }
|
||||
|
||||
:deep(.el-form-item) { margin-bottom: 0; }
|
||||
:deep(.analyze-table .el-table__cell) { white-space: nowrap; }
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.analyze-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
height: auto;
|
||||
min-height: 70px;
|
||||
padding: 14px 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-container {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.analyze-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.stat-group {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stat-box .value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.sparkline-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.pure-form .el-form-item) {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:deep(.pure-form .el-form-item__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.pure-form .el-date-editor.el-input),
|
||||
:deep(.pure-form .el-button) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.kline-dialog .el-dialog) {
|
||||
border-radius: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user