refactor: organize frontend application structure
This commit is contained in:
parent
4acad681e1
commit
4ccd97254a
@ -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
7
client/src/api/client.ts
Normal 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
1
client/src/api/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { apiBaseUrl, request } from './client'
|
||||
9
client/src/composables/useResponsive.ts
Normal file
9
client/src/composables/useResponsive.ts
Normal 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) }
|
||||
}
|
||||
9
client/src/layouts/AppNavigation.vue
Normal file
9
client/src/layouts/AppNavigation.vue
Normal 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>
|
||||
9
client/src/layouts/DesktopLayout.vue
Normal file
9
client/src/layouts/DesktopLayout.vue
Normal 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>
|
||||
8
client/src/layouts/MobileLayout.vue
Normal file
8
client/src/layouts/MobileLayout.vue
Normal 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>
|
||||
8
client/src/router/index.ts
Normal file
8
client/src/router/index.ts
Normal 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
9
client/src/stores/app.ts
Normal 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 }
|
||||
}
|
||||
})
|
||||
5
client/src/stores/practice.ts
Normal file
5
client/src/stores/practice.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const usePracticeStore = defineStore('practice', {
|
||||
state: () => ({ sessionId: null as string | null, currentIndex: 0 })
|
||||
})
|
||||
5
client/src/stores/profile.ts
Normal file
5
client/src/stores/profile.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useProfileStore = defineStore('profile', {
|
||||
state: () => ({ nickname: '备考人', targetScore: 72, examDate: '' })
|
||||
})
|
||||
1
client/src/types/view-models.ts
Normal file
1
client/src/types/view-models.ts
Normal file
@ -0,0 +1 @@
|
||||
export type NavigationItem = { label: string; index: number }
|
||||
1
client/src/utils/format.ts
Normal file
1
client/src/utils/format.ts
Normal file
@ -0,0 +1 @@
|
||||
export const formatPercent = (value: number) => `${Math.round(value)}%`
|
||||
3
client/src/views/dashboard/DashboardView.vue
Normal file
3
client/src/views/dashboard/DashboardView.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<section class="welcome"><h2>备考通已就绪</h2><p>前端工程架构已启动,后续任务将逐页还原原型并接入真实数据。</p></section>
|
||||
</template>
|
||||
Loading…
x
Reference in New Issue
Block a user