zhou zhou
2 天以前 92cb389a2d15b3d83385aebadbc60ca0976b1081
docs: add rsf-design phase-1 implementation plan
1个文件已添加
454 ■■■■■ 已修改文件
docs/superpowers/plans/2026-03-28-rsf-design-replace-rsf-admin-phase-1.md 454 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/superpowers/plans/2026-03-28-rsf-design-replace-rsf-admin-phase-1.md
New file
@@ -0,0 +1,454 @@
# rsf-design Replace rsf-admin Phase 1 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make `rsf-design` independently handle login, auth state, user info, menu loading, dynamic routing, homepage, and button permissions against the existing `rsf-server` APIs while `rsf-admin` remains available as the old entry.
**Architecture:** Replace the Art Design Pro template auth/menu assumptions with the real `rsf-admin` protocol. Introduce one backend-menu adapter that converts `/auth/menu` payloads into the route shape expected by the existing dynamic router, and keep unpublished resources out of the new menu entirely. Store token, user info, roles, and button permissions in the existing Pinia stores and drive guards/directives from that single source of truth.
**Tech Stack:** Vue 3, Vite, Pinia, Vue Router, Element Plus, Art Design Pro, Axios, Node `node:test`, pnpm
---
## File Map
- Create: `rsf-design/src/router/adapters/backendMenuAdapter.js`
- Create: `rsf-design/tests/backend-menu-adapter.test.mjs`
- Create: `rsf-design/tests/auth-contract.test.mjs`
- Modify: `rsf-design/package.json`
- Modify: `rsf-design/src/api/auth.js`
- Modify: `rsf-design/src/store/modules/user.js`
- Modify: `rsf-design/src/views/auth/login/index.vue`
- Modify: `rsf-design/src/router/core/MenuProcessor.js`
- Modify: `rsf-design/src/router/core/ComponentLoader.js`
- Modify: `rsf-design/src/router/guards/beforeEach.js`
- Modify: `rsf-design/src/hooks/core/useAuth.js`
- Modify: `rsf-design/src/directives/core/auth.js`
- Modify: `rsf-design/src/directives/core/roles.js`
- Modify: `rsf-design/src/config/index.js`
- Modify: `rsf-design/src/views/dashboard/console/index.vue`
- Optional verify-only reads: `rsf-admin/src/api/auth/index.js`, `rsf-admin/src/page/ResourceContent.js`, `docs/superpowers/specs/2026-03-28-rsf-design-replace-rsf-admin-design.md`
### Task 1: Establish a Minimal Test Harness for the New Protocol Path
**Files:**
- Modify: `rsf-design/package.json`
- Create: `rsf-design/tests/auth-contract.test.mjs`
- Create: `rsf-design/tests/backend-menu-adapter.test.mjs`
- [ ] **Step 1: Write the failing auth contract test**
```js
import assert from 'node:assert/strict'
import test from 'node:test'
import { buildLoginPayload, normalizeLoginResponse } from '../src/api/auth.js'
test('buildLoginPayload matches the rsf-server login contract', () => {
  assert.deepEqual(buildLoginPayload({ username: 'demo', password: '123456' }), {
    username: 'demo',
    password: '123456'
  })
})
test('normalizeLoginResponse extracts the real token fields', () => {
  assert.deepEqual(
    normalizeLoginResponse({
      code: 200,
      data: { accessToken: 'abc', user: { id: 1 } }
    }),
    { accessToken: 'abc', refreshToken: '', user: { id: 1 } }
  )
})
```
- [ ] **Step 2: Run the auth contract test to verify it fails**
Run: `node --test rsf-design/tests/auth-contract.test.mjs`
Expected: FAIL because `buildLoginPayload` and `normalizeLoginResponse` do not exist yet.
- [ ] **Step 3: Write the failing backend menu adapter test**
```js
import assert from 'node:assert/strict'
import test from 'node:test'
import { adaptBackendMenuTree } from '../src/router/adapters/backendMenuAdapter.js'
test('adapts a backend menu leaf into a routable phase-1 page', () => {
  const result = adaptBackendMenuTree([
    {
      id: 10,
      path: 'system',
      name: 'System',
      children: [
        { id: 11, path: 'user-login', component: 'userLogin', title: '登录日志' }
      ]
    }
  ])
  assert.equal(result[0].children[0].component, '/system/user-login')
})
```
- [ ] **Step 4: Run the backend menu adapter test to verify it fails**
Run: `node --test rsf-design/tests/backend-menu-adapter.test.mjs`
Expected: FAIL because the adapter module does not exist yet.
- [ ] **Step 5: Add a stable repo-local test command**
```json
{
  "scripts": {
    "test": "node --test tests"
  }
}
```
- [ ] **Step 6: Re-run both tests from the `rsf-design` root**
Run: `pnpm test`
Expected: FAIL, but the command should execute the new test files instead of erroring due to a missing script.
- [ ] **Step 7: Commit**
```bash
git add rsf-design/package.json rsf-design/tests/auth-contract.test.mjs rsf-design/tests/backend-menu-adapter.test.mjs
git commit -m "test: add rsf-design phase-1 protocol tests"
```
### Task 2: Replace Template Auth API Assumptions with the Real rsf-server Contract
**Files:**
- Modify: `rsf-design/src/api/auth.js`
- Modify: `rsf-design/src/store/modules/user.js`
- Test: `rsf-design/tests/auth-contract.test.mjs`
- [ ] **Step 1: Make the auth contract test more explicit before implementation**
```js
test('normalizeUserInfo returns roles and buttons arrays safely', () => {
  assert.deepEqual(
    normalizeUserInfo({ userId: 1, roles: ['R_ADMIN'], buttons: ['add'] }),
    { userId: 1, roles: ['R_ADMIN'], buttons: ['add'] }
  )
})
```
- [ ] **Step 2: Run the auth contract test again**
Run: `node --test rsf-design/tests/auth-contract.test.mjs`
Expected: FAIL with missing exports or assertion mismatch.
- [ ] **Step 3: Implement the minimal real-contract helpers and API calls**
```js
export function buildLoginPayload({ username, password }) {
  return { username, password }
}
export function normalizeLoginResponse(payload) {
  return {
    accessToken: payload?.data?.accessToken || '',
    refreshToken: payload?.data?.refreshToken || '',
    user: payload?.data?.user || {}
  }
}
export function normalizeUserInfo(data) {
  return {
    ...data,
    roles: Array.isArray(data?.roles) ? data.roles : [],
    buttons: Array.isArray(data?.buttons) ? data.buttons : []
  }
}
```
- [ ] **Step 4: Point the auth API at the real endpoints**
```js
function fetchLogin(params) {
  return request.post({ url: '/login', params: buildLoginPayload(params) })
}
function fetchGetUserInfo() {
  return request.get({ url: '/auth/user' })
}
function fetchGetMenuList() {
  return request.get({ url: '/auth/menu' })
}
```
- [ ] **Step 5: Update the user store token setters to persist the real fields**
```js
const setToken = (newAccessToken, newRefreshToken = '') => {
  accessToken.value = newAccessToken || ''
  refreshToken.value = newRefreshToken || ''
}
```
- [ ] **Step 6: Run the auth contract test to verify it passes**
Run: `node --test rsf-design/tests/auth-contract.test.mjs`
Expected: PASS.
- [ ] **Step 7: Commit**
```bash
git add rsf-design/src/api/auth.js rsf-design/src/store/modules/user.js rsf-design/tests/auth-contract.test.mjs
git commit -m "feat: align rsf-design auth api with rsf-server"
```
### Task 3: Rebuild the Login Flow Around the Real Contract
**Files:**
- Modify: `rsf-design/src/views/auth/login/index.vue`
- Modify: `rsf-design/src/config/index.js`
- Test: `rsf-design/tests/auth-contract.test.mjs`
- [ ] **Step 1: Write a failing login-page-oriented auth test**
```js
test('normalizeLoginResponse accepts the old backend accessToken shape', () => {
  const result = normalizeLoginResponse({
    code: 200,
    data: { accessToken: 'token-1', refreshToken: '', user: { username: 'admin' } }
  })
  assert.equal(result.accessToken, 'token-1')
})
```
- [ ] **Step 2: Run the auth contract test**
Run: `node --test rsf-design/tests/auth-contract.test.mjs`
Expected: PASS if the previous task is complete. Use this as a safety baseline before editing the login page.
- [ ] **Step 3: Remove template-only login behavior**
```vue
const formData = reactive({
  username: '',
  password: '',
  rememberPassword: true
})
const handleSubmit = async () => {
  const payload = normalizeLoginResponse(await fetchLogin(formData))
  userStore.setToken(payload.accessToken, payload.refreshToken)
  userStore.setLoginStatus(true)
  if (payload.user && Object.keys(payload.user).length > 0) userStore.setUserInfo(payload.user)
  router.push(route.query.redirect || '/')
}
```
- [ ] **Step 4: Remove demo account selectors and align visible copy**
```js
systemInfo: {
  name: 'RSF Design'
}
```
- [ ] **Step 5: Run lint on the edited login and config files**
Run: `pnpm lint rsf-design/src/views/auth/login/index.vue rsf-design/src/config/index.js`
Expected: PASS, or no syntax errors if the lint command needs to be run as `pnpm lint`.
- [ ] **Step 6: Run a quick build smoke test**
Run: `pnpm --dir rsf-design build`
Expected: PASS.
- [ ] **Step 7: Commit**
```bash
git add rsf-design/src/views/auth/login/index.vue rsf-design/src/config/index.js
git commit -m "feat: rebuild rsf-design login flow for rsf-server"
```
### Task 4: Introduce the Backend Menu Adapter and Drive Dynamic Routing From It
**Files:**
- Create: `rsf-design/src/router/adapters/backendMenuAdapter.js`
- Modify: `rsf-design/src/router/core/MenuProcessor.js`
- Modify: `rsf-design/src/router/core/ComponentLoader.js`
- Test: `rsf-design/tests/backend-menu-adapter.test.mjs`
- [ ] **Step 1: Expand the failing backend menu adapter test with realistic constraints**
```js
test('drops unpublished resources from the phase-1 menu tree', () => {
  const result = adaptBackendMenuTree([
    {
      id: 1,
      path: 'system',
      children: [
        { id: 2, path: 'user-login', component: 'userLogin', title: '登录日志' },
        { id: 3, path: 'host', component: 'host', title: '主机管理' }
      ]
    }
  ])
  assert.equal(result[0].children.length, 1)
  assert.equal(result[0].children[0].meta.title, '登录日志')
})
```
- [ ] **Step 2: Run the backend menu adapter test**
Run: `node --test rsf-design/tests/backend-menu-adapter.test.mjs`
Expected: FAIL.
- [ ] **Step 3: Implement the adapter with an explicit phase-1 resource map**
```js
const PHASE_1_COMPONENTS = {
  console: '/dashboard/console',
  user: '/system/user',
  role: '/system/role',
  menu: '/system/menu',
  userLogin: '/system/user-login'
}
export function adaptBackendMenuTree(menuTree) {
  // normalize ids, names, meta, child paths, and component mappings here
}
```
- [ ] **Step 4: Change `MenuProcessor` to fetch `/auth/menu` through the auth API and hand off to the adapter**
```js
async processBackendMenu() {
  const list = await fetchGetMenuList()
  return adaptBackendMenuTree(list)
}
```
- [ ] **Step 5: Make `ComponentLoader` treat adapted component strings as final Vue view paths only**
```js
load(componentPath) {
  const fullPath = `../../views${componentPath}.vue`
  const fullPathWithIndex = `../../views${componentPath}/index.vue`
  // no resource-name guessing here
}
```
- [ ] **Step 6: Run the backend adapter test to verify it passes**
Run: `node --test rsf-design/tests/backend-menu-adapter.test.mjs`
Expected: PASS.
- [ ] **Step 7: Commit**
```bash
git add rsf-design/src/router/adapters/backendMenuAdapter.js rsf-design/src/router/core/MenuProcessor.js rsf-design/src/router/core/ComponentLoader.js rsf-design/tests/backend-menu-adapter.test.mjs
git commit -m "feat: adapt backend menus for rsf-design dynamic routing"
```
### Task 5: Wire the Guard, Permissions, and Homepage to the New Data Flow
**Files:**
- Modify: `rsf-design/src/router/guards/beforeEach.js`
- Modify: `rsf-design/src/hooks/core/useAuth.js`
- Modify: `rsf-design/src/directives/core/auth.js`
- Modify: `rsf-design/src/directives/core/roles.js`
- Modify: `rsf-design/src/views/dashboard/console/index.vue`
- [ ] **Step 1: Add a failing permission-focused adapter/auth test**
```js
test('normalizeUserInfo preserves buttons for permission checks', () => {
  const result = normalizeUserInfo({ buttons: ['add', 'edit'] })
  assert.deepEqual(result.buttons, ['add', 'edit'])
})
```
- [ ] **Step 2: Run the auth contract test**
Run: `node --test rsf-design/tests/auth-contract.test.mjs`
Expected: PASS before editing permission consumers.
- [ ] **Step 3: Update the route guard to fetch user info and menu from the real chain only once per session rebuild**
```js
const data = normalizeUserInfo(await fetchGetUserInfo())
userStore.setUserInfo(data)
const menuList = await menuProcessor.getMenuList()
```
- [ ] **Step 4: Make `useAuth` and directives read from store-backed user info and current route meta consistently**
```js
const backendAuthList = Array.isArray(route.meta.authList) ? route.meta.authList : []
const frontendAuthList = Array.isArray(info.value?.buttons) ? info.value.buttons : []
```
- [ ] **Step 5: Replace the template dashboard copy with an RSF phase-1 landing page**
```vue
<template>
  <div class="art-full-height">
    <ElRow :gutter="20" class="mb-5">
      <ElCol :xs="24" :md="8">
        <ArtStatsCard title="当前阶段" count="1" description="运行骨架替换" icon="ri:rocket-line" />
      </ElCol>
    </ElRow>
  </div>
</template>
```
- [ ] **Step 6: Run build and the full test suite**
Run: `pnpm --dir rsf-design test`
Expected: PASS.
Run: `pnpm --dir rsf-design build`
Expected: PASS.
- [ ] **Step 7: Commit**
```bash
git add rsf-design/src/router/guards/beforeEach.js rsf-design/src/hooks/core/useAuth.js rsf-design/src/directives/core/auth.js rsf-design/src/directives/core/roles.js rsf-design/src/views/dashboard/console/index.vue
git commit -m "feat: wire rsf-design phase-1 auth, permissions, and homepage"
```
### Task 6: Verify the Parallel-Run Entry and Record the Manual Acceptance Checks
**Files:**
- Modify: `docs/superpowers/specs/2026-03-28-rsf-design-replace-rsf-admin-design.md` (only if implementation changes the agreed contract)
- Verify: `rsf-design/.env`
- Verify: `rsf-design/vite.config.js`
- [ ] **Step 1: Confirm the new entry still runs in backend mode**
Run: `Get-Content rsf-design/.env`
Expected: `VITE_ACCESS_MODE = backend`
- [ ] **Step 2: Start the new frontend locally**
Run: `pnpm --dir rsf-design dev`
Expected: Vite starts successfully and prints the local URL.
- [ ] **Step 3: Manually verify the phase-1 acceptance path**
Checklist:
- Login with a real `rsf-server` account
- Refresh after login and verify the menu rebuilds
- Visit a protected route directly and verify it resolves after route registration
- Verify hidden buttons remain hidden for a low-permission account
- Verify an unpublished resource does not appear in the new menu
- Verify `rsf-admin` still runs from its old entry unchanged
- [ ] **Step 4: Run the final automated verification**
Run: `pnpm --dir rsf-design test && pnpm --dir rsf-design build`
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add rsf-design/.env rsf-design/vite.config.js
git commit -m "chore: verify rsf-design phase-1 replacement readiness"
```