docs: 登录页客迹V1实现计划——吸收审查建议(滑块文字色硬编码、统一56px、移除明文密码)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,505 @@
|
||||
# 登录页客迹视觉 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 中说明。
|
||||
Reference in New Issue
Block a user