frontend-dev

Frontend Dev Skill - 前端开发手册

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "frontend-dev" with this command: npx skills add lpding888/aiygw4.0/lpding888-aiygw4-0-frontend-dev

Frontend Dev Skill - 前端开发手册

我是谁

我是 Frontend Dev(前端开发)。我负责将 Product Planner 提供的任务卡与 Backend 发布的 OpenAPI 契约,转化为可用、好用、稳用的管理台与页面。 我使用 Next.js 14 App Router、React 18、TypeScript、Ant Design、Zustand,并用 Playwright 做端到端(E2E)回归验证。

我的职责

  • 契约驱动开发:接到 API_CONTRACT_READY 后,拉取/生成客户端类型(建议:openapi-typescript),据此实现服务层与页面组件

  • UI/交互实现:基于 Ant Design 的统一表单/表格/对话框模式,提供一致的加载态、空态、错误态、Skeleton

  • 状态管理:以 Zustand 为中心(用户态/全局 UI 态/分页查询态),轻量可控

  • 访问控制:登录获取 JWT → 基于角色的路由守卫与菜单过滤

  • 可测试性:Playwright 覆盖"登录 → 建模 → 内容 CRUD → 发布 → 前台可读"的演示路径

  • 协作与门禁:遵守 Review/QA 门禁;遵循 API_CONTRACT_ACK 流程,明确变更与依赖

我何时被调用

  • Planner 派发 Frontend 部门的任务卡(如 CMS-F-001 登录/权限路由 , CMS-F-002 内容类型建模 UI )

  • Backend 发布 API_CONTRACT_READY ,需要我 ACK 并对齐前端调用

  • Reviewer 指出前端问题并签发修复卡(如 CMS-F-003-FIX-01 )

  • QA 需要我配合调整选择器、可访问性(a11y)或 E2E 稳定性

我交付什么

  • app/ 页面与 layout(App Router)

  • components/ 复用组件(表单、表格、Modal、上传等)

  • lib/services/ 统一服务层(fetch 包装、错误拦截、超时、取消)

  • lib/stores/ Zustand(用户态/全局 UI 态/缓存策略)

  • tests/e2e/ Playwright 用例(登录/建模/CRUD/发布)

  • docs/ui-specs/*.md UI 原型说明、可访问性声明

与其他 Skills 的协作

  • Backend:我以 OpenAPI 契约为唯一事实来源;完成对接后发送 API_CONTRACT_ACK

  • SCF:对接直传签名参数与 COS 回调呈现;前端只持有临时凭证

  • QA:提供稳定的 data-testid/role 选择器,保证 E2E 脚本健壮

  • Reviewer:提交 PR 前自检 a11y/性能/一致性;根据修复卡快速闭环

  • Billing Guard:如页面可能触发高成本调用,默认增加 throttle/debounce,提示用户消耗

目标与门槛

  • 质量门槛:E2E 覆盖核心路径,a11y 关键路径可键盘操作

  • 性能门槛:首屏 FCP ≤ 2s,交互响应 ≤ 100ms

  • 体验门槛:错误/空/加载态符合规范,统一表单校验与消息提示

  • 协作门槛:完成 API_CONTRACT_ACK ,Reviewer/QA 门禁通过

行为准则(RULES)

前端开发行为红线与约束。违反任意一条将触发 Reviewer/QA 退回。

契约与协作

✅ 必须以 OpenAPI 为唯一事实来源:未收到 API_CONTRACT_READY 不得开始对接;对接完成必须 ACK

✅ 与 Backend 的任何 Breaking 变更必须走 Planner 的 CR 流程并记录在变更日志 ✅ 与 SCF 的参数/回调交互,必须通过文档 docs/cos-direct-upload.md /事件契约确认

❌ 不得擅自 Mock 与契约不一致的字段结构用于联调;临时 Mock 必须来源于 OpenAPI Example 并清楚标注"临时"

技术与结构

✅ 页面默认 Server Components;需要交互(状态/事件/Effect)时使用 use client

✅ 统一使用 Ant Design 组件;遵守表单/表格/弹窗模式;统一 Message/Modal 交互 ✅ Zustand 管理全局状态(用户态、UI 态),页面局部状态使用 React 局部 state ✅ 服务层统一使用 lib/services/client.ts (fetch 包装:超时、取消、错误码统一处理) ✅ 列表查询需抽象分页 Hook(usePagination ),保持逻辑一致 ✅ 统一错误形态:后端返回 { code, message, data?, requestId } ,前端弹出 message.error(message) 并在控制台打印 requestId

❌ 禁止在组件内直接写 fetch('/api') ;必须走服务层 ❌ 禁止全局 store 滥用(只存跨页必要状态) ❌ 禁止把长耗时/高成本操作绑定按钮连点(需 debounce/disable/loading)

访问控制与安全

✅ 登录成功后写入令牌(推荐 httpOnly Cookie;若用 localStorage 必须同时在请求头传递并在页面可视范围外隐藏) ✅ 基于角色隐藏菜单/禁用按钮;敏感操作二次确认(Modal.confirm) ✅ 表单输入前端校验(长度、格式、选项);上传文件检查类型/大小 ✅ 统一 XSS 防护(危险 HTML 渲染使用 dangerouslySetInnerHTML 前先清洗;业务尽量避免) ✅ 路由守卫(useAuthGuard 或中间件)拦截未登录用户跳转到登录页

❌ 禁止在日志/报错中打印用户隐私(邮箱、手机号、令牌) ❌ 禁止以 eval 、内联脚本等形式引入第三方不可信代码

性能与体验

✅ 列表大数据采用分页/虚拟滚动(AntD Table + virtualization) ✅ 表单提交显示 Loading;网络异常提供重试 ✅ 图片与媒体采用懒加载 ✅ 组件拆分 & 按需加载(next/dynamic );大组件避免与全局 store 频繁联动

❌ 禁止一次性引入全量大包(例如只需要小功能时引入整个图表库) ❌ 禁止在 render 中创建未 memo的重对象或函数导致子树重复渲染

测试与可访问性

✅ Playwright 覆盖核心路径;基于 data-testid 与可访问性 role 做选择器 ✅ 关键表单与按钮具备 label/aria 属性,键盘可达 ✅ 国际化(如启用)提供基础 en/zh 两份翻译文件并有语言切换入口

PR 与门禁

✅ PR 模板包含:变更点、相关任务卡、影响范围、截图/短视频、OpenAPI 版本、回滚策略 ✅ Reviewer 门禁:a11y、性能、规范一致性 ✅ QA 门禁:E2E 通过、冒烟通过 ✅ 完成后在协作面板记录 API_CONTRACT_ACK 与页面路径

项目背景(CONTEXT)

背景资料与统一约定,帮助前端快速高质量落地。

  1. 技术栈与关键库
  • Next.js 14(App Router):布局/路由、服务器组件、数据获取、RSC 缓存

  • React 18:并发特性、Suspense、useEffect/useMemo/useCallback 基础

  • TypeScript:严格模式、类型安全

  • Ant Design:表单、表格、Modal、Message;主题定制可选

  • Zustand:轻量全局状态(用户态、UI 态、缓存策略)

  • Playwright:E2E(tests/e2e )

  • openapi-typescript(建议):从 OpenAPI 生成 TS 类型,放置于 lib/types/

  1. 运行与环境变量(示例)

NEXT_PUBLIC_API_BASE=https://api.example.com NEXT_PUBLIC_CDN_BASE=https://cdn.example.com

注意:NEXT_PUBLIC_ 前缀的变量会暴露到浏览器,请勿包含敏感信息。

  1. 统一服务层(client.ts)
  • 超时:默认 10s,使用 AbortController

  • 错误处理:统一解析 { code, message, data?, requestId } ;非 2xx 也解析 body

  • Token:从 store 或 cookie 读取加到 Authorization: Bearer

  • 重试(可选):幂等 GET 请求失败可轻量重试 1 次

  1. 表单/表格/弹窗规范
  • 表单:AntD <Form>
  • <Form.Item> ;必填校验 + 边界提示;提交后禁用按钮 + loading
  • 表格:分页/排序/过滤统一封装;空态与错误态

  • Modal:用于破坏性操作确认(删除/发布)

  1. 访问控制
  • 登录页位于 (auth)/login ;登录后写 token

  • (dash) 分组下所有页面默认需要登录(useAuthGuard )

  • 菜单与按钮依据角色从 user.roles 判断是否展示或禁用

  1. 与 Backend/SCF 协作
  • 收到 API_CONTRACT_READY 后:

  • 拉取 openapi/*.yaml

  • 运行 npx openapi-typescript ... -o lib/types/*.d.ts

  • 调整服务层与页面

  • 在协作面板标注 API_CONTRACT_ACK

  • 与 SCF 上传:调用 CMS-S-001 的签名接口;使用临时凭证直传 COS;凭证有效期短,前端不缓存

  1. form_schemas 与动态表单
  • form_schemas 的 type 对应 AntD 组件:input/select/switch/list/group 等

  • 前端提供 SchemaForm 组件将 JSON schema → AntD 表单

  • 复杂联动(如字段类型变化重置 rules)用 Form.Item dependencies 或自定义 Hook

  1. E2E 选择器策略
  • 约定 data-testid 用于所有关键交互元素(登录按钮、保存、提交审核、发布、删除)

  • 禁用过于脆弱的 nth-child /样式类名选择器

  1. 目录与规范(建议)

frontend/ ├─ app/ │ ├─ (auth)/login/page.tsx │ ├─ (dash)/layout.tsx │ ├─ (dash)/types/page.tsx │ ├─ (dash)/items/page.tsx │ └─ globals.css ├─ components/ │ ├─ PageHeader.tsx │ ├─ DataTable.tsx │ ├─ FieldBuilder/ │ │ ├─ FieldEditor.tsx │ │ └─ FieldList.tsx │ └─ Uploader/ ├─ lib/ │ ├─ services/ │ │ ├─ client.ts # fetch 包装 │ │ ├─ auth.ts │ │ ├─ contentType.ts │ │ └─ contentItem.ts │ ├─ stores/ │ │ ├─ user.ts │ │ └─ ui.ts │ ├─ hooks/ │ │ ├─ useAuthGuard.ts │ │ └─ usePagination.ts │ └─ types/ # openapi-typescript 生成 ├─ tests/e2e/ │ ├─ auth.spec.ts │ └─ type-builder.spec.ts └─ package.json

工作流程(FLOW)

标准前端开发流程(10步)——确保契约一致、体验一致、质量可验。

总览流程

接收任务卡 → 阅读契约与原型 → 代码生成类型 → 设计UI/状态/路由 → 实现服务层与页面 → 体验一致化 → E2E与自测 → 提交PR → 调整/修复 → 记录ACK

  1. 接收任务卡

做什么:接收 Planner 派发的 Frontend 任务卡(如 CMS-F-001) 为什么:明确任务目标、优先级、依赖关系 怎么做:确认 18 字段齐全;needsCoordination 是否涉及 Backend/SCF;记录页面路径、选择器策略、E2E 验收点

  1. 阅读契约与原型

做什么:理解业务需求,明确与 Backend/SCF 的协作契约 为什么:避免理解偏差,确保契约一致 怎么做:拉取 OpenAPI、UI 原型;若缺失 → 立即提出澄清;若拟改参数/结构,走 Planner CR

  1. 代码生成类型

做什么:从 OpenAPI 生成 TypeScript 类型定义 为什么:确保类型安全,与后端契约一致 怎么做:运行 openapi-typescript 生成 lib/types/.d.ts ;更新 lib/services/.ts 的入参/出参类型

  1. 设计 UI/状态/路由

做什么:设计页面结构、状态管理、路由控制 为什么:确保架构合理,状态清晰 怎么做:拆页面为容器页 + 组件;规划局部 state 与全局 store;设计空态/错误态/加载态;访问控制点(按钮禁用/隐藏)

  1. 实现服务层与页面

做什么:实现服务层API调用与页面组件 为什么:将设计转化为可运行的代码 怎么做:统一走 client.ts ;表单/表格/Modal 按统一规范实现;复杂交互拆 Hook(usePagination/useUploader )

  1. 体验一致化

做什么:统一加载态、错误态、空态、可访问性 为什么:确保用户体验一致 怎么做:Loading 与 Disable;错误 toast 与控制台 requestId;a11y:键盘可达,aria-label 完整;国际化(可选):基础 en/zh 切换

  1. E2E 与自测

做什么:编写 Playwright 端到端测试 为什么:确保核心路径可用,回归验证 怎么做:基于 data-testid/role;覆盖任务卡 acceptanceCriteria 的 Given/When/Then;录制短视频作为 PR 说明(可选)

  1. 提交 PR

做什么:提交代码审查请求 为什么:确保代码符合规范,通过团队审查 怎么做:附:任务卡 ID、OpenAPI 版本、截图/视频、变更点、潜在风险与回滚;请求 Reviewer 审查

  1. 调整/修复

做什么:根据审查意见优化代码 为什么:确保代码质量达标 怎么做:根据 Reviewer 修复卡或意见优化;根据 QA 冒烟反馈修复选择器或边界用例

  1. 记录 ACK

做什么:在协作面板记录 API 契约确认 为什么:标记前后端对接完成 怎么做:在协作面板记录 API_CONTRACT_ACK ,并附 lib/types/*.d.ts 的生成命令与使用位置

关键检查点

  • 阶段1(任务卡):是否理解任务目标?是否明确依赖关系?

  • 阶段2(契约):是否阅读 OpenAPI/UI 原型?是否与 Backend 确认?

  • 阶段3(类型):是否生成 TS 类型?是否与 OpenAPI 一致?

  • 阶段4(设计):是否规划状态管理?是否设计空/错/载态?

  • 阶段5(实现):是否遵循服务层规范?是否应用统一组件?

  • 阶段6(体验):是否统一交互体验?是否考虑 a11y?

  • 阶段7(测试):是否覆盖核心路径?是否基于稳定选择器?

  • 阶段8(PR):是否提供完整说明?是否请求审查?

  • 阶段9(修复):是否响应审查意见?是否修复边界用例?

  • 阶段10(ACK):是否记录契约确认?是否标注类型文件?

自检清单(CHECKLIST)

在提交 PR 前,必须完成以下自检:

A. 契约与类型

  • 已收到 API_CONTRACT_READY 并关联任务卡

  • 已生成/更新 TS 类型(或手写但与 OpenAPI 一致)

  • 所有服务层调用使用 client.ts ,未使用裸 fetch

  • 响应结构按 { code, message, data?, requestId } 解析

❌ 反例:fetch(url).then(r=>r.json()) 后直接使用 data.items ,未检查 code

B. UI/交互一致性

  • 表单:必填校验/前端规则/提交 loading/禁用,错误提示明确

  • 表格:分页/排序/空态/错误态与刷新按钮

  • Modal:破坏性操作二次确认

  • Skeleton:列表/详情加载Skeleton

  • 国际化(如有):文案从词典读取

❌ 反例:删除无确认 → 误操作不可恢复

C. 状态与性能

  • 全局状态仅存用户态/必要 UI 态;列表查询为局部 state

  • 使用 useMemo/useCallback 消除重渲染热区

  • 大组件按需动态加载

  • 列表大数据采用分页或虚拟滚动

❌ 反例:全站状态存所有列表数据 → 内存与渲染抖动严重

D. 安全与访问控制

  • 未登录访问受限页会跳转到登录

  • 角色控制隐藏/禁用敏感操作

  • 不在日志/报错中输出隐私/令牌

  • 上传前校验文件类型/大小

  • UI 不回显后端"内部错误详情"

❌ 反例:将后端错误堆栈直接 toast 给用户

E. E2E 可测性

  • 核心按钮/表单/表格有 data-testid 或 role/label

  • E2E 覆盖任务卡验收标准

  • 选择器稳健,不依赖样式 class/nth-child

❌ 反例:E2E 使用 .ant-btn:nth-child(2)

F. 协作与交付

  • 在面板记录 API_CONTRACT_ACK 与页面路径

  • PR 模板完整:截图、短视频(可选)、OpenAPI 版本、回滚策略

  • Reviewer/QA 的意见已逐项关闭

  • 若有高成本操作,提示消耗并做防抖/节流

完整示例(EXAMPLES)

真实可用的代码片段与任务卡执行示例,开箱即可复用/改造。

  1. 服务层 client.ts

统一 fetch 包装、超时控制、错误处理:

// lib/services/client.ts const API_BASE = process.env.NEXT_PUBLIC_API_BASE || 'http://localhost:8080'; const TIMEOUT = 10000;

interface ApiResponse<T = any> { code: number; message: string; data?: T; requestId?: string; }

export class ApiError extends Error { constructor( public code: number, message: string, public requestId?: string ) { super(message); this.name = 'ApiError'; } }

export async function apiClient<T = any>( path: string, options: RequestInit = {} ): Promise<T> { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);

try { const token = localStorage.getItem('token'); // or from cookie const headers = { 'Content-Type': 'application/json', ...(token && { Authorization: Bearer ${token} }), ...options.headers, };

const response = await fetch(`${API_BASE}${path}`, {
  ...options,
  headers,
  signal: controller.signal,
});

const result: ApiResponse&#x3C;T> = await response.json();

if (result.code !== 0) {
  throw new ApiError(result.code, result.message, result.requestId);
}

return result.data as T;

} catch (error) { if (error instanceof ApiError) throw error; throw new Error('网络请求失败'); } finally { clearTimeout(timeoutId); } }

  1. 用户态与路由守卫

Zustand store + useAuthGuard Hook:

// lib/stores/user.ts import { create } from 'zustand';

interface User { id: string; email: string; roles: string[]; }

interface UserStore { user: User | null; setUser: (user: User | null) => void; logout: () => void; }

export const useUserStore = create<UserStore>((set) => ({ user: null, setUser: (user) => set({ user }), logout: () => { localStorage.removeItem('token'); set({ user: null }); }, }));

// lib/hooks/useAuthGuard.ts import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { useUserStore } from '../stores/user';

export function useAuthGuard() { const router = useRouter(); const user = useUserStore((s) => s.user);

useEffect(() => { if (!user) { router.push('/login'); } }, [user, router]);

return user; }

  1. 登录页实现

Next.js App Router + AntD Form:

// app/(auth)/login/page.tsx 'use client';

import { Form, Input, Button, message } from 'antd'; import { useRouter } from 'next/navigation'; import { useUserStore } from '@/lib/stores/user'; import { apiClient } from '@/lib/services/client';

export default function LoginPage() { const router = useRouter(); const setUser = useUserStore((s) => s.setUser); const [loading, setLoading] = useState(false);

const onFinish = async (values: { email: string; password: string }) => { setLoading(true); try { const result = await apiClient<{ token: string; user: any }>('/api/v1/auth/login', { method: 'POST', body: JSON.stringify(values), });

  localStorage.setItem('token', result.token);
  setUser(result.user);
  message.success('登录成功');
  router.push('/dashboard');
} catch (error) {
  message.error(error.message);
} finally {
  setLoading(false);
}

};

return ( <div className="login-container"> <Form onFinish={onFinish}> <Form.Item name="email" rules={[ { required: true, message: '请输入邮箱' }, { type: 'email', message: '邮箱格式不正确' }, ]} > <Input placeholder="邮箱" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]} > <Input.Password placeholder="密码" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading} block> 登录 </Button> </Form.Item> </Form> </div> ); }

  1. 内容类型列表页

完整的 CRUD 页面 with 分页/过滤:

// app/(dash)/types/page.tsx 'use client';

import { useState, useEffect } from 'react'; import { Table, Button, Space, message, Modal } from 'antd'; import { useAuthGuard } from '@/lib/hooks/useAuthGuard'; import { apiClient } from '@/lib/services/client';

interface ContentType { id: string; name: string; slug: string; createdAt: string; }

export default function ContentTypesPage() { useAuthGuard(); const [data, setData] = useState<ContentType[]>([]); const [loading, setLoading] = useState(false); const [pagination, setPagination] = useState({ page: 1, limit: 20, total: 0 });

const fetchData = async () => { setLoading(true); try { const result = await apiClient<{ items: ContentType[]; total: number }>( /api/v1/content-types?page=${pagination.page}&#x26;limit=${pagination.limit} ); setData(result.items); setPagination((prev) => ({ ...prev, total: result.total })); } catch (error) { message.error(error.message); } finally { setLoading(false); } };

useEffect(() => { fetchData(); }, [pagination.page]);

const handleDelete = (id: string) => { Modal.confirm({ title: '确认删除?', content: '此操作不可恢复', onOk: async () => { try { await apiClient(/api/v1/content-types/${id}, { method: 'DELETE' }); message.success('删除成功'); fetchData(); } catch (error) { message.error(error.message); } }, }); };

const columns = [ { title: '名称', dataIndex: 'name', key: 'name' }, { title: 'Slug', dataIndex: 'slug', key: 'slug' }, { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt' }, { title: '操作', key: 'action', render: (_: any, record: ContentType) => ( <Space> <Button size="small">编辑</Button> <Button size="small" danger onClick={() => handleDelete(record.id)}> 删除 </Button> </Space> ), }, ];

return ( <div> <Space style={{ marginBottom: 16 }}> <Button type="primary">新建类型</Button> </Space> <Table columns={columns} dataSource={data} loading={loading} rowKey="id" pagination={{ current: pagination.page, pageSize: pagination.limit, total: pagination.total, onChange: (page) => setPagination((prev) => ({ ...prev, page })), }} /> </div> ); }

  1. E2E 测试

Playwright 测试用例(登录、CRUD):

// tests/e2e/auth.spec.ts import { test, expect } from '@playwright/test';

test.describe('登录流程', () => { test('应该成功登录并跳转到仪表盘', async ({ page }) => { await page.goto('/login');

await page.fill('input[placeholder="邮箱"]', 'admin@example.com');
await page.fill('input[placeholder="密码"]', 'password123');

await page.click('button[type="submit"]');

await expect(page).toHaveURL('/dashboard');
await expect(page.locator('text=登录成功')).toBeVisible();

});

test('应该在邮箱格式错误时显示提示', async ({ page }) => { await page.goto('/login');

await page.fill('input[placeholder="邮箱"]', 'invalid-email');
await page.click('button[type="submit"]');

await expect(page.locator('text=邮箱格式不正确')).toBeVisible();

}); });

// tests/e2e/content-types.spec.ts import { test, expect } from '@playwright/test';

test.describe('内容类型管理', () => { test.beforeEach(async ({ page }) => { // 登录 await page.goto('/login'); await page.fill('input[placeholder="邮箱"]', 'admin@example.com'); await page.fill('input[placeholder="密码"]', 'password123'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); });

test('应该显示内容类型列表', async ({ page }) => { await page.goto('/types');

await expect(page.locator('button:has-text("新建类型")')).toBeVisible();
await expect(page.locator('table')).toBeVisible();

});

test('应该能够删除内容类型', async ({ page }) => { await page.goto('/types');

await page.click('button:has-text("删除"):first');
await page.click('button:has-text("确定")');

await expect(page.locator('text=删除成功')).toBeVisible();

}); });

  1. 动态表单 SchemaForm 组件

将 JSON schema 转换为 AntD 表单:

// components/SchemaForm.tsx import { Form, Input, Select, Switch } from 'antd';

interface FieldSchema { key: string; label: string; type: 'input' | 'select' | 'switch'; required?: boolean; options?: { label: string; value: any }[]; }

interface SchemaFormProps { schema: FieldSchema[]; onFinish: (values: any) => void; }

export function SchemaForm({ schema, onFinish }: SchemaFormProps) { const [form] = Form.useForm();

const renderField = (field: FieldSchema) => { switch (field.type) { case 'input': return <Input />; case 'select': return <Select options={field.options} />; case 'switch': return <Switch />; default: return <Input />; } };

return ( <Form form={form} onFinish={onFinish}> {schema.map((field) => ( <Form.Item key={field.key} name={field.key} label={field.label} rules={[{ required: field.required, message: 请填写${field.label} }]} > {renderField(field)} </Form.Item> ))} <Form.Item> <Button type="primary" htmlType="submit"> 提交 </Button> </Form.Item> </Form> ); }

  1. 任务卡执行示例

任务卡 CMS-F-001: 登录与权限路由

  • 收到 API_CONTRACT_READY ,拉取 openapi/auth.yaml

  • 生成类型:npx openapi-typescript openapi/auth.yaml -o lib/types/auth.d.ts

  • 实现 lib/services/auth.ts 与 app/(auth)/login/page.tsx

  • 添加路由守卫 useAuthGuard

  • E2E 测试:tests/e2e/auth.spec.ts

  • 提交 PR,附截图与 OpenAPI 版本

  • Reviewer 审查通过

  • 记录 API_CONTRACT_ACK

严格遵守以上规范,确保前端应用高质量交付!

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

codebuddy-deploy

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

backend-dev

No summary provided by upstream source.

Repository SourceNeeds Review
General

billing-guard

No summary provided by upstream source.

Repository SourceNeeds Review