diff --git a/checklist-agent2-phase2.md b/checklist-agent2-phase2.md
deleted file mode 100644
index d386b5a..0000000
--- a/checklist-agent2-phase2.md
+++ /dev/null
@@ -1,199 +0,0 @@
-# Agent2 前端自查 Checklist - Phase 2 (v0.4)
-
-> 项目: mach-cms-frontend
-> 版本: Phase 2 v0.4
-> 角色: 前端开发 Agent
-> 要求: 逐项自查,全部通过后再提交验收
-
----
-
-## 0. 环境前置
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 0.1 | 远程仓库可访问 | `git remote -v` 显示正确 origin | ✓ |
-| 0.2 | 本地 main 分支最新 | `git pull --rebase origin main` 无冲突 | ✓ |
-| 0.3 | Node.js 20+ | `node -v` 输出 v20.x | ✓ |
-| 0.4 | 后端已启动 | `http://localhost:8080/actuator/health` 返回 UP | ✓ |
-
----
-
-## 1. basePath 修正确认
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 1.1 | `client.ts` 中 basePath 为空字符串 | 所有 `new XxxApi(config, '', axiosInstance)` 第二个参数为 `''` | ✓ |
-| 1.2 | 无 `/api/api` 请求 | 浏览器 DevTools Network 面板中,请求路径无 `/api/api` 前缀 | ✓ |
-| 1.3 | 请求正常到达后端 | 所有 API 请求状态码 200/201,非 404 | ✓ |
-
----
-
-## 2. Git 规范自查
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 2.1 | 提交历史规范 | `git log --oneline -20` 所有 message 符合 `type(scope): subject` | ✓ |
-| 2.2 | 无 "update"/"fix bug" 等垃圾提交 | 全局搜索无此类 message | ✓ |
-| 2.3 | 单 commit 行数 ≤ 200 | `git log --stat` 抽查最近 5 个 commit,无超 200 行 | ☐ | △ bfcbf4a=335、459e094=225 超200行(已push历史,本次验收后续 commit 均≤200) |
-| 2.4 | 每次 commit 已 push | `git status` 显示 "Your branch is up to date with 'origin/main'" | ✓ |
-| 2.5 | 无未提交代码 | `git status` 无未 stage 的修改 | ✓ |
-
----
-
-## 3. Task 1: API 代码重新生成
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 3.1 | `npm run api:generate` 成功 | 无报错,生成目录更新 | ✓ |
-| 3.2 | `src/generated/api/` 存在 | 目录存在且包含 `api.ts` + `models/` | ✓ |
-| 3.3 | Comment 类型生成 | `models/` 包含 `comment-response.ts`, `comment-submit-request.ts` | ☐ | ✓* models/ 未单独拆目录(typescript-axios 默认单文件 api.ts),CommentResponse/CommentSubmitRequest 存在于 api.ts |
-| 3.4 | User 类型生成 | `models/` 包含 `user-info.ts`, `update-role-request.ts` | ☐ | ✓* UserInfo/UpdateUserRoleRequest 存在于 api.ts(未拆 models/) |
-| 3.5 | API 类生成 | `api.ts` 包含 `CommentsApi`, `CommentAdminApi`, `UserAdminApi` | ☐ | ✓* 类名 CommentApi/CommentAdminApi/UserApi(由契约 tag 决定),非清单 Suggest CommentsApi/UserAdminApi |
-| 3.6 | `StoredFile` 扩展 | 包含 `thumbnailUrl` 字段 | ✓ |
-| 3.7 | `client.ts` 已更新 | 新增 `commentsApi`, `commentAdminApi`, `userAdminApi` 实例,basePath 为 `''` | ☐ | ✓* 实例 commentApi/commentAdminApi/userApi(契约命名) |
-
----
-
-## 4. Task 2: 注册页面
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 4.1 | 路由存在 | `/register` 可访问 | ✓ |
-| 4.2 | 表单字段 | 用户名、密码、确认密码、邮箱 | ✓ |
-| 4.3 | 前端校验 | 密码与确认密码不一致时阻止提交 | ✓ |
-| 4.4 | 用户名长度校验 | 3-50 字符 | ✓ |
-| 4.5 | 密码长度校验 | 6-100 字符 | ✓ |
-| 4.6 | 调用注册 API | 点击注册调用 `authApi.register()` | ✓ |
-| 4.7 | 注册后自动登录 | 成功后自动调用 `authStore.login()` | ✓ |
-| 4.8 | 跳转后台 | 登录成功后进入 `/admin` | ✓ |
-| 4.9 | 已登录拦截 | 已登录用户访问 `/register` 自动跳转 `/admin` | ✓ |
-| 4.10 | 错误提示 | 注册失败(如用户名已存在)显示错误信息 | ✓ |
-
----
-
-## 5. Task 3: 评论组件
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 5.1 | `CommentSection.vue` 存在 | `src/components/CommentSection.vue` 存在 | ✓ |
-| 5.2 | 接收 slug prop | `` | ✓ |
-| 5.3 | 加载评论列表 | 进入文章详情页自动加载评论 | ✓ |
-| 5.4 | 只显示已审核 | 未审核评论不显示 | ✓ |
-| 5.5 | 提交表单 | 昵称(必填)、邮箱(可选)、内容(必填) | ✓ |
-| 5.6 | 提交后提示 | 显示"评论已提交,等待审核" | ✓ |
-| 5.7 | 无需登录 | 未登录用户可提交评论 | ✓ |
-| 5.8 | 样式 | 评论间有分隔线,时间格式化显示 | ✓ |
-| 5.9 | 文章详情页引入 | `ArticleDetailView.vue` 底部包含 `` | ✓ |
-
----
-
-## 6. Task 4: Markdown 渲染 + XSS 防护
-
-| # | 检查项 | 通过标准 | 结果 |
-|---|--------|---------|------|
-| 6.1 | 依赖安装 | `marked`, `dompurify`, `highlight.js` 已安装 | ✓ |
-| 6.2 | `renderMarkdown` 工具 | `src/utils/markdown.ts` 存在 | ✓ |
-| 6.3 | Markdown 转 HTML | 标题、段落、列表、链接正确渲染 | ✓ |
-| 6.4 | 代码高亮 | 代码块有语法高亮(背景色、关键字着色) | ✓ |
-| 6.5 | XSS 过滤 | `DOMPurify.sanitize()` 过滤 ``,前台渲染时不执行脚本 | ☐ |
-| 5.5 | SQL 注入防护 | 搜索关键词输入 `' OR 1=1 --`,后端正常处理不报错 | ☐ |
-| 5.6 | 目录遍历防护 | 上传时 `directory=../../etc` 被安全处理 | ☐ |
-| 5.7 | JWT 安全 | Token 中不包含敏感信息(密码等);Secret 不是硬编码的弱密钥 | ☐ |
-
----
-
-## 6. 问题记录模板
-
-联调中发现的问题按以下格式记录:
-
-### Issue #1 — 搜索接口复现为后端旧进程所致(已解决)
-- **发现时间**: 2026-08-10 00:00
-- **发现人**: Agent2(前端)
-- **问题描述**: 初测时 `GET /api/articles/search?q=Spring` 返回 `{code:3001,"文章不存在: search"}`。后端重启(`./gradlew bootRun` 全新进程)后同一请求返回 code=0 正确搜索结果,确认该现象由旧后端进程(未包含 SearchController 的旧构建)造成,非契约/前端问题。
-- **复现步骤**: 旧进程下 `curl "http://localhost:8080/api/articles/search?q=spring"`
-- **预期结果**: 返回搜索结果
-- **实际结果**: 重启后返回 `{code:0, data:{list:[...]}}`,2.3.5/1.5.1 通过
-- **根因分析**: 后端旧进程加载了过期构建产物,未包含搜索路由
-- **责任方**: Agent1(后端,运维)
-- **修复方案**: 联调前确保后端为最新 `bootRun` 进程
-- **验证结果**: ☐ 已解决(2026-08-10 复验通过)
-
-### Issue #2 — 后端 `POST /auth/logout` 未吊销 Refresh Token
-- **发现时间**: 2026-08-10 00:05
-- **发现人**: Agent2(前端)
-- **影响**: 前端 2.4.6 通过(本地清理 localStorage 正常);仅后端吊销语义缺失(1.3.4)
-- **问题描述**: 前端 `auth.logout()` 调 `POST /api/auth/logout` 后,旧 refreshToken 仍可通过 `POST /api/auth/refresh` 换取新 TokenPair,令牌吊销语义失效(违反 checklist 1.3.4)。
-- **复现步骤**: 1) 登录获取 refreshToken;2) 调 `/auth/logout`(code=0);3) 用旧 refreshToken 调 `/auth/refresh` → 仍返回 code=0 新 token(在最新 bootRun 进程上复验一致)
-- **预期结果**: logout 后旧 refreshToken 刷新应失败(401/业务错误)
-- **实际结果**: 刷新成功并返回新的 TokenPair
-- **根因分析**: 后端登出逻辑未将 refreshToken 加入黑名单/吊销列表(JWT 无状态未记录失效)
-- **责任方**: Agent1(后端)
-- **修复方案**: 服务端维护 refreshToken 吊销集合(DB/redis),refresh 时校验
-- **验证结果**: ☐ 待修复
-
-### Issue #3 — 契约路径加 `/api` 前缀后前端 baseURL 适配(已处理)
-- **发现时间**: 2026-08-10 00:20
-- **发现人**: Agent2(前端,承接 Agent1 提示)
-- **问题描述**: Agent1 将契约路径改为带 `/api` 前缀(`servers.url=http://localhost:8080`)。重新 `npm run api:generate` 后,生成的 localVarPath 变为 `/api/auth/...`、方法名变为 `apiXxx` 前缀、新增 `PublicApi`/`UserApi`。若前端 axios `baseURL` 仍为 `/api`,会拼成 `/api/api/...` → 404。
-- **Agent1 提示**: 需把 client.ts 中 `new XxxApi(config, '/api', ...)` 的 `/api` 改为 `''`。经核实**不正确/不完整**:生成的 `common.ts` 拼接逻辑为 `axios.defaults.baseURL ? '' : (configuration?.basePath ?? basePath)`,axios 的 `baseURL` 优先于 XxxApi 的 basePath 参数;且 axios 会自行 combineURLs。若仅改 XxxApi 参数而保留 baseURL=`/api`,仍会双前缀。
-- **实际修复**: 将 axios 实例 `baseURL` 从 `/api` 改为 `/`(各 XxxApi 的 `/api` basePath 参数无害保留),并批量重命名方法调用(`articlesGet`→`apiArticlesGet` 等)。实测 `/api/articles` 单前缀、经 vite 代理到 8080 正常,2.3~2.6 全部用例复验 PASS。
-- **责任方**: 前端(Agent2)
-- **验证结果**: ☑ 已处理(2026-08-10 复验 26/26 通过)
-
-```markdown
-### Issue #{编号}
-- **发现时间**: YYYY-MM-DD HH:MM
-- **发现人**: Agent1/Agent2/架构师
-- **问题描述**:
-- **复现步骤**:
-- **预期结果**:
-- **实际结果**:
-- **根因分析**:
-- **责任方**: Agent1(后端)/ Agent2(前端)/ openapi.yaml(契约)
-- **修复方案**:
-- **验证结果**: ☐ 待修复 / ☐ 已修复 / ☐ 已验证
-```
-
----
-
-## 验收签字
-
-| 角色 | 姓名 | 日期 | 签字 |
-|------|------|------|------|
-| 架构 Owner | | | |
-| Agent1(后端) | | | |
-| Agent2(前端) | | | |
-
----
-
-## 附录:快速测试命令
-
-```bash
-# 后端健康检查
-curl http://localhost:8080/actuator/health
-
-# 注册
-curl -X POST http://localhost:8080/api/auth/register \
- -H "Content-Type: application/json" \
- -d '{"username":"admin","password":"123456"}'
-
-# 登录
-curl -X POST http://localhost:8080/api/auth/login \
- -H "Content-Type: application/json" \
- -d '{"username":"admin","password":"123456"}'
-
-# 创建文章(替换 $TOKEN)
-curl -X POST http://localhost:8080/api/admin/articles \
- -H "Authorization: Bearer $TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"title":"Hello World","content":"This is a test article."}'
-
-# 搜索
-curl "http://localhost:8080/api/articles/search?q=hello&page=1&size=10"
-
-# 前台列表(无认证)
-curl "http://localhost:8080/api/articles?page=1&size=10"
-```
diff --git a/prompt-agent2-frontend-v0.3.md b/prompt-agent2-frontend-v0.3.md
deleted file mode 100644
index 196c274..0000000
--- a/prompt-agent2-frontend-v0.3.md
+++ /dev/null
@@ -1,507 +0,0 @@
-# Agent2 Prompt: Mach-CMS Frontend Implementation
-
-## 你的角色
-你是前端开发 Agent,负责实现 Mach-CMS 的管理后台和前台页面。你**不是设计师**,不讨论 UI 美观性,只按契约和规范实现功能。
-
-## 契约文件(唯一真理来源)
-后端仓库根目录下的 `openapi.yaml` 是你唯一的 API 规范。你必须:
-1. 用 OpenAPI Generator 从该 YAML 生成 TypeScript 类型和 API 客户端
-2. **禁止手写任何 API 请求类型或接口定义**
-3. 如果实现中发现 YAML 与实际返回不符,**暂停并上报**,禁止自行修改后不同步
-
-## 技术栈(禁止变更)
-- Vue 3.4+
-- TypeScript 5.4+(严格模式)
-- Vite 5.2+
-- Vue Router 4.3+
-- Pinia 2.1+
-- Axios 1.7+
-- OpenAPI Generator 7.x
-
-## 项目初始化
-
-```bash
-npm create vue@latest mach-cms-frontend
-# 选择:TypeScript + Vue Router + Pinia
-
-cd mach-cms-frontend
-npm install
-npm install axios
-npm install -D @openapitools/openapi-generator-cli
-```
-
-## 你的任务(按顺序执行,逐项验收)
-
-### Task 1: OpenAPI 代码生成配置
-
-创建 `openapitools.json`:
-```json
-{
- "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
- "spaces": 2,
- "generator-cli": {
- "version": "7.6.0",
- "generators": {
- "api": {
- "generatorName": "typescript-axios",
- "inputSpec": "../mach-cms-backend/openapi.yaml",
- "output": "src/generated/api",
- "additionalProperties": {
- "supportsES6": "true",
- "npmName": "mach-cms-api",
- "snapshot": "false",
- "withInterfaces": "false"
- }
- }
- }
- }
-}
-```
-
-在 `package.json` 添加脚本:
-```json
-{
- "scripts": {
- "api:generate": "openapi-generator-cli generate"
- }
-}
-```
-
-执行:
-```bash
-npm run api:generate
-```
-
-确认生成目录结构:
-```
-src/generated/api/
-├── api.ts # 所有 API 类(AuthApi, ArticlesApi, ArticleAdminApi, TagsApi, MediaApi...)
-├── base.ts # Axios 实例和配置
-├── configuration.ts
-├── common.ts
-├── index.ts
-└── models/
- ├── index.ts
- ├── response.ts
- ├── page-result.ts
- ├── article-detail.ts
- ├── article-list-item.ts
- ├── article-create-request.ts
- ├── article-update-request.ts
- ├── token-pair.ts
- ├── login-request.ts
- ├── register-request.ts
- ├── tag.ts
- ├── stored-file.ts
- └── ...
-```
-
-### Task 2: Axios 客户端封装
-
-文件 `src/api/client.ts`:
-```typescript
-import {
- Configuration,
- AuthApi,
- ArticlesApi,
- ArticleAdminApi,
- TagsApi,
- MediaApi,
- SearchApi
-} from '@/generated/api'
-import axios from 'axios'
-
-const axiosInstance = axios.create({
- baseURL: '/api',
- timeout: 10000
-})
-
-// 请求拦截:自动带 Access Token
-axiosInstance.interceptors.request.use((config) => {
- const token = localStorage.getItem('token')
- if (token) {
- config.headers.Authorization = `Bearer ${token}`
- }
- return config
-})
-
-// 响应拦截:401 静默刷新 Token
-axiosInstance.interceptors.response.use(
- (res) => res,
- async (err) => {
- const original = err.config
- if (err.response?.status === 401 && !original._retry) {
- original._retry = true
- const refresh = localStorage.getItem('refreshToken')
- if (refresh) {
- try {
- const authApi = new AuthApi(new Configuration(), '/api', axiosInstance)
- const resp = await authApi.refreshToken({ refreshToken: refresh })
- const data = resp.data.data!
- localStorage.setItem('token', data.accessToken)
- localStorage.setItem('refreshToken', data.refreshToken)
- original.headers.Authorization = `Bearer ${data.accessToken}`
- return axiosInstance(original)
- } catch {
- localStorage.removeItem('token')
- localStorage.removeItem('refreshToken')
- window.location.href = '/login'
- }
- } else {
- window.location.href = '/login'
- }
- }
- return Promise.reject(err)
- }
-)
-
-const config = new Configuration()
-
-export const authApi = new AuthApi(config, '/api', axiosInstance)
-export const articlesApi = new ArticlesApi(config, '/api', axiosInstance)
-export const articleAdminApi = new ArticleAdminApi(config, '/api', axiosInstance)
-export const tagsApi = new TagsApi(config, '/api', axiosInstance)
-export const mediaApi = new MediaApi(config, '/api', axiosInstance)
-export const searchApi = new SearchApi(config, '/api', axiosInstance)
-```
-
-**禁止**:直接 `import axios from 'axios'` 在任何组件或服务中使用,必须统一通过 `client.ts`。
-
-### Task 3: Pinia 认证 Store
-
-文件 `src/stores/auth.ts`:
-```typescript
-import { defineStore } from 'pinia'
-import { ref, computed } from 'vue'
-import { authApi } from '@/api/client'
-import type { LoginRequest, RegisterRequest } from '@/generated/api'
-
-export const useAuthStore = defineStore('auth', () => {
- const token = ref(localStorage.getItem('token'))
- const refreshToken = ref(localStorage.getItem('refreshToken'))
- const username = ref(null)
- const isLoggedIn = computed(() => !!token.value)
-
- async function register(request: RegisterRequest) {
- const resp = await authApi.register(request)
- const data = resp.data.data!
- setTokens(data.accessToken, data.refreshToken)
- }
-
- async function login(request: LoginRequest) {
- const resp = await authApi.login(request)
- const data = resp.data.data!
- setTokens(data.accessToken, data.refreshToken)
- }
-
- async function logout() {
- try {
- await authApi.logout()
- } finally {
- clearTokens()
- }
- }
-
- function setTokens(access: string, refresh: string) {
- token.value = access
- refreshToken.value = refresh
- localStorage.setItem('token', access)
- localStorage.setItem('refreshToken', refresh)
- }
-
- function clearTokens() {
- token.value = null
- refreshToken.value = null
- username.value = null
- localStorage.removeItem('token')
- localStorage.removeItem('refreshToken')
- }
-
- return {
- token,
- refreshToken,
- username,
- isLoggedIn,
- register,
- login,
- logout,
- setTokens,
- clearTokens
- }
-})
-```
-
-### Task 4: Vue Router + 路由守卫
-
-文件 `src/router/index.ts`:
-```typescript
-import { createRouter, createWebHistory } from 'vue-router'
-import { useAuthStore } from '@/stores/auth'
-
-const router = createRouter({
- history: createWebHistory(),
- routes: [
- {
- path: '/',
- name: 'Home',
- component: () => import('@/views/HomeView.vue')
- },
- {
- path: '/post/:slug',
- name: 'ArticleDetail',
- component: () => import('@/views/ArticleDetailView.vue')
- },
- {
- path: '/search',
- name: 'Search',
- component: () => import('@/views/SearchView.vue')
- },
- {
- path: '/login',
- name: 'Login',
- component: () => import('@/views/LoginView.vue'),
- meta: { guestOnly: true }
- },
- {
- path: '/admin',
- component: () => import('@/views/admin/AdminLayout.vue'),
- meta: { requiresAuth: true },
- children: [
- {
- path: '',
- redirect: '/admin/articles'
- },
- {
- path: 'articles',
- name: 'AdminArticleList',
- component: () => import('@/views/admin/ArticleListView.vue')
- },
- {
- path: 'articles/new',
- name: 'AdminArticleCreate',
- component: () => import('@/views/admin/ArticleEditorView.vue')
- },
- {
- path: 'articles/edit/:id',
- name: 'AdminArticleEdit',
- component: () => import('@/views/admin/ArticleEditorView.vue')
- },
- {
- path: 'tags',
- name: 'AdminTagList',
- component: () => import('@/views/admin/TagListView.vue')
- }
- ]
- }
- ]
-})
-
-router.beforeEach((to, from, next) => {
- const auth = useAuthStore()
-
- if (to.meta.requiresAuth && !auth.isLoggedIn) {
- next('/login')
- } else if (to.meta.guestOnly && auth.isLoggedIn) {
- next('/admin')
- } else {
- next()
- }
-})
-
-export default router
-```
-
-### Task 5: Vite 代理配置
-
-文件 `vite.config.ts`:
-```typescript
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import { resolve } from 'path'
-
-export default defineConfig({
- plugins: [vue()],
- resolve: {
- alias: {
- '@': resolve(__dirname, 'src')
- }
- },
- server: {
- port: 5173,
- proxy: {
- '/api': {
- target: 'http://localhost:8080',
- changeOrigin: true
- },
- '/uploads': {
- target: 'http://localhost:8080',
- changeOrigin: true
- }
- }
- }
-})
-```
-
-### Task 6: 页面实现
-
-#### 6.1 首页 `src/views/HomeView.vue`
-- 调用 `articlesApi.listArticles()` 获取文章列表
-- 展示文章卡片:标题、摘要、发布时间、作者、标签
-- 点击卡片跳转 `/post/:slug`
-- 支持分页
-
-#### 6.2 文章详情 `src/views/ArticleDetailView.vue`
-- 路由参数 `slug`
-- 调用 `articlesApi.getArticleBySlug(slug)`
-- 渲染 Markdown 内容(先用 `v-html` 配合 DOMPurify,后期可接 Markdown 渲染库)
-- 展示:标题、作者、发布时间、标签、浏览量
-
-#### 6.3 搜索页 `src/views/SearchView.vue`
-- Query 参数 `?q=keyword`
-- 调用 `searchApi.searchArticles(q, page, size)`
-- 展示搜索结果列表
-
-#### 6.4 登录页 `src/views/LoginView.vue`
-- 表单:用户名、密码
-- 调用 `authStore.login({ username, password })`
-- 登录成功跳转 `/admin`
-- 提供"去注册"链接(Phase 2 完善)
-
-#### 6.5 管理后台布局 `src/views/admin/AdminLayout.vue`
-- 侧边栏导航:文章管理、标签管理
-- 顶部栏:显示当前用户、退出按钮
-- 中间 ``
-
-#### 6.6 文章列表 `src/views/admin/ArticleListView.vue`
-- 调用 `articleAdminApi`(注意:需要认证)
-- 表格展示:标题、状态、发布时间、操作(编辑、删除、发布、归档)
-- 分页
-- 状态用不同颜色标签区分(DRAFT=灰色, PUBLISHED=绿色, ARCHIVED=橙色)
-
-#### 6.7 文章编辑器 `src/views/admin/ArticleEditorView.vue`
-- 复用组件:创建和编辑共用
-- 表单:标题、摘要(textarea)、内容(textarea,后期接 Markdown 编辑器)、封面图 URL、标签选择
-- 创建调用 `articleAdminApi.createArticle()`
-- 编辑调用 `articleAdminApi.updateArticle(id, ...)`
-- 保存成功后跳转列表页
-
-#### 6.8 标签管理 `src/views/admin/TagListView.vue`
-- 调用 `tagsApi.listTags()`
-- 表格展示:名称、文章数、操作(编辑、删除)
-- 新增标签表单
-
-### Task 7: 组件规范
-
-文件 `src/components/AppButton.vue`、`src/components/AppInput.vue` 等基础组件(可选,可用原生 HTML 先跑通)。
-
-**必须有的组件**:
-- `src/components/ArticleCard.vue`:文章卡片,接收 `ArticleListItem` 类型 props
-- `src/components/Pagination.vue`:分页组件,接收 `page/size/totalPages`,emit `change`
-
-## 代码规范(违反 = 拒收)
-
-| # | 规则 | 处罚 |
-|---|------|------|
-| 1 | **禁止手写 API 类型**,必须从 `openapi.yaml` 生成 | 重写 |
-| 2 | **禁止直接 `import axios`**,统一用 `src/api/client.ts` | 重写 |
-| 3 | 组件必须用 `