refactor: organize frontend application structure

This commit is contained in:
liyy 2026-08-28 09:42:26 +08:00
parent 4acad681e1
commit 4ccd97254a
14 changed files with 81 additions and 8 deletions

View File

@ -1,5 +1,8 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import DesktopLayout from './layouts/DesktopLayout.vue'
import MobileLayout from './layouts/MobileLayout.vue'
import DashboardView from './views/dashboard/DashboardView.vue'
const nav = ['数据中枢', '刷题中心', '模考分析', '备考计划', '要闻', '我的']
const active = ref(0)
@ -8,13 +11,8 @@ const title = computed(() => nav[active.value])
<template>
<div class="app-shell">
<aside class="sidebar">
<div class="brand"><span></span><strong>备考中枢</strong></div>
<button v-for="(item, index) in nav" :key="item" :class="['nav-item', { active: active === index }]" @click="active = index">{{ item }}</button>
<div class="sidebar-spacer" />
<button class="nav-item">题库管理</button><button class="nav-item">设置</button>
</aside>
<main class="content"><header><div><h1>{{ title }}</h1><p>个人备考工作台 · 页面骨架</p></div><button class="primary">开始使用</button></header><section class="welcome"><h2>备考通已就绪</h2><p>前端工程骨架已启动后续任务将逐页还原原型并接入真实数据</p></section></main>
<nav class="mobile-tabs"> <button v-for="(item, index) in nav" :key="item" :class="{ active: active === index }" @click="active = index"><span>{{ ['⌂','▣','▥','▤','▤','●'][index] }}</span>{{ item }}</button></nav>
<DesktopLayout :items="nav" :active="active" @select="active = $event" />
<main class="content"><header><div><h1>{{ title }}</h1><p>个人备考工作台 · 页面骨架</p></div><button class="primary">开始使用</button></header><DashboardView /></main>
<MobileLayout :items="nav" :active="active" @select="active = $event" />
</div>
</template>

7
client/src/api/client.ts Normal file
View File

@ -0,0 +1,7 @@
export const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000/api'
export async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${apiBaseUrl}${path}`, { headers: { 'content-type': 'application/json' }, ...init })
if (!response.ok) throw new Error(`请求失败:${response.status}`)
return response.json() as Promise<T>
}

1
client/src/api/index.ts Normal file
View File

@ -0,0 +1 @@
export { apiBaseUrl, request } from './client'

View File

@ -0,0 +1,9 @@
import { computed, onMounted, onUnmounted, ref } from 'vue'
export function useResponsive() {
const width = ref(typeof window === 'undefined' ? 1440 : window.innerWidth)
const update = () => { width.value = window.innerWidth }
onMounted(() => window.addEventListener('resize', update))
onUnmounted(() => window.removeEventListener('resize', update))
return { width, isMobile: computed(() => width.value < 768) }
}

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
defineProps<{ items: string[]; active: number }>()
const emit = defineEmits<{ select: [index: number] }>()
</script>
<template>
<div class="brand"><span></span><strong>备考中枢</strong></div>
<button v-for="(item, index) in items" :key="item" :class="['nav-item', { active: active === index }]" @click="emit('select', index)">{{ item }}</button>
</template>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import AppNavigation from './AppNavigation.vue'
defineProps<{ items: string[]; active: number }>()
const emit = defineEmits<{ select: [index: number] }>()
</script>
<template>
<aside class="sidebar"><AppNavigation :items="items" :active="active" @select="emit('select', $event)" /><div class="sidebar-spacer" /><button class="nav-item">题库管理</button><button class="nav-item">设置</button></aside>
</template>

View File

@ -0,0 +1,8 @@
<script setup lang="ts">
defineProps<{ items: string[]; active: number }>()
const emit = defineEmits<{ select: [index: number] }>()
</script>
<template>
<nav class="mobile-tabs"><button v-for="(item, index) in items" :key="item" :class="{ active: active === index }" @click="emit('select', index)"><span>{{ ['⌂','▣','▥','▤','▤','●'][index] }}</span>{{ item }}</button></nav>
</template>

View File

@ -0,0 +1,8 @@
export const routes = {
dashboard: '/',
practice: '/practice',
analysis: '/analysis',
plan: '/plan',
news: '/news',
profile: '/profile'
} as const

9
client/src/stores/app.ts Normal file
View File

@ -0,0 +1,9 @@
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({ busy: false, notice: '' }),
actions: {
setBusy(value: boolean) { this.busy = value },
notify(message: string) { this.notice = message }
}
})

View File

@ -0,0 +1,5 @@
import { defineStore } from 'pinia'
export const usePracticeStore = defineStore('practice', {
state: () => ({ sessionId: null as string | null, currentIndex: 0 })
})

View File

@ -0,0 +1,5 @@
import { defineStore } from 'pinia'
export const useProfileStore = defineStore('profile', {
state: () => ({ nickname: '备考人', targetScore: 72, examDate: '' })
})

View File

@ -0,0 +1 @@
export type NavigationItem = { label: string; index: number }

View File

@ -0,0 +1 @@
export const formatPercent = (value: number) => `${Math.round(value)}%`

View File

@ -0,0 +1,3 @@
<template>
<section class="welcome"><h2>备考通已就绪</h2><p>前端工程架构已启动后续任务将逐页还原原型并接入真实数据</p></section>
</template>