diff --git a/web/frontend/src/App.vue b/web/frontend/src/App.vue index 1e4e5fb..9a92b7c 100644 --- a/web/frontend/src/App.vue +++ b/web/frontend/src/App.vue @@ -1,13 +1,17 @@ - + + 期货报告 用户管理 + + + + + + 期货报告 + + 打分列表 + K 线 / 持仓 + 手动打分 + 用户管理 + + + + - - {{ auth.user?.username }} - - {{ auth.isAdmin ? '管理员' : '普通用户' }} - + + + ☰ + + + {{ auth.user?.username }} + + {{ auth.isAdmin ? '管理员' : '普通用户' }} + + diff --git a/web/frontend/src/components/KLineChart.vue b/web/frontend/src/components/KLineChart.vue index 72cfe9a..7469461 100644 --- a/web/frontend/src/components/KLineChart.vue +++ b/web/frontend/src/components/KLineChart.vue @@ -3,9 +3,11 @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue' import * as echarts from 'echarts' import type { Candle } from '@/api/candles' import { useThemeStore } from '@/stores/theme' +import { useMobile } from '@/composables/useMobile' const props = defineProps<{ data: Candle[] }>() const theme = useThemeStore() +const { isMobile } = useMobile() const containerRef = ref(null) let chart: echarts.ECharts | null = null @@ -32,8 +34,8 @@ function render() { tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } }, legend: { data: ['K 线', '持仓量'], top: 0 }, grid: [ - { left: 60, right: 40, top: 40, height: '60%' }, - { left: 60, right: 40, top: '78%', height: '18%' }, + { left: isMobile.value ? 48 : 60, right: isMobile.value ? 12 : 40, top: 40, height: '60%' }, + { left: isMobile.value ? 48 : 60, right: isMobile.value ? 12 : 40, top: '78%', height: '18%' }, ], xAxis: [ { type: 'category', data: dates, scale: true, boundaryGap: false }, @@ -100,6 +102,10 @@ watch( render() }, ) +watch(isMobile, () => { + ensureChart() + render() +}) @@ -111,4 +117,9 @@ watch( width: 100%; height: 560px; } +@media (max-width: 768px) { + .chart { + height: 420px; + } +} diff --git a/web/frontend/src/components/ScoreDetailDrawer.vue b/web/frontend/src/components/ScoreDetailDrawer.vue index cc58ab2..72bb573 100644 --- a/web/frontend/src/components/ScoreDetailDrawer.vue +++ b/web/frontend/src/components/ScoreDetailDrawer.vue @@ -2,6 +2,9 @@ import { computed, ref, watch } from 'vue' import { getScore, type Score } from '@/api/scores' import { parseTsCode } from '@/utils/contract' +import { useMobile } from '@/composables/useMobile' + +const { isMobile } = useMobile() const props = defineProps<{ scoreId: number | null }>() const emit = defineEmits<{ (e: 'close'): void }>() @@ -34,9 +37,9 @@ watch( - + - + {{ parseTsCode(score.ts_code).symbol }} {{ parseTsCode(score.ts_code).contract }} {{ score.trade_date }} @@ -50,23 +53,25 @@ watch( {{ score.short_term.toFixed(2) }} {{ score.medium_term.toFixed(2) }} - + {{ score.long_term.toFixed(2) }} 短期 7 日逐日打分 - - - - - - - - + + + + + + + + + + 中期(15d)细节 - + {{ (score.detail.medium_detail.price_return_pct * 100).toFixed(2) }}% @@ -79,20 +84,20 @@ watch( {{ score.detail.medium_detail.long_down_days }} - + {{ score.detail.medium_detail.fund_signal }} 长期(30d)细节 - + {{ score.detail.long_detail.avg_oi.toFixed(0) }} {{ score.detail.long_detail.oi_before.toFixed(0) }} - + {{ (score.detail.long_detail.change_pct * 100).toFixed(2) }}% @@ -104,4 +109,10 @@ watch( .section { margin: 18px 0 8px; } +.table-wrapper { + overflow-x: auto; +} +.detail-table { + min-width: 520px; +} diff --git a/web/frontend/src/composables/useMobile.ts b/web/frontend/src/composables/useMobile.ts new file mode 100644 index 0000000..a2cf442 --- /dev/null +++ b/web/frontend/src/composables/useMobile.ts @@ -0,0 +1,26 @@ +import { ref, onMounted, onUnmounted } from 'vue' + +const isMobile = ref(false) + +function check() { + isMobile.value = window.matchMedia('(max-width: 768px)').matches +} + +let listeners = 0 + +export function useMobile() { + onMounted(() => { + if (listeners === 0) { + check() + window.addEventListener('resize', check) + } + listeners++ + }) + onUnmounted(() => { + listeners-- + if (listeners === 0) { + window.removeEventListener('resize', check) + } + }) + return { isMobile } +} diff --git a/web/frontend/src/views/AdminUsersView.vue b/web/frontend/src/views/AdminUsersView.vue index 57ad2cb..7a277d1 100644 --- a/web/frontend/src/views/AdminUsersView.vue +++ b/web/frontend/src/views/AdminUsersView.vue @@ -103,45 +103,47 @@ onMounted(reload) - - - - - - {{ row.role }} - - - - - - {{ row.disabled ? '已禁用' : '正常' }} - - - - - - - - 重置密码 - - {{ row.disabled ? '启用' : '禁用' }} - - - 删除 - - - - + + + + + + + {{ row.role }} + + + + + + {{ row.disabled ? '已禁用' : '正常' }} + + + + + + + + 重置密码 + + {{ row.disabled ? '启用' : '禁用' }} + + + 删除 + + + + + @@ -195,4 +197,19 @@ onMounted(reload) align-items: center; color: var(--el-text-color-regular); } +.table-wrapper { + background: var(--el-bg-color); + border-radius: 4px; + overflow-x: auto; +} +.user-table { + min-width: 960px; +} +@media (max-width: 768px) { + .head { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } +} diff --git a/web/frontend/src/views/ChangePasswordView.vue b/web/frontend/src/views/ChangePasswordView.vue index d29b199..f138117 100644 --- a/web/frontend/src/views/ChangePasswordView.vue +++ b/web/frontend/src/views/ChangePasswordView.vue @@ -87,9 +87,12 @@ async function submit() { align-items: center; justify-content: center; background: linear-gradient(135deg, #1f2d3d 0%, #3a506b 100%); + overflow: hidden; + padding: 16px; } .card { width: 360px; + max-width: 100%; padding: 36px 32px; background: var(--el-bg-color); color: var(--el-text-color-primary); @@ -106,4 +109,9 @@ async function submit() { font-size: 12px; text-align: center; } +@media (max-width: 768px) { + .card { + padding: 28px 20px; + } +} diff --git a/web/frontend/src/views/ChartView.vue b/web/frontend/src/views/ChartView.vue index 4f9b05c..0fc766b 100644 --- a/web/frontend/src/views/ChartView.vue +++ b/web/frontend/src/views/ChartView.vue @@ -4,6 +4,9 @@ import { ElMessage } from 'element-plus' import { listContracts } from '@/api/scores' import { listCandles, type Candle } from '@/api/candles' import KLineChart from '@/components/KLineChart.vue' +import { useMobile } from '@/composables/useMobile' + +const { isMobile } = useMobile() const filter = reactive<{ ts_code: string; range: [string, string] | [] }>({ ts_code: '', @@ -40,13 +43,13 @@ onMounted(async () => { - + @@ -59,6 +62,7 @@ onMounted(async () => { range-separator="→" start-placeholder="起" end-placeholder="止" + :style="{ width: isMobile ? '100%' : 'auto' }" /> diff --git a/web/frontend/src/views/LoginView.vue b/web/frontend/src/views/LoginView.vue index 3dd113b..69a2b5c 100644 --- a/web/frontend/src/views/LoginView.vue +++ b/web/frontend/src/views/LoginView.vue @@ -70,9 +70,11 @@ async function submit() { justify-content: center; background: linear-gradient(135deg, #1f2d3d 0%, #3a506b 100%); overflow: hidden; + padding: 16px; } .card { width: 360px; + max-width: 100%; padding: 36px 32px; background: var(--el-bg-color); color: var(--el-text-color-primary); @@ -89,4 +91,9 @@ async function submit() { font-size: 12px; text-align: center; } +@media (max-width: 768px) { + .card { + padding: 28px 20px; + } +} diff --git a/web/frontend/src/views/RunView.vue b/web/frontend/src/views/RunView.vue index f73b860..09dc96b 100644 --- a/web/frontend/src/views/RunView.vue +++ b/web/frontend/src/views/RunView.vue @@ -8,6 +8,9 @@ import { type RunResponse, } from '@/api/run' import { parseTsCode } from '@/utils/contract' +import { useMobile } from '@/composables/useMobile' + +const { isMobile } = useMobile() const SYMBOLS = ['FG', 'SA', 'RB', 'MA', 'CF', 'M'] @@ -139,7 +142,7 @@ onMounted(loadActive) 打分结果 - + {{ parseTsCode(result.ts_code).symbol }} {{ parseTsCode(result.ts_code).contract }} {{ result.trade_date }} @@ -151,7 +154,7 @@ onMounted(loadActive) {{ result.composite }} - + {{ result.signal }} diff --git a/web/frontend/src/views/ScoresView.vue b/web/frontend/src/views/ScoresView.vue index ded1927..2b51092 100644 --- a/web/frontend/src/views/ScoresView.vue +++ b/web/frontend/src/views/ScoresView.vue @@ -3,6 +3,9 @@ import { onMounted, reactive, ref } from 'vue' import { listContracts, listScores, type Score } from '@/api/scores' import ScoreDetailDrawer from '@/components/ScoreDetailDrawer.vue' import { parseTsCode } from '@/utils/contract' +import { useMobile } from '@/composables/useMobile' + +const { isMobile } = useMobile() const filter = reactive<{ ts_code?: string @@ -59,14 +62,14 @@ onMounted(async () => { - + @@ -79,6 +82,7 @@ onMounted(async () => { range-separator="→" start-placeholder="起" end-placeholder="止" + :style="{ width: isMobile ? '100%' : 'auto' }" /> @@ -87,8 +91,8 @@ onMounted(async () => { 查询 - - + + { - - - - - {{ parseTsCode(row.ts_code).symbol }} - - - - - {{ parseTsCode(row.ts_code).contract }} - - - - - - - - - - - {{ row.composite.toFixed(2) }} - - - - - {{ row.signal }} - - - - - 明细 - - - + + + + + + {{ parseTsCode(row.ts_code).symbol }} + + + + + {{ parseTsCode(row.ts_code).contract }} + + + + + + + + + + + {{ row.composite.toFixed(2) }} + + + + + {{ row.signal }} + + + + + 明细 + + + + { outline: none; box-shadow: none; } -.score-table { +.table-wrapper { background: var(--el-bg-color); + border-radius: 4px; + overflow-x: auto; +} +.score-table { + min-width: 960px; +} + +@media (max-width: 768px) { + .signal-item { + flex-wrap: wrap; + } + .signal-group { + flex-wrap: wrap; + } }