rebuild(stage-13): redesign mobile information architecture

This commit is contained in:
leefer
2026-07-30 08:02:47 +08:00
parent 4125ae2740
commit bec0878494
16 changed files with 449 additions and 42 deletions
+4 -1
View File
@@ -8,6 +8,7 @@ import { useMarketStore } from "../../shared/stores/market";
import DesktopSidebar from "./DesktopSidebar.vue";
import MarketStrip from "./MarketStrip.vue";
import MobileNav from "./MobileNav.vue";
import MobileWorkspaceSwitch from "./MobileWorkspaceSwitch.vue";
import StatusBar from "./StatusBar.vue";
import TopBar from "./TopBar.vue";
@@ -33,11 +34,13 @@ onBeforeUnmount(() => window.removeEventListener("keydown", globalShortcut));
<template>
<div class="app-shell">
<a class="skip-link" href="#main-content">跳到主要内容</a>
<DesktopSidebar />
<div class="shell-main">
<TopBar />
<MarketStrip />
<RouterView />
<MobileWorkspaceSwitch />
<div id="main-content" class="shell-content" tabindex="-1"><RouterView /></div>
</div>
<StatusBar />
<MobileNav />
+16 -5
View File
@@ -1,9 +1,20 @@
<script setup lang="ts">
import { computed } from "vue";
import { useRoute } from "vue-router";
import { findWorkspace } from "../workspaceRegistry";
const route = useRoute();
const current = computed(() => String(route.params.workspace ?? ""));
const marketActive = computed(() => findWorkspace(current.value)?.group === "市场复盘");
</script>
<template>
<nav class="mobile-nav" aria-label="移动端主导航">
<RouterLink class="mobile-nav-item" to="/workspace/emotion">行情</RouterLink>
<RouterLink class="mobile-nav-item" to="/workspace/screener">选股</RouterLink>
<RouterLink class="mobile-nav-item" to="/workspace/mentor">问师</RouterLink>
<RouterLink class="mobile-nav-item" to="/workspace/heaven">问天</RouterLink>
<RouterLink class="mobile-nav-item" to="/workspace/review">复盘</RouterLink>
<RouterLink class="mobile-nav-item" :class="{ active: marketActive }" :aria-current="marketActive ? 'page' : undefined" to="/workspace/emotion">行情</RouterLink>
<RouterLink class="mobile-nav-item" :class="{ active: current === 'screener' }" to="/workspace/screener">选股</RouterLink>
<RouterLink class="mobile-nav-item" :class="{ active: current === 'mentor' }" to="/workspace/mentor">问师</RouterLink>
<RouterLink class="mobile-nav-item" :class="{ active: current === 'heaven' }" to="/workspace/heaven">问天</RouterLink>
<RouterLink class="mobile-nav-item" :class="{ active: current === 'review' }" to="/workspace/review">复盘</RouterLink>
</nav>
</template>
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { findWorkspace, workspaces } from "../workspaceRegistry";
import TradingDateControl from "./TradingDateControl.vue";
const route = useRoute();
const router = useRouter();
const current = computed(() => String(route.params.workspace ?? ""));
const marketWorkspaces = workspaces.filter((item) => item.group === "市场复盘");
const visible = computed(() => findWorkspace(current.value)?.group === "市场复盘");
function change(event: Event): void {
void router.push(`/workspace/${(event.target as HTMLSelectElement).value}`);
}
</script>
<template>
<div class="mobile-context-bar">
<label v-if="visible" class="mobile-workspace-switch">
<span>行情页面</span>
<select :value="current" aria-label="切换行情页面" @change="change">
<option v-for="workspace in marketWorkspaces" :key="workspace.key" :value="workspace.key">
{{ workspace.title }} · {{ workspace.description }}
</option>
</select>
</label>
<TradingDateControl class="mobile-date-control" />
</div>
</template>
+2 -21
View File
@@ -8,6 +8,7 @@ import { useMarketStore } from "../../shared/stores/market";
import { useSessionStore } from "../../shared/stores/session";
import { useUiStore } from "../../shared/stores/ui";
import { findWorkspace } from "../workspaceRegistry";
import TradingDateControl from "./TradingDateControl.vue";
const route = useRoute();
const router = useRouter();
@@ -32,22 +33,6 @@ function openAccountDialog(name: "profile" | "membership" | "password"): void {
ui.openDialog(name);
}
function changeDate(event: Event): void {
const input = event.target as HTMLInputElement;
const value = input.value.trim();
const parsed = new Date(`${value}T12:00:00+08:00`);
if (
/^\d{4}-\d{2}-\d{2}$/.test(value) &&
!Number.isNaN(parsed.valueOf()) &&
parsed.toISOString().slice(0, 10) === value
) {
void market.selectDate(value);
return;
}
input.value = market.selectedDate;
ui.showToast("请输入 YYYY-MM-DD 格式的日期");
}
async function endSession(action: "logout" | "switch-account"): Promise<void> {
menuOpen.value = false;
try {
@@ -84,11 +69,7 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", closeOnOutside))
<header class="topbar">
<span class="topbar-title">{{ title }}</span>
<div class="topbar-spacer"></div>
<div class="date-control desktop-only">
<button class="icon-button" type="button" aria-label="前一日" title="前一日" @click="market.moveDate(-1)"></button>
<input :value="market.selectedDate" class="date-input" type="text" inputmode="numeric" pattern="\d{4}-\d{2}-\d{2}" maxlength="10" aria-label="交易日期" @change="changeDate" />
<button class="icon-button" type="button" aria-label="后一日" title="后一日" @click="market.moveDate(1)"></button>
</div>
<TradingDateControl class="desktop-only" />
<button class="icon-button" type="button" aria-label="全局搜索" title="全局搜索(Ctrl+K" @click="ui.openDialog('search')"></button>
<button class="icon-button topbar-alert desktop-only" type="button" aria-label="提醒中心" title="提醒中心" @click="ui.openDialog('alerts')">!<span v-if="ui.alertUnread" class="alert-badge">{{ ui.alertUnread > 99 ? '99+' : ui.alertUnread }}</span></button>
<button class="icon-button desktop-only" type="button" aria-label="复盘助手" title="复盘助手" @click="ui.openDialog('assistant')"></button>
@@ -0,0 +1,27 @@
<script setup lang="ts">
import { useMarketStore } from "../../shared/stores/market";
import { useUiStore } from "../../shared/stores/ui";
const market = useMarketStore();
const ui = useUiStore();
function change(event: Event): void {
const input = event.target as HTMLInputElement;
const value = input.value.trim();
const parsed = new Date(`${value}T12:00:00+08:00`);
if (/^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === value) {
void market.selectDate(value);
return;
}
input.value = market.selectedDate;
ui.showToast("请输入 YYYY-MM-DD 格式的日期");
}
</script>
<template>
<div class="date-control" aria-label="交易日期控制">
<button class="icon-button" type="button" aria-label="前一日" title="前一日" @click="market.moveDate(-1)"></button>
<input :value="market.selectedDate" class="date-input" type="text" inputmode="numeric" pattern="\d{4}-\d{2}-\d{2}" maxlength="10" aria-label="交易日期" @change="change" />
<button class="icon-button" type="button" aria-label="后一日" title="后一日" @click="market.moveDate(1)"></button>
</div>
</template>