commit 5721ca7dd20ef0a3580e04859427dc4b0a39bd5c Author: KiriAky 107 Date: Thu Aug 27 11:19:36 2026 +0800 初始化项目基础结构 添加了完整的前后端开发环境配置,包括: - 创建 .gitignore 文件忽略本地生成目录和环境文件 - 添加详细的 README.md 开发指南文档 - 配置 backend 目录结构和 FastAPI 应用基础框架 - 实现应用配置管理、健康检查和状态接口 - 设置 uv 依赖管理和虚拟环境配置 - 完成 CORS 中间件配置支持前端开发联调 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee18178 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Frontend +frontend/node_modules/ +frontend/dist/ +frontend/*.tsbuildinfo +.pnpm-store/ + +# Backend +backend/.venv/ +backend/.uv-cache/ +backend/.pytest_cache/ +backend/**/__pycache__/ +backend/.env + +# Editors and operating systems +.idea/ +.vscode/ +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..7be6e32 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# Notes Agent(暂命名) 团队开发说明 + +> 本文件用于团队开发期间快速配置环境和启动项目,不是正式的项目 README。 + +## 当前目录 + +```text +NotesAgent/ +├── frontend/ Vue 3 + TypeScript + Vite 前端 +├── backend/ FastAPI + Pydantic 后端 +├── docs/ 分工与技术栈说明 +└── server sync/ 云同步服务预留目录,当前未实现 +``` + +## 开发环境 + +当前前后端壳子需要: + +| 环境 | 要求 | 说明 | +| --- | --- | --- | +| Git | 较新稳定版 | 代码版本管理 | +| Node.js | 22 或更高版本 | 推荐使用 Node.js 24 | +| pnpm | 10 或更高版本 | 前端依赖与脚本管理 | +| Python | 3.11 或更高版本 | 推荐使用 Python 3.12 | +| uv | 较新稳定版 | 后端依赖和虚拟环境管理 | + +检查本机环境: + +```powershell +git --version +node --version +pnpm --version +python --version +uv --version +``` + +当前壳子暂不需要 Rust 和 Tauri。开始桌面端集成后,再按照 `docs/AI笔记软件技术栈说明-团队版-v2.2.md` 安装 Rust Toolchain 与 Tauri CLI。 + +## 首次初始化 + +### 后端 + +```powershell +cd backend +uv sync +cd .. +``` + +`uv sync` 会根据 `backend/pyproject.toml` 安装依赖,并自动创建和管理 `backend/.venv`,不需要手动创建或激活虚拟环境。 + +### 前端 + +```powershell +cd frontend +pnpm install +cd .. +``` + +## 启动开发环境 + +前端和后端需要在两个终端中分别启动。 + +### 终端一:启动后端 + +```powershell +cd backend +uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000 +``` + +后端地址: + +- 健康检查: +- 服务状态: +- API 文档: +- OpenAPI JSON: + +### 终端二:启动前端 + +```powershell +cd frontend +pnpm dev +``` + +前端地址: + +开发环境中,Vite 会将 `/api` 和 `/health` 请求代理到 `http://127.0.0.1:8000`。联调时应先启动后端,再启动或刷新前端。 + +## 测试与构建 + +后端测试: + +```powershell +cd backend +uv run pytest +``` + +前端类型检查及生产构建: + +```powershell +cd frontend +pnpm build +``` + +构建产物位于 `frontend/dist`,该目录不提交到 Git。 + +## 日常开发注意事项 + +- Python 依赖统一修改 `backend/pyproject.toml`,修改后执行 `uv sync`。 +- 前端依赖统一使用 pnpm 安装,不要混用 npm 或 yarn。 +- `backend/.venv`、`frontend/node_modules`、`frontend/dist` 均为本地生成目录,不提交到 Git。 +- API 默认监听 `127.0.0.1:8000`,前端默认监听 `127.0.0.1:5173`。 +- 跨模块接口发生变化时,需要同步更新前后端类型和 `docs` 中的接口说明。 diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..c6d6f5e --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,5 @@ +APP_NAME=Notes Agent AI Core +APP_VERSION=0.1.0 +APP_ENVIRONMENT=development +APP_HOST=127.0.0.1 +APP_PORT=8000 diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..4ec4808 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,15 @@ +# Backend + +FastAPI + Pydantic 的最小后端壳子。项目使用 uv 管理依赖和虚拟环境。 + +```powershell +uv sync +uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000 +``` + +`uv sync` 首次运行时会自动创建由 uv 管理的 `.venv`,无需手动执行 `python -m venv` 或激活环境。 + +启动后可访问: + +- 健康检查: +- API 文档: diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..4a3b084 --- /dev/null +++ b/backend/app/__init__.py @@ -0,0 +1 @@ +"""Notes Agent AI Core.""" diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..dcc4054 --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,25 @@ +import os +from dataclasses import dataclass +from functools import lru_cache + + +@dataclass(frozen=True) +class Settings: + """应用基础配置;正式环境可通过 APP_* 环境变量覆盖。""" + + name: str + version: str + environment: str + host: str + port: int + + +@lru_cache +def get_settings() -> Settings: + return Settings( + name=os.getenv("APP_NAME", "Notes Agent AI Core"), + version=os.getenv("APP_VERSION", "0.1.0"), + environment=os.getenv("APP_ENVIRONMENT", "development"), + host=os.getenv("APP_HOST", "127.0.0.1"), + port=int(os.getenv("APP_PORT", "8000")), + ) diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..4b0aeae --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,35 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.config import get_settings +from app.schemas import HealthResponse, ServiceStatusResponse + +settings = get_settings() + +app = FastAPI( + title=settings.name, + version=settings.version, + description="AI 笔记软件的本地 FastAPI 服务壳子。", +) + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://127.0.0.1:5173", "http://localhost:5173"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/health", response_model=HealthResponse, tags=["System"]) +async def health() -> HealthResponse: + return HealthResponse() + + +@app.get("/api/status", response_model=ServiceStatusResponse, tags=["System"]) +async def service_status() -> ServiceStatusResponse: + return ServiceStatusResponse( + name=settings.name, + version=settings.version, + environment=settings.environment, + ) diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..2ae8dd3 --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,13 @@ +from typing import Literal + +from pydantic import BaseModel + + +class HealthResponse(BaseModel): + status: Literal["ok"] = "ok" + + +class ServiceStatusResponse(HealthResponse): + name: str + version: str + environment: str diff --git a/backend/notes_agent_backend.egg-info/PKG-INFO b/backend/notes_agent_backend.egg-info/PKG-INFO new file mode 100644 index 0000000..a54ff14 --- /dev/null +++ b/backend/notes_agent_backend.egg-info/PKG-INFO @@ -0,0 +1,22 @@ +Metadata-Version: 2.4 +Name: notes-agent-backend +Version: 0.1.0 +Summary: Notes Agent 的 FastAPI 基础壳子 +Requires-Python: >=3.11 +Description-Content-Type: text/markdown +Requires-Dist: fastapi<1.0,>=0.116 +Requires-Dist: uvicorn[standard]<1.0,>=0.35 + +# Backend + +FastAPI + Pydantic 的最小后端壳子。 + +```powershell +uv sync +uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000 +``` + +启动后可访问: + +- 健康检查: +- API 文档: diff --git a/backend/notes_agent_backend.egg-info/SOURCES.txt b/backend/notes_agent_backend.egg-info/SOURCES.txt new file mode 100644 index 0000000..853a10f --- /dev/null +++ b/backend/notes_agent_backend.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +README.md +pyproject.toml +app/__init__.py +app/config.py +app/main.py +app/schemas.py +notes_agent_backend.egg-info/PKG-INFO +notes_agent_backend.egg-info/SOURCES.txt +notes_agent_backend.egg-info/dependency_links.txt +notes_agent_backend.egg-info/requires.txt +notes_agent_backend.egg-info/top_level.txt +tests/test_api.py \ No newline at end of file diff --git a/backend/notes_agent_backend.egg-info/dependency_links.txt b/backend/notes_agent_backend.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/notes_agent_backend.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/backend/notes_agent_backend.egg-info/requires.txt b/backend/notes_agent_backend.egg-info/requires.txt new file mode 100644 index 0000000..11dbd01 --- /dev/null +++ b/backend/notes_agent_backend.egg-info/requires.txt @@ -0,0 +1,2 @@ +fastapi<1.0,>=0.116 +uvicorn[standard]<1.0,>=0.35 diff --git a/backend/notes_agent_backend.egg-info/top_level.txt b/backend/notes_agent_backend.egg-info/top_level.txt new file mode 100644 index 0000000..b80f0bd --- /dev/null +++ b/backend/notes_agent_backend.egg-info/top_level.txt @@ -0,0 +1 @@ +app diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..3551f6d --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "notes-agent-backend" +version = "0.1.0" +description = "Notes Agent 的 FastAPI 基础壳子" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "fastapi>=0.116,<1.0", + "uvicorn[standard]>=0.35,<1.0", +] + +[dependency-groups] +dev = [ + "pytest>=8.4,<9.0", +] + +[tool.pytest.ini_options] +pythonpath = ["."] +testpaths = ["tests"] diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py new file mode 100644 index 0000000..5638924 --- /dev/null +++ b/backend/tests/test_api.py @@ -0,0 +1,16 @@ +import asyncio + +from app.main import health, service_status + + +def test_health() -> None: + response = asyncio.run(health()) + + assert response.model_dump() == {"status": "ok"} + + +def test_service_status() -> None: + response = asyncio.run(service_status()) + + assert response.name == "Notes Agent AI Core" + assert response.status == "ok" diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..baa01e3 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Notes Agent + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..38b8605 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "notes-agent-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "latest" + }, + "devDependencies": { + "@types/node": "latest", + "@vitejs/plugin-vue": "latest", + "typescript": "~5.9.3", + "vite": "latest", + "vue-tsc": "latest" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..3240b3f --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,719 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + vue: + specifier: latest + version: 3.5.41(typescript@5.9.3) + devDependencies: + '@types/node': + specifier: latest + version: 26.3.0 + '@vitejs/plugin-vue': + specifier: latest + version: 6.0.8(vite@8.2.2(@types/node@26.3.0))(vue@3.5.41(typescript@5.9.3)) + typescript: + specifier: ~5.9.3 + version: 5.9.3 + vite: + specifier: latest + version: 8.2.2(@types/node@26.3.0) + vue-tsc: + specifier: latest + version: 3.3.11(typescript@5.9.3) + +packages: + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@oxc-project/types@0.146.0': + resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} + + '@rolldown/binding-android-arm-eabi@1.2.5': + resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.5': + resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.5': + resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.5': + resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.5': + resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': + resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.5': + resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.5': + resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.5': + resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.5': + resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.5': + resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.5': + resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.5': + resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.5': + resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.5': + resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@types/node@26.3.0': + resolution: {integrity: sha512-L3fgrnchriRC2ExBflb8j4uZZURHZfQsmQeyVzhjcHW4kkwVyo8/0h1B2MVzMTrYUJYu6G7EWs14hW/L9putqw==} + + '@vitejs/plugin-vue@6.0.8': + resolution: {integrity: sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/compiler-core@3.5.41': + resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==} + + '@vue/compiler-dom@3.5.41': + resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==} + + '@vue/compiler-sfc@3.5.41': + resolution: {integrity: sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==} + + '@vue/compiler-ssr@3.5.41': + resolution: {integrity: sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==} + + '@vue/language-core@3.3.11': + resolution: {integrity: sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==} + + '@vue/reactivity@3.5.41': + resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==} + + '@vue/runtime-core@3.5.41': + resolution: {integrity: sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==} + + '@vue/runtime-dom@3.5.41': + resolution: {integrity: sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==} + + '@vue/server-renderer@3.5.41': + resolution: {integrity: sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==} + + '@vue/shared@3.5.41': + resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==} + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + rolldown@1.2.5: + resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.2.0: + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + + vue-tsc@3.3.11: + resolution: {integrity: sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.41: + resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + +snapshots: + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@oxc-project/types@0.146.0': {} + + '@rolldown/binding-android-arm-eabi@1.2.5': + optional: true + + '@rolldown/binding-android-arm64@1.2.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.5': + optional: true + + '@rolldown/binding-darwin-x64@1.2.5': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.5': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.5': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@types/node@26.3.0': + dependencies: + undici-types: 8.3.0 + + '@vitejs/plugin-vue@6.0.8(vite@8.2.2(@types/node@26.3.0))(vue@3.5.41(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.2(@types/node@26.3.0) + vue: 3.5.41(typescript@5.9.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.2.0 + optionalDependencies: + typescript: 5.9.3 + + '@vue/compiler-core@3.5.41': + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.41 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.41': + dependencies: + '@vue/compiler-core': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/compiler-sfc@3.5.41': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.41 + '@vue/compiler-dom': 3.5.41 + '@vue/compiler-ssr': 3.5.41 + '@vue/shared': 3.5.41 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.26 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.41': + dependencies: + '@vue/compiler-dom': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/language-core@3.3.11': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.41 + '@vue/shared': 3.5.41 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.7 + + '@vue/reactivity@3.5.41': + dependencies: + '@vue/shared': 3.5.41 + + '@vue/runtime-core@3.5.41': + dependencies: + '@vue/reactivity': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/runtime-dom@3.5.41': + dependencies: + '@vue/reactivity': 3.5.41 + '@vue/runtime-core': 3.5.41 + '@vue/shared': 3.5.41 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.41': + dependencies: + '@vue/compiler-ssr': 3.5.41 + '@vue/runtime-dom': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/shared@3.5.41': {} + + alien-signals@3.2.1: {} + + csstype@3.2.3: {} + + detect-libc@2.1.2: {} + + entities@7.0.1: {} + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + muggle-string@0.4.1: {} + + nanoid@3.3.18: {} + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rolldown@1.2.5: + dependencies: + '@oxc-project/types': 0.146.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.5 + '@rolldown/binding-android-arm64': 1.2.5 + '@rolldown/binding-darwin-arm64': 1.2.5 + '@rolldown/binding-darwin-x64': 1.2.5 + '@rolldown/binding-freebsd-x64': 1.2.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.5 + '@rolldown/binding-linux-arm64-gnu': 1.2.5 + '@rolldown/binding-linux-arm64-musl': 1.2.5 + '@rolldown/binding-linux-ppc64-gnu': 1.2.5 + '@rolldown/binding-linux-s390x-gnu': 1.2.5 + '@rolldown/binding-linux-x64-gnu': 1.2.5 + '@rolldown/binding-linux-x64-musl': 1.2.5 + '@rolldown/binding-openharmony-arm64': 1.2.5 + '@rolldown/binding-win32-arm64-msvc': 1.2.5 + '@rolldown/binding-win32-x64-msvc': 1.2.5 + + source-map-js@1.2.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + typescript@5.9.3: {} + + undici-types@8.3.0: {} + + vite@8.2.2(@types/node@26.3.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.26 + rolldown: 1.2.5 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.3.0 + fsevents: 2.3.3 + + vscode-uri@3.2.0: {} + + vue-tsc@3.3.11(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.28(typescript@5.9.3) + '@vue/language-core': 3.3.11 + typescript: 5.9.3 + + vue@3.5.41(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.41 + '@vue/compiler-sfc': 3.5.41 + '@vue/runtime-dom': 3.5.41 + '@vue/server-renderer': 3.5.41 + '@vue/shared': 3.5.41 + optionalDependencies: + typescript: 5.9.3 diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..02cbee3 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,95 @@ + + + diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 0000000..ea07804 --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,16 @@ +export interface ServiceStatus { + name: string + version: string + environment: string + status: 'ok' +} + +const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? '' + +export async function getServiceStatus(): Promise { + const response = await fetch(`${apiBaseUrl}/api/status`) + if (!response.ok) { + throw new Error(`后端请求失败:HTTP ${response.status}`) + } + return response.json() as Promise +} diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..fe5bae3 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './style.css' + +createApp(App).mount('#app') diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..cb30731 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,70 @@ +:root { + font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif; + color: #20211f; + background: #f4f1e9; + font-synthesis: none; + text-rendering: optimizeLegibility; + --ink: #20211f; + --muted: #77776f; + --paper: #fffdf7; + --line: #dedbd0; + --accent: #e76f3d; + --success: #307b59; +} + +* { box-sizing: border-box; } +body { margin: 0; min-width: 320px; min-height: 100vh; } +button { font: inherit; } + +.app-shell { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; } +.sidebar { + display: flex; flex-direction: column; padding: 28px 20px; + color: #f8f5ed; background: #20211f; +} +.brand { display: flex; align-items: center; gap: 12px; margin-bottom: 48px; font-weight: 700; } +.brand-mark { + display: grid; place-items: center; width: 32px; height: 32px; + border-radius: 10px; color: #20211f; background: #f2c14e; +} +nav { display: grid; gap: 6px; } +.nav-item { + padding: 11px 14px; border: 0; border-radius: 8px; text-align: left; + color: #bcbdb7; background: transparent; +} +.nav-item.active { color: white; background: #343632; } +.nav-item:disabled { cursor: not-allowed; opacity: .55; } +.sidebar-hint { margin-top: auto; color: #8f918a; font-size: 13px; } + +.workspace { padding: 64px clamp(28px, 6vw, 88px); } +.workspace header { max-width: 720px; margin-bottom: 42px; } +.eyebrow, .card-label { margin: 0 0 10px; color: var(--accent); font-size: 12px; font-weight: 800; letter-spacing: .14em; } +h1 { margin: 0; font-family: Georgia, "Noto Serif SC", serif; font-size: clamp(42px, 7vw, 76px); line-height: 1; letter-spacing: -.045em; } +.subtitle { max-width: 580px; margin: 20px 0 0; color: var(--muted); font-size: 17px; line-height: 1.7; } +.cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; max-width: 960px; } +.card { min-height: 180px; padding: 28px; border: 1px solid var(--line); border-radius: 18px; background: var(--paper); box-shadow: 0 12px 40px rgb(46 43 36 / 6%); } +.hero-card { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr auto; gap: 22px; align-items: center; } +.card h2 { margin: 0 0 12px; font-size: 21px; } +.card p { color: var(--muted); line-height: 1.6; } +.status-line { display: flex; align-items: center; gap: 12px; min-width: 250px; } +.status-line p { margin: 3px 0 0; font-size: 13px; } +.status-dot { width: 10px; height: 10px; border-radius: 50%; background: #aaa; box-shadow: 0 0 0 5px rgb(120 120 120 / 10%); } +.status-line.success .status-dot { background: var(--success); box-shadow: 0 0 0 5px rgb(48 123 89 / 12%); } +.status-line.error .status-dot { background: #b84737; box-shadow: 0 0 0 5px rgb(184 71 55 / 12%); } +.primary-button { + justify-self: end; padding: 10px 16px; border: 0; border-radius: 9px; + color: white; background: var(--ink); cursor: pointer; +} +.primary-button:disabled { cursor: wait; opacity: .6; } + +@media (max-width: 720px) { + .app-shell { grid-template-columns: 1fr; } + .sidebar { min-height: auto; padding: 18px 20px; } + .brand { margin-bottom: 18px; } + nav { grid-template-columns: repeat(4, 1fr); } + .nav-item { padding: 9px 6px; text-align: center; font-size: 13px; } + .sidebar-hint { display: none; } + .workspace { padding-top: 42px; } + .cards { grid-template-columns: 1fr; } + .hero-card { display: grid; grid-column: auto; } + .primary-button { justify-self: start; } +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..4d16005 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.vue"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..77a18fc --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..baaeb98 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + host: '127.0.0.1', + port: 5173, + proxy: { + '/api': 'http://127.0.0.1:8000', + '/health': 'http://127.0.0.1:8000', + }, + }, +})