You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

6.6 KiB

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:

// 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)