Files
fangke/docs/superpowers/plans/2026-09-17-login-keji-visual-v1.md
T

506 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 登录页客迹视觉 V1 对齐 — 实现计划
> **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:** 将 admin 登录页从 Art Design Pro 模板原版重做为客迹视觉 V1(深蓝品牌区 + 浅色表单区双栏),对齐设计稿 assets/design/keji-admin-v1/03-login.png。
**Architecture:** 重写三页共用组件 LoginLeftView.vue(背景图 + logo + 标语叠加层),简化 login/index.vue(移除 AuthTopBar/账号下拉/注册与忘记密码链接)并重写 style.css(固定浅色 + Element Plus 变量覆盖防暗色污染),同步更新 zh/en i18n 文案。
**Tech Stack:** Vue3 + TS + Element Plus + Tailwind(Art Design Pro 模板),Playwright 视觉验证。
**Spec:** docs/superpowers/specs/2026-09-17-login-keji-visual-v1-design.md(含全部决策记录与色值)
**TDD 说明:** 纯视觉重构、登录逻辑零改动(spec 明确),无新增单元测试。验证 = Playwright 截图对照设计稿 + `pnpm build` + 现有测试不受影响。
**工作目录:** 全程在 `admin/` 下操作(git 仓库根为 D:\project\cms-cj,分支 m1)。
---
### Task 1: i18n 文案更新
**Files:**
- Modify: `admin/src/locales/langs/zh.json:151-179`(login 对象)
- Modify: `admin/src/locales/langs/en.json:151-179`(login 对象)
- [ ] **Step 1: 更新 zh.json 的 login 对象**
将 `zh.json` 中 `"login"` 对象内以下 key 的值替换(其余 key 原样保留):
```json
"leftView": {
"title": "看见来访,读懂客迹",
"subTitle": "网站访问与客户旅程分析",
"slogan": "数据驱动增长 · 洞察创造价值",
"footer": "让每一次来访都有价值"
},
"title": "欢迎登录客迹",
"subTitle": "登录您的独立数据工作台",
```
并在 `"placeholder"` 对象同级新增三个 key(放在 `"btnText"` 之后):
```json
"accountLabel": "账号",
"passwordLabel": "密码",
"adminAssigned": "账号由管理员分配",
```
同时把 `"rememberPwd": "记住密码"` 改为 `"rememberPwd": "保持登录"`。
删除 `"noAccount"` 和 `"register"` 两个 key(入口已移除)。
- [ ] **Step 2: 更新 en.json 的 login 对象**
`en.json` 同结构对应更新:
```json
"leftView": {
"title": "See every visit, understand every journey",
"subTitle": "Website visit & customer journey analytics",
"slogan": "Data-driven growth · Insight-driven value",
"footer": "Make every visit count"
},
"title": "Welcome to KEJI",
"subTitle": "Sign in to your data workspace",
```
新增:
```json
"accountLabel": "Account",
"passwordLabel": "Password",
"adminAssigned": "Accounts are assigned by the administrator",
```
`"rememberPwd": "Stay signed in"`;删除 `"noAccount"`、`"register"`。
**注意:** en.json 删除 noAccount/register 后,`register/index.vue` 等其他文件若引用 `$t('login.register')` 会显示 key 本身。先全局搜索确认只有 login/index.vue 引用(本任务后 Task 3 会移除该引用;若搜索发现其他引用处则保留 key 不删)。
- [ ] **Step 3: 验证 JSON 合法**
Run: `python -c "import json; json.load(open('src/locales/langs/zh.json', encoding='utf-8')); json.load(open('src/locales/langs/en.json', encoding='utf-8')); print('OK')"`
Expected: `OK`
- [ ] **Step 4: Commit**
```bash
git add src/locales/langs/zh.json src/locales/langs/en.json
git commit -m "feat(admin): 登录页i18n文案对齐客迹V1"
```
---
### Task 2: 重写 LoginLeftView.vue(左侧品牌区)
**Files:**
- Modify: `admin/src/components/core/views/login/LoginLeftView.vue`(整文件重写)
资源(已存在,无需新增):`src/assets/images/logo-w.png`(1944×809 透明底白字)、`src/assets/images/login/login-left-background.png`(1196×1315)。
- [ ] **Step 1: 整文件替换为以下内容**
```vue
<!-- 登录、注册、忘记密码左侧品牌区(客迹视觉V1) -->
<template>
<div class="login-left-view" :style="{ backgroundImage: `url(${bg})` }">
<img class="logo" :src="logoWhite" alt="客迹 KEJI ANALYTICS" />
<div class="text-wrap">
<h1>{{ $t('login.leftView.title') }}</h1>
<p class="sub-title">{{ $t('login.leftView.subTitle') }}</p>
<span class="accent-bar"></span>
<p class="slogan">{{ $t('login.leftView.slogan') }}</p>
</div>
<p class="footer">{{ $t('login.leftView.footer') }}</p>
</div>
</template>
<script setup lang="ts">
import bg from '@imgs/login/login-left-background.png'
import logoWhite from '@imgs/logo-w.png'
</script>
<style lang="scss" scoped>
.login-left-view {
position: relative;
box-sizing: border-box;
width: 58vw;
height: 100%;
overflow: hidden;
background-color: #0b1630; /* 图片加载前/失败兜底 */
background-position: center;
background-size: cover;
background-repeat: no-repeat;
.logo {
position: absolute;
top: 56px;
left: 64px;
z-index: 10;
display: block;
width: auto;
height: 52px;
}
.text-wrap {
position: absolute;
top: 24%;
left: 64px;
z-index: 10;
h1 {
max-width: 80%;
font-size: 38px;
font-weight: 700;
color: #fff;
letter-spacing: 2px;
}
.sub-title {
margin-top: 14px;
font-size: 18px;
color: #dbe6f5;
}
.accent-bar {
display: block;
width: 50px;
height: 6px;
margin-top: 24px;
background: #2563eb;
border-radius: 3px;
}
.slogan {
margin-top: 24px;
font-size: 14px;
color: #a8c0e8;
letter-spacing: 4px;
}
}
.footer {
position: absolute;
bottom: 56px;
left: 64px;
z-index: 10;
font-size: 14px;
color: #7d94c4;
letter-spacing: 8px;
}
@media only screen and (width <= 1180px) {
display: none;
}
}
</style>
```
要点:背景图走 JS import + inline style(Vite 哈希 + 懒加载友好);`hideContent` prop 删除(已确认无调用方);`<=1600px` 中间宽度规则不再保留,仅 58vw 一档;`<=1180px` 直接 display:none。
- [ ] **Step 2: 类型检查编译通过**
Run: `pnpm typecheck || pnpm run build`(若项目无 typecheck 脚本则跳过,Task 5 统一 build 验证)
Expected: 无错误(LoginLeftView 无外部引用变化,register/forget-password 自动获得新品牌区)
- [ ] **Step 3: Commit**
```bash
git add src/components/core/views/login/LoginLeftView.vue
git commit -m "feat(admin): 登录左侧品牌区重做为客迹V1深蓝视觉"
```
---
### Task 3: 简化 login/index.vue + 重写 style.css(右侧表单)
**Files:**
- Modify: `admin/src/views/auth/login/index.vue`(模板 + script + style)
- Modify: `admin/src/views/auth/login/style.css`(整文件重写)
- [ ] **Step 1: 替换 index.vue 的 template 部分**
```vue
<!-- 登录页面 -->
<template>
<div class="flex w-full h-screen">
<LoginLeftView />
<div class="relative flex-1 auth-right-bg">
<div class="auth-right-wrap">
<div class="form">
<h3 class="title">{{ $t('login.title') }}</h3>
<p class="sub-title">{{ $t('login.subTitle') }}</p>
<ElForm
ref="formRef"
:model="formData"
:rules="rules"
label-position="top"
:key="formKey"
@keyup.enter="handleSubmit"
class="login-form"
>
<ElFormItem :label="$t('login.accountLabel')" prop="username">
<ElInput
class="custom-height"
:placeholder="$t('login.placeholder.username')"
:prefix-icon="User"
v-model.trim="formData.username"
/>
</ElFormItem>
<ElFormItem :label="$t('login.passwordLabel')" prop="password">
<ElInput
class="custom-height"
:placeholder="$t('login.placeholder.password')"
:prefix-icon="Lock"
v-model.trim="formData.password"
type="password"
autocomplete="off"
show-password
/>
</ElFormItem>
<!-- 推拽验证 -->
<div class="relative pb-5 mt-4">
<div
class="relative z-[2] overflow-hidden select-none rounded-lg border border-transparent tad-300"
:class="{ '!border-[#FF4E4F]': !isPassing && isClickPass }"
>
<ArtDragVerify
ref="dragVerify"
v-model:value="isPassing"
:text="$t('login.sliderText')"
textColor="#4D5875"
:successText="$t('login.sliderSuccessText')"
progressBarBg="var(--main-color)"
background="#E8EEF6"
handlerBg="#FFFFFF"
:height="56"
radius="10px"
/>
</div>
<p
class="absolute top-0 z-[1] px-px mt-2 text-xs text-[#f56c6c] tad-300"
:class="{ 'translate-y-10': !isPassing && isClickPass }"
>
{{ $t('login.placeholder.slider') }}
</p>
</div>
<div class="flex-cb mt-2 text-sm">
<ElCheckbox v-model="formData.rememberPassword">{{
$t('login.rememberPwd')
}}</ElCheckbox>
</div>
<div class="mt-6">
<ElButton
class="w-full custom-height"
type="primary"
@click="handleSubmit"
:loading="loading"
v-ripple
>
{{ $t('login.btnText') }}
</ElButton>
</div>
<div class="admin-assigned">
<span class="line"></span>
<span class="text">{{ $t('login.adminAssigned') }}</span>
<span class="line"></span>
</div>
</ElForm>
</div>
</div>
</div>
</div>
</template>
```
- [ ] **Step 2: 修改 index.vue 的 script 部分**
1. 新增 import:`import { User, Lock } from '@element-plus/icons-vue'`
2. 删除:`import { useSettingStore } from '@/store/modules/setting'`、`const settingStore = useSettingStore()`、`const { isDark } = storeToRefs(settingStore)`(滑块已固定浅色,isDark 不再使用)
3. 删除:`type AccountKey`、`export interface Account`、`const accounts = computed...` 整块、`setupAccount` 函数、`onMounted(() => { setupAccount('admin') })`
4. `formData` 初始化改为 `username: 'admin'`(保持 M1 单管理员预填行为):
```ts
const formData = reactive({
account: '',
username: 'admin',
password: '',
rememberPassword: true
})
```
5. 其余逻辑(handleSubmit、resetDragVerify、showLoginSuccessNotice、formKey/locale watch)**零改动**。
- [ ] **Step 3: index.vue 底部 scoped style 简化**
删除 `:deep(.el-select__wrapper)` 覆盖块(ElSelect 已移除),`@import './style.css'` 保留。
- [ ] **Step 4: style.css 整文件替换**
```css
@reference '@styles/core/tailwind.css';
/* 授权页右侧区域(客迹V1:固定浅色,不随暗色主题变化) */
.auth-right-bg {
/* 覆盖 Element Plus 暗色变量,防止 html.dark 时表单控件变暗 */
--el-bg-color: #ffffff;
--el-bg-color-overlay: #ffffff;
--el-fill-color-blank: #ffffff;
--el-fill-color-light: #f5f7fa;
--el-text-color-primary: #1a1a1a;
--el-text-color-regular: #303133;
--el-border-color: #dcdfe6;
--el-border-color-light: #e4e7ed;
--el-border-color-lighter: #ebeef5;
background-color: #f7f8fa;
}
.auth-right-wrap {
@apply absolute inset-0 w-[440px] h-[680px] py-[5px] m-auto overflow-hidden
max-sm:px-7 max-sm:w-full
animate-[slideInRight_0.6s_cubic-bezier(0.25,0.46,0.45,0.94)_forwards]
max-md:animate-none;
.form {
@apply h-full py-[40px];
}
.title {
@apply text-[#1A1A1A] text-[32px] font-semibold text-center;
}
.sub-title {
@apply mt-[10px] text-[#8A94A6] text-[15px] text-center;
}
.custom-height {
@apply !h-[56px];
}
/* 表单标签样式 */
:deep(.el-form-item__label) {
color: #1a1a1a;
font-weight: 600;
}
/* 账号由管理员分配 */
.admin-assigned {
@apply flex-cc gap-3 mt-8;
.line {
@apply inline-block w-6 h-px bg-[#D3D9E3];
}
.text {
@apply text-[13px] text-[#9AA3B2];
}
}
}
/* 滑入动画 */
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
```
- [ ] **Step 5: 验证无残留引用**
Run: `grep -rn "setupAccount\|AuthTopBar\|noAccount\|ElSelect" src/views/auth/login/`
Expected: 无输出(login 页内已无这些引用;register/forget-password 中的 AuthTopBar 不受影响)
- [ ] **Step 6: Commit**
```bash
git add src/views/auth/login/index.vue src/views/auth/login/style.css
git commit -m "feat(admin): 登录表单区对齐客迹V1——移除账号下拉/注册/忘记密码入口,固定浅色"
```
---
### Task 4: 视觉验证(Playwright 截图对照设计稿)
**Files:**
- 无代码改动;产出截图到 `admin/exports/`(或临时目录,不入库)
- [ ] **Step 1: 启动 dev server**
Run: `cd admin && pnpm dev`(后台运行)
Expected: Vite 启动在 http://localhost:3006
- [ ] **Step 2: Playwright 三档截图**
用 playwright MCP 打开 `http://localhost:3006/#/login`(或路由实际形态,先 navigate 探测),分别设置视口并截图:
1. 1920×1080(桌面标准)
2. 1440×900(小桌面)
3. 375×812(移动,验证左侧隐藏 + 表单单栏)
- [ ] **Step 3: 对照检查清单**
- [ ] 左侧 58vw 深蓝背景图完整覆盖、无拉伸变形
- [ ] logo-w.png 白字清晰可见(左上角)
- [ ] 主标语/副标题/短横线/口号/底部文案齐全、层级与留白接近设计稿
- [ ] 右侧 #F7F8FA 底、标题居中、输入框 56px 高带图标
- [ ] 滑块浅蓝灰底、文字 #4D5875 可读、验证通过后主色进度条
- [ ] 登录按钮全宽主色
- [ ] 底部"— 账号由管理员分配 —"
- [ ] 375px:左侧隐藏、表单居中占满
- [ ] 浏览器切 html.dark 类(Playwright evaluate `document.documentElement.classList.add('dark')`)后右侧仍浅色
发现问题 → 回到对应 Task 修正(样式微调不新增任务)。
- [ ] **Step 4: 实际登录冒烟**
本地 dev 直连生产 API 不可用时仅验证 UI 交互:滑块可拖动、按钮可点、空值提交报错提示正常。(真实登录冒烟放到 Task 5 线上验证时用管理员账号完成,密码不入库——问用户索取或由用户自测)
---
### Task 5: 构建验证 + 部署上线
- [ ] **Step 1: pnpm build**
Run: `cd admin && pnpm build`
Expected: 构建成功,dist/ 生成新哈希文件
- [ ] **Step 2: 标准部署流程(项目约定:测试通过后直接上线,无需等确认)**
```bash
cd admin && pnpm build
tar -czf /tmp/admin-dist.tar.gz dist/
scp -i ~/.ssh/id_ed25519 -J root@8.152.213.143 /tmp/admin-dist.tar.gz root@47.254.176.10:/tmp/
ssh -i ~/.ssh/id_ed25519 -J root@8.152.213.143 root@47.254.176.10 "cd /opt/cms-cj && tar -xzf /tmp/admin-dist.tar.gz && rm /tmp/admin-dist.tar.gz"
```
(原地覆盖 dist 内容,bind mount 不受影响,无需重启容器)
- [ ] **Step 3: 线上验证**
Playwright 打开 https://tracker.12418113.xyz 登录页,截图确认新视觉生效;记录 `index-xxx.js` 哈希作为版本号。
- [ ] **Step 4: 汇报**
汇报内容:完成项清单、三档截图、线上版本哈希、验证结果。
---
## 风险提示(执行者注意)
1. **严格不动服务器其他项目**(1Panel、其他网站容器)——只解压覆盖 /opt/cms-cj 下的前端文件。
2. `05-login-left-background.png` 为 AI 去字重建背景,与设计稿非逐像素一致是**预期内的**,不要试图"修正"背景图。
3. register/forget-password 页共用 LoginLeftView,视觉同步变化是**预期行为**(spec 决策 4)。
4. 若 grep 发现 `login.noAccount`/`login.register` 有 login 页之外的引用,保留对应 i18n key 不删,并在 commit message 中说明。