1 changed files with 184 additions and 0 deletions
@ -0,0 +1,184 @@ |
|||||
|
# Registration Page Improvement Design |
||||
|
|
||||
|
**Date**: 2026-05-15 |
||||
|
**Branch**: feat/registration-page |
||||
|
**Status**: draft |
||||
|
|
||||
|
## Scope |
||||
|
|
||||
|
Full-stack improvements to the registration page covering UI/UX, engineering quality, and visual polish. The existing `register.vue` (146 lines) works correctly but has accumulated technical debt and UX gaps. |
||||
|
|
||||
|
## Architecture: Component Decomposition (Approach B) |
||||
|
|
||||
|
Split the monolithic `register.vue` into focused components with clear data flow. |
||||
|
|
||||
|
### File Structure |
||||
|
|
||||
|
``` |
||||
|
app/pages/register.vue # Page shell (~60 lines) |
||||
|
app/components/register/ |
||||
|
RegisterForm.vue # Main form with submission logic (~120 lines) |
||||
|
PasswordInput.vue # Password field with show/hide toggle (~40 lines) |
||||
|
PasswordStrength.vue # Real-time strength indicator (~35 lines) |
||||
|
CaptchaField.vue # Captcha SVG + refresh + input (~50 lines) |
||||
|
packages/shared/auth-schema.ts # Shared Zod schema (client + server) |
||||
|
``` |
||||
|
|
||||
|
### Data Flow |
||||
|
|
||||
|
``` |
||||
|
register.vue |
||||
|
│ Owns: captchaToken, captchaSvg, toast |
||||
|
│ Provides: fetchCaptcha() |
||||
|
│ |
||||
|
└─ RegisterForm |
||||
|
│ Props: captchaSvg, captchaToken |
||||
|
│ Emits: refresh-captcha, success, error |
||||
|
│ Uses: useHttpFetch → POST /api/auth/register |
||||
|
│ Internal: loading state |
||||
|
│ |
||||
|
├─ PasswordInput (x2: password + confirmPassword) |
||||
|
│ Props: modelValue, label, placeholder, disabled |
||||
|
│ Emits: update:modelValue |
||||
|
│ Internal: showPassword toggle |
||||
|
│ |
||||
|
├─ PasswordStrength |
||||
|
│ Props: password (computed from form values) |
||||
|
│ Output: score 0-4, label text, color-coded bar |
||||
|
│ |
||||
|
└─ CaptchaField |
||||
|
Props: svg (v-html), loading |
||||
|
Emits: refresh |
||||
|
``` |
||||
|
|
||||
|
## Component Specifications |
||||
|
|
||||
|
### PasswordInput |
||||
|
|
||||
|
| Prop | Type | Default | Purpose | |
||||
|
|------|------|---------|---------| |
||||
|
| modelValue | string | required | v-model binding | |
||||
|
| label | string | "密码" | Field label | |
||||
|
| placeholder | string | "请输入密码" | Placeholder | |
||||
|
| disabled | boolean | false | Disable during submission | |
||||
|
|
||||
|
| Emit | Payload | Purpose | |
||||
|
|------|---------|---------| |
||||
|
| update:modelValue | string | v-model update | |
||||
|
|
||||
|
States: default, showing password, focused (ring), error (red border + message), disabled. |
||||
|
|
||||
|
### PasswordStrength |
||||
|
|
||||
|
| Prop | Type | Purpose | |
||||
|
|------|------|---------| |
||||
|
| password | string | Password to evaluate | |
||||
|
|
||||
|
Scoring algorithm: |
||||
|
- **Empty**: Nothing rendered |
||||
|
- **Weak (1)**: Only digits or only letters, < 8 chars — red 1/4 bar |
||||
|
- **Fair (2)**: Letters + digits, >= 8 chars — amber 2/4 bar |
||||
|
- **Strong (3)**: Upper + lower + digits, >= 10 chars — green 3/4 bar |
||||
|
- **Very Strong (4)**: Upper + lower + digits + special chars, >= 12 chars — emerald 4/4 bar |
||||
|
|
||||
|
### CaptchaField |
||||
|
|
||||
|
| Prop | Type | Purpose | |
||||
|
|------|------|---------| |
||||
|
| svg | string | SVG markup rendered via v-html | |
||||
|
| loading | boolean | Show spinner, disable input during refresh | |
||||
|
|
||||
|
| Emit | Payload | Purpose | |
||||
|
|------|---------|---------| |
||||
|
| refresh | none | Request new captcha | |
||||
|
|
||||
|
States: default (SVG + input + refresh button), refreshing (spinner + dimmed), load error (error message + retry link). |
||||
|
|
||||
|
### RegisterForm Lifecycle |
||||
|
|
||||
|
1. **Idle**: Form editable, submit button enabled |
||||
|
2. **Validating**: Client-side Zod via UForm, field-level errors |
||||
|
3. **Submitting**: Button shows loading spinner + "注册中...", all inputs/buttons disabled |
||||
|
4. **Failure**: Field-level errors (via setFieldError) or generic toast |
||||
|
5. **Success**: Card content replaced with success state (check icon + "注册成功" + "去登录" link) |
||||
|
|
||||
|
## Schema Deduplication |
||||
|
|
||||
|
Extract the Zod schema from both `register.vue` (client inline) and `server/utils/auth/validation.ts` into a single shared module: |
||||
|
|
||||
|
``` |
||||
|
packages/shared/auth-schema.ts |
||||
|
exports: registerSchema |
||||
|
used by: client (UForm validation), server (safeParse) |
||||
|
``` |
||||
|
|
||||
|
## HTTP Client Consistency |
||||
|
|
||||
|
Replace raw `$fetch` in register.vue with `useHttpFetch` from `app/utils/http/factory.ts`, matching the pattern used elsewhere in the app. The `useHttpFetch` composable auto-unwraps the `{ code, message, data }` envelope. |
||||
|
|
||||
|
## Error Handling |
||||
|
|
||||
|
### Response Format Extension |
||||
|
|
||||
|
Existing envelope unchanged. Add optional `field` in `data` for field-level errors: |
||||
|
|
||||
|
```json |
||||
|
// Username conflict → field-level |
||||
|
{ "code": 1, "message": "用户名已存在", "data": { "field": "username" } } |
||||
|
|
||||
|
// Captcha wrong → field-level |
||||
|
{ "code": 1, "message": "验证码错误", "data": { "field": "captchaText" } } |
||||
|
|
||||
|
// Captcha expired → field-level |
||||
|
{ "code": 1, "message": "验证码已过期", "data": { "field": "captchaText" } } |
||||
|
|
||||
|
// Unknown → generic toast |
||||
|
{ "code": 1, "message": "服务器内部错误", "data": null } |
||||
|
``` |
||||
|
|
||||
|
### Error Layers |
||||
|
|
||||
|
| Layer | Scenario | Handling | UI | |
||||
|
|-------|----------|----------|-----| |
||||
|
| Client validation | Format, length, mismatch | UForm + Zod, no request | Field-level red text | |
||||
|
| Server business | Duplicate username, wrong captcha | API returns field code | setFieldError or toast | |
||||
|
| Network | Timeout, offline, 500 | try/catch on useHttpFetch | Toast "网络异常,请稍后重试" | |
||||
|
| Mount failure | Captcha load fails | try/catch in onMounted | "加载失败,点此重试" in captcha area | |
||||
|
|
||||
|
## Success Flow |
||||
|
|
||||
|
After successful registration, instead of navigating to `/login?registered=1` (which does not exist yet), show an inline success state within the card: |
||||
|
- Green check icon |
||||
|
- "注册成功" heading |
||||
|
- Link/button: "去登录" |
||||
|
|
||||
|
The existing redirect behavior is kept as a fallback once the login page exists. |
||||
|
|
||||
|
## Testing Strategy |
||||
|
|
||||
|
### Unit Tests (Vitest) |
||||
|
- `auth-schema.ts`: Zod validation pass/fail for various inputs |
||||
|
- `PasswordStrength`: Scoring logic for each strength level |
||||
|
- `server/utils/auth/validation.ts`: Server-side schema validation |
||||
|
|
||||
|
### Component Tests (@nuxt/test-utils + @vue/test-utils) |
||||
|
- `PasswordInput`: Show/hide toggle, v-model binding, disabled state |
||||
|
- `PasswordStrength`: Renders correct color and label for each password input |
||||
|
- `CaptchaField`: SVG rendering, refresh emit, loading state |
||||
|
- `RegisterForm`: Form validation, submission flow, disabled state during loading |
||||
|
|
||||
|
## Visual Polish |
||||
|
|
||||
|
- Optimize card vertical centering and form field spacing |
||||
|
- Softer card shadow and border radius |
||||
|
- Add "已有账号?去登录" + home page links below the form |
||||
|
- Captcha refresh button shows rotation animation during loading |
||||
|
|
||||
|
## What's NOT Included |
||||
|
|
||||
|
- Login page (separate feature) |
||||
|
- Session/JWT creation on registration (separate feature) |
||||
|
- Email verification (separate feature) |
||||
|
- Social login (separate feature) |
||||
|
- Rate limiting (separate feature, needs infrastructure decisions) |
||||
|
- Captcha storage migration from in-memory Map (low traffic, Map is sufficient for now) |
||||
Loading…
Reference in new issue