rebuild(stage-13): redesign mobile information architecture
This commit is contained in:
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user