chore: initialize frontend and backend workspace

This commit is contained in:
liyy 2026-08-28 08:46:31 +08:00
parent 86c906727b
commit ca9ce54d13
15 changed files with 2051 additions and 1 deletions

9
.gitignore vendored
View File

@ -1 +1,8 @@
.DS_store node_modules/
.DS_Store
dist/
.env
.env.*
!.env.example
client/src/api/generated/
server/data/*.tmp

2
client/index.html Normal file
View File

@ -0,0 +1,2 @@
<!doctype html>
<html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>备考通</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body></html>

21
client/package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "@gwy/client",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"@vitejs/plugin-vue": "latest",
"vite": "latest",
"vue": "latest",
"vue-router": "latest",
"pinia": "latest"
},
"devDependencies": {
"typescript": "^5.7.3",
"vue-tsc": "latest"
}
}

20
client/src/App.vue Normal file
View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
const nav = ['数据中枢', '刷题中心', '模考分析', '备考计划', '要闻', '我的']
const active = ref(0)
const title = computed(() => nav[active.value])
</script>
<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>
</div>
</template>

6
client/src/main.ts Normal file
View File

@ -0,0 +1,6 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import './styles/main.css'
createApp(App).use(createPinia()).mount('#app')

View File

@ -0,0 +1 @@
:root{font-family:Arial,"PingFang SC",sans-serif;color:#1a1f2e;background:#f7f6f2;font-synthesis:none}*{box-sizing:border-box}body{margin:0}.app-shell{min-height:100vh}.sidebar{position:fixed;inset:0 auto 0 0;width:220px;padding:16px 10px;background:#fff;border-right:1px solid #e8e6e0;display:flex;flex-direction:column;gap:7px}.brand{height:36px;display:flex;align-items:center;gap:9px}.brand span{display:grid;place-items:center;width:28px;height:28px;background:#2b4c8f;border-radius:8px;color:white}.nav-item{border:0;background:transparent;color:#6b7280;height:40px;border-radius:10px;text-align:left;padding:0 12px;cursor:pointer;font-size:13px}.nav-item.active{background:#2b4c8f;color:#fff}.sidebar-spacer{flex:1}.content{margin-left:220px;padding:32px;max-width:1250px}header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px}h1{font-size:24px;margin:0 0 5px}h2{margin:0 0 8px}p{font-size:13px;color:#6b7280;margin:0;line-height:1.5}.primary{height:40px;border:0;border-radius:10px;padding:0 16px;background:#2b4c8f;color:#fff;font-weight:600;cursor:pointer}.welcome{background:#fff;border:1px solid #e8e6e0;border-radius:16px;padding:24px}.mobile-tabs{display:none}@media(max-width:767px){.sidebar{display:none}.content{margin:0;padding:28px 24px 110px}.content header{margin-bottom:24px}.content h1{font-size:22px}.mobile-tabs{position:fixed;display:flex;z-index:2;left:16px;right:16px;bottom:16px;height:70px;background:#fff;border:1px solid #e8e6e0;border-radius:30px;box-shadow:0 4px 12px rgba(27,45,91,.08);justify-content:space-around;padding:8px}.mobile-tabs button{border:0;background:transparent;color:#9ca2ad;font-size:10px;display:flex;flex-direction:column;align-items:center;gap:4px;border-radius:24px;padding:7px 9px}.mobile-tabs button.active{background:#2b4c8f;color:#fff}.mobile-tabs span{font-size:17px}.primary{height:36px;padding:0 11px;font-size:12px}}

4
client/tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
"compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "Bundler", "strict": true, "jsx": "preserve", "skipLibCheck": true, "allowImportingTsExtensions": true, "isolatedModules": true, "noEmit": true, "types": ["vite/client"] },
"include": ["src/**/*.ts", "src/**/*.vue"]
}

7
client/vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: { port: 5173 }
})

22
docs/checks/task-01.md Normal file
View File

@ -0,0 +1,22 @@
# 任务 01 检查记录:项目初始化与目录确定
日期2026-08-28
## 命令检查
- `pnpm install`:通过
- `pnpm typecheck`通过server、client
- `pnpm build`通过server、client
- `GET /health`:通过,返回 `{"status":"ok","service":"gwy-server"}`
- 前端开发服务可启动Vite 返回 HTTP 200
## 浏览器检查
- 已启动 Vite 与 Fastify 服务。
- 按计划尝试使用内置浏览器打开前端并获取 DOM/截图。
- 当前运行环境的内置浏览器无法访问本地开发端口,返回 `ERR_CONNECTION_REFUSED`;终端网络环境可以访问同一端口。
- 因此本项的浏览器实际操作和原型截图对比暂记为“环境阻塞”,不宣称通过;待浏览器可访问本地服务后补检。
## 当前结论
工程骨架、依赖安装、类型检查、生产构建和服务健康检查均通过;浏览器检查未通过,后续任务继续前需补齐该项。

11
package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "gwy-exam",
"private": true,
"packageManager": "pnpm@9.1.2",
"scripts": {
"dev": "pnpm --filter @gwy/client dev",
"dev:server": "pnpm --filter @gwy/server dev",
"build": "pnpm -r build",
"typecheck": "pnpm -r typecheck"
}
}

1930
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,3 @@
packages:
- client
- server

8
server/package.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "@gwy/server",
"private": true,
"type": "module",
"scripts": { "dev": "tsx watch src/server.ts", "build": "tsc", "typecheck": "tsc --noEmit" },
"dependencies": { "@fastify/cors": "latest", "fastify": "latest", "zod": "latest" },
"devDependencies": { "tsx": "latest", "typescript": "latest", "@types/node": "latest" }
}

7
server/src/server.ts Normal file
View File

@ -0,0 +1,7 @@
import Fastify from 'fastify'
import cors from '@fastify/cors'
const app = Fastify({ logger: true })
await app.register(cors, { origin: true })
app.get('/health', async () => ({ status: 'ok', service: 'gwy-server' }))
app.listen({ port: Number(process.env.PORT ?? 3000), host: '127.0.0.1' })

1
server/tsconfig.json Normal file
View File

@ -0,0 +1 @@
{"compilerOptions":{"target":"ES2022","module":"NodeNext","moduleResolution":"NodeNext","strict":true,"esModuleInterop":true,"skipLibCheck":true,"rootDir":"src","outDir":"dist"},"include":["src/**/*.ts"]}