docs:添加按钮简化与自定义弹窗实现计划
This commit is contained in:
@@ -0,0 +1,679 @@
|
||||
# 小程序添加标签按钮简化与自定义弹窗 实现计划
|
||||
|
||||
> **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:** 简化编辑资料页"添加"按钮文字、收紧字数限制到 4 字、标签不换行省略号截断、用自定义深色太空主题弹窗组件替换 `uni.showModal`。
|
||||
|
||||
**Architecture:** 新建 `TagDialog.vue` 组件支持 input / confirm 双模式,样式与小程序深色太空主题一致;编辑资料页和管理页分别引入该组件,重构 `addCustomTag` 和 `confirmDelete` 从 `uni.showModal` 切换到自定义弹窗;标签样式增加 `white-space: nowrap` 三件套防止换行。
|
||||
|
||||
**Tech Stack:** UniApp 3 + Vue 3 Composition API (`<script setup>`),样式用 rpx,深色太空主题(glass-card 毛玻璃 + 紫色渐变 + 金色强调)。
|
||||
|
||||
**Spec 文档:** `docs/superpowers/specs/2026-06-24-tag-add-button-simplify-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 文件结构总览
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `mini-program/src/components/TagDialog.vue` | 自定义弹窗组件,input / confirm 双模式,深色太空主题样式 |
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|---|---|
|
||||
| `mini-program/src/pages/onboarding/index.vue` | 按钮文字简化为"+ 添加"、引入 TagDialog、重构 addCustomTag、字数限制 8→4、toast 文案、`.tag-choice` 不换行 |
|
||||
| `mini-program/src/pages/onboarding/tag-manage.vue` | 引入 TagDialog、重构 confirmDelete、`.tag-text` 不换行 |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 新建 TagDialog.vue 组件
|
||||
|
||||
**目标:** 创建自定义弹窗组件,支持 input(输入)和 confirm(确认)两种模式,样式与小程序深色太空主题一致。
|
||||
|
||||
**Files:**
|
||||
- Create: `mini-program/src/components/TagDialog.vue`
|
||||
|
||||
### Step 1: 创建组件文件
|
||||
|
||||
创建 `mini-program/src/components/TagDialog.vue`,完整内容:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view v-if="visible" class="tag-dialog-mask" @click="onMaskClick">
|
||||
<view class="tag-dialog-card glass-card" @click.stop>
|
||||
<view class="tag-dialog-title-row">
|
||||
<text class="tag-dialog-gold">✦</text>
|
||||
<text class="tag-dialog-title">{{ title }}</text>
|
||||
</view>
|
||||
|
||||
<input
|
||||
v-if="mode === 'input'"
|
||||
class="tag-dialog-input"
|
||||
:value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
placeholder-class="tag-dialog-placeholder"
|
||||
:maxlength="4"
|
||||
@input="onInput"
|
||||
@confirm="onInputConfirm"
|
||||
/>
|
||||
|
||||
<text v-if="mode === 'confirm'" class="tag-dialog-content">{{ content }}</text>
|
||||
|
||||
<view class="tag-dialog-actions">
|
||||
<view class="tag-dialog-btn tag-dialog-btn-cancel" @click="onCancel">
|
||||
<text>取消</text>
|
||||
</view>
|
||||
<view class="tag-dialog-btn tag-dialog-btn-confirm" @click="onConfirm">
|
||||
<text>确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'input'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirm', 'cancel', 'update:modelValue', 'update:visible'])
|
||||
|
||||
const onInput = (event) => {
|
||||
const value = event.detail.value
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
|
||||
const onInputConfirm = () => {
|
||||
emit('confirm', props.modelValue)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
if (props.mode === 'input') {
|
||||
emit('confirm', props.modelValue)
|
||||
} else {
|
||||
emit('confirm')
|
||||
}
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
emit('cancel')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
const onMaskClick = () => {
|
||||
emit('cancel')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tag-dialog-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(3, 2, 13, 0.72);
|
||||
}
|
||||
|
||||
.tag-dialog-card {
|
||||
width: 560rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 36rpx 32rpx 28rpx;
|
||||
border-radius: 22rpx;
|
||||
border: 1rpx solid rgba(155, 110, 255, 0.14);
|
||||
background:
|
||||
radial-gradient(circle at 92% 12%, rgba(104, 66, 255, 0.1), transparent 34%),
|
||||
rgba(10, 13, 43, 0.54);
|
||||
box-shadow: inset 0 0 30rpx rgba(123, 60, 255, 0.05), 0 10rpx 36rpx rgba(0, 0, 0, 0.16);
|
||||
backdrop-filter: blur(24rpx);
|
||||
-webkit-backdrop-filter: blur(24rpx);
|
||||
}
|
||||
|
||||
.tag-dialog-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.tag-dialog-gold {
|
||||
color: #ffd58c;
|
||||
font-size: 26rpx;
|
||||
text-shadow: 0 0 20rpx rgba(255, 202, 125, 0.45);
|
||||
}
|
||||
|
||||
.tag-dialog-title {
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.tag-dialog-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 80rpx;
|
||||
margin-bottom: 28rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 14rpx;
|
||||
border: 1rpx solid rgba(151, 111, 255, 0.22);
|
||||
background: rgba(10, 12, 40, 0.66);
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.tag-dialog-placeholder {
|
||||
color: rgba(214, 204, 235, 0.42);
|
||||
}
|
||||
|
||||
.tag-dialog-content {
|
||||
display: block;
|
||||
margin-bottom: 32rpx;
|
||||
color: rgba(239, 232, 255, 0.86);
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tag-dialog-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tag-dialog-btn {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
border-radius: 14rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tag-dialog-btn-cancel {
|
||||
color: rgba(239, 232, 255, 0.82);
|
||||
border: 1rpx solid rgba(151, 111, 255, 0.42);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.tag-dialog-btn-confirm {
|
||||
color: #fff;
|
||||
border: 1rpx solid rgba(206, 82, 255, 0.95);
|
||||
background: linear-gradient(180deg, rgba(169, 61, 255, 0.62), rgba(107, 41, 206, 0.5));
|
||||
box-shadow: 0 0 18rpx rgba(168, 67, 255, 0.52), inset 0 1rpx 0 rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### Step 2: 提交
|
||||
|
||||
```bash
|
||||
git add mini-program/src/components/TagDialog.vue
|
||||
git commit -m "feat:新增 TagDialog 自定义弹窗组件,深色太空主题样式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 编辑资料页改动(index.vue)
|
||||
|
||||
**目标:** 简化添加按钮文字、引入 TagDialog、重构 addCustomTag 用自定义弹窗、字数限制收紧到 4、标签不换行。
|
||||
|
||||
**Files:**
|
||||
- Modify: `mini-program/src/pages/onboarding/index.vue`
|
||||
|
||||
### Step 1: 模板 — 简化按钮文字
|
||||
|
||||
修改 `mini-program/src/pages/onboarding/index.vue`。
|
||||
|
||||
找到性格标签 panel 末尾占位(约第 146 行):
|
||||
|
||||
```html
|
||||
<text class="tag-choice dashed" @click="addCustomTag('personality')">+ 添加标签</text>
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```html
|
||||
<text class="tag-choice dashed" @click="addCustomTag('personality')">+ 添加</text>
|
||||
```
|
||||
|
||||
找到兴趣爱好 panel 末尾占位(约第 167 行):
|
||||
|
||||
```html
|
||||
<text class="tag-choice dashed" @click="addCustomTag('hobby')">+ 添加兴趣</text>
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```html
|
||||
<text class="tag-choice dashed" @click="addCustomTag('hobby')">+ 添加</text>
|
||||
```
|
||||
|
||||
### Step 2: 模板 — 挂载 TagDialog 组件
|
||||
|
||||
找到 `</scroll-view>` 和 `</view>` 包裹的页面结构末尾(约第 185-186 行):
|
||||
|
||||
```html
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
在 `</scroll-view>` 之后、`</view>` 之前插入 TagDialog 挂载:
|
||||
|
||||
```html
|
||||
</scroll-view>
|
||||
|
||||
<TagDialog
|
||||
v-model="dialogInput"
|
||||
v-model:visible="dialogVisible"
|
||||
:mode="dialogMode"
|
||||
:title="dialogTitle"
|
||||
:placeholder="dialogPlaceholder"
|
||||
:content="dialogContent"
|
||||
@confirm="onDialogConfirm"
|
||||
@cancel="onDialogCancel"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Step 3: script — 引入组件
|
||||
|
||||
找到 import 区(约第 189-193 行):
|
||||
|
||||
```javascript
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from '../../stores/app.js'
|
||||
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
|
||||
```
|
||||
|
||||
在最后一行 import 之后追加:
|
||||
|
||||
```javascript
|
||||
import TagDialog from '../../components/TagDialog.vue'
|
||||
```
|
||||
|
||||
### Step 4: script — 新增弹窗响应式状态
|
||||
|
||||
找到 `const saving = ref(false)` 这一行(约第 198 行附近,在 `const isEdit = ref(false)` 之后),在其下方追加:
|
||||
|
||||
```javascript
|
||||
const dialogVisible = ref(false)
|
||||
const dialogMode = ref('input')
|
||||
const dialogTitle = ref('')
|
||||
const dialogPlaceholder = ref('')
|
||||
const dialogContent = ref('')
|
||||
const dialogInput = ref('')
|
||||
const dialogAction = ref(null)
|
||||
```
|
||||
|
||||
### Step 5: script — 重构 addCustomTag 并新增 handleAddTag / onDialogConfirm / onDialogCancel
|
||||
|
||||
找到现有的 `addCustomTag` 函数(约第 410-449 行,从 `/**` 注释到 `}` 结束):
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 新增自定义标签(性格标签 / 兴趣爱好通用)
|
||||
* @param {string} type 'personality' 或 'hobby'
|
||||
*/
|
||||
const addCustomTag = (type) => {
|
||||
const isPersonality = type === 'personality'
|
||||
const library = isPersonality ? form.personalityTagLibrary : form.hobbyLibrary
|
||||
const selected = isPersonality ? form.personalityTags : form.hobbies
|
||||
const title = isPersonality ? '添加性格标签' : '添加兴趣爱好'
|
||||
|
||||
uni.showModal({
|
||||
title,
|
||||
editable: true,
|
||||
placeholderText: '请输入标签(最多8个字符)',
|
||||
success: (res) => {
|
||||
const value = String(res.content || '').trim()
|
||||
if (!res.confirm) return
|
||||
if (!value) {
|
||||
uni.showToast({ title: '请输入标签内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (value.length > 8) {
|
||||
uni.showToast({ title: '标签最多8个字符', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (library.includes(value)) {
|
||||
uni.showToast({ title: '标签已存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 新标签插入到库数组最前面
|
||||
library.unshift(value)
|
||||
// 若已选未满 5 个,自动加入已选;否则仅提示
|
||||
if (selected.length >= 5) {
|
||||
uni.showToast({ title: '已达上限,仅加入标签库', icon: 'none' })
|
||||
return
|
||||
}
|
||||
selected.push(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
完整替换为以下四个函数:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 打开添加标签弹窗
|
||||
* @param {string} type 'personality' 或 'hobby'
|
||||
*/
|
||||
const addCustomTag = (type) => {
|
||||
const isPersonality = type === 'personality'
|
||||
dialogMode.value = 'input'
|
||||
dialogTitle.value = isPersonality ? '添加性格标签' : '添加兴趣爱好'
|
||||
dialogPlaceholder.value = '请输入标签(最多4个字)'
|
||||
dialogInput.value = ''
|
||||
dialogAction.value = { kind: 'add', type }
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理添加标签的实际逻辑(字数限制 4 字)
|
||||
*/
|
||||
const handleAddTag = (type, rawValue) => {
|
||||
const value = String(rawValue || '').trim()
|
||||
const isPersonality = type === 'personality'
|
||||
const library = isPersonality ? form.personalityTagLibrary : form.hobbyLibrary
|
||||
const selected = isPersonality ? form.personalityTags : form.hobbies
|
||||
|
||||
if (!value) {
|
||||
uni.showToast({ title: '请输入标签内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (value.length > 4) {
|
||||
uni.showToast({ title: '内容不能超过4个字', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (library.includes(value)) {
|
||||
uni.showToast({ title: '标签已存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// 新标签插入到库数组最前面
|
||||
library.unshift(value)
|
||||
// 若已选未满 5 个,自动加入已选;否则仅提示
|
||||
if (selected.length >= 5) {
|
||||
uni.showToast({ title: '已达上限,仅加入标签库', icon: 'none' })
|
||||
return
|
||||
}
|
||||
selected.push(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗确认回调
|
||||
*/
|
||||
const onDialogConfirm = (value) => {
|
||||
const action = dialogAction.value
|
||||
if (!action) return
|
||||
if (action.kind === 'add') {
|
||||
handleAddTag(action.type, value)
|
||||
}
|
||||
dialogAction.value = null
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗取消回调
|
||||
*/
|
||||
const onDialogCancel = () => {
|
||||
dialogAction.value = null
|
||||
dialogVisible.value = false
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: 样式 — .tag-choice 不换行
|
||||
|
||||
找到 `.tag-choice` 样式(约第 1008-1011 行):
|
||||
|
||||
```css
|
||||
.tag-choice {
|
||||
height: 40rpx;
|
||||
font-size: 21rpx;
|
||||
}
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```css
|
||||
.tag-choice {
|
||||
height: 40rpx;
|
||||
font-size: 21rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 7: 提交
|
||||
|
||||
```bash
|
||||
git add mini-program/src/pages/onboarding/index.vue
|
||||
git commit -m "feat:编辑页添加按钮简化为添加,字数限制4字,标签不换行,接入自定义弹窗"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 管理页改动(tag-manage.vue)
|
||||
|
||||
**目标:** 引入 TagDialog、重构 confirmDelete 用自定义弹窗、标签文字不换行。
|
||||
|
||||
**Files:**
|
||||
- Modify: `mini-program/src/pages/onboarding/tag-manage.vue`
|
||||
|
||||
### Step 1: 模板 — 挂载 TagDialog 组件
|
||||
|
||||
找到 `</scroll-view>` 和 `</view>` 包裹的页面结构末尾(约第 44-46 行):
|
||||
|
||||
```html
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
在 `</scroll-view>` 之后、`</view>` 之前插入 TagDialog 挂载:
|
||||
|
||||
```html
|
||||
</scroll-view>
|
||||
|
||||
<TagDialog
|
||||
v-model:visible="dialogVisible"
|
||||
mode="confirm"
|
||||
:title="dialogTitle"
|
||||
:content="dialogContent"
|
||||
@confirm="onDialogConfirm"
|
||||
@cancel="onDialogCancel"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Step 2: script — 引入组件
|
||||
|
||||
找到 import 区(约第 48-51 行):
|
||||
|
||||
```javascript
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useAppStore } from '../../stores/app.js'
|
||||
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
|
||||
```
|
||||
|
||||
在最后一行 import 之后追加:
|
||||
|
||||
```javascript
|
||||
import TagDialog from '../../components/TagDialog.vue'
|
||||
```
|
||||
|
||||
### Step 3: script — 新增弹窗响应式状态
|
||||
|
||||
找到 `const type = ref('personality')` 这一行(约第 56 行),在其下方追加:
|
||||
|
||||
```javascript
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('确认删除')
|
||||
const dialogContent = ref('')
|
||||
const pendingTag = ref(null)
|
||||
```
|
||||
|
||||
### Step 4: script — 重构 confirmDelete 并新增 onDialogConfirm / onDialogCancel
|
||||
|
||||
找到现有的 `confirmDelete` 函数(约第 127-139 行):
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 删除确认
|
||||
*/
|
||||
const confirmDelete = (tag) => {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定删除「${tag}」吗?删除后不可恢复`,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
deleteTag(tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
完整替换为以下三个函数:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 打开删除确认弹窗
|
||||
*/
|
||||
const confirmDelete = (tag) => {
|
||||
pendingTag.value = tag
|
||||
dialogContent.value = `确定删除「${tag}」吗?删除后不可恢复`
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗确认回调
|
||||
*/
|
||||
const onDialogConfirm = () => {
|
||||
if (pendingTag.value) {
|
||||
deleteTag(pendingTag.value)
|
||||
pendingTag.value = null
|
||||
}
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗取消回调
|
||||
*/
|
||||
const onDialogCancel = () => {
|
||||
pendingTag.value = null
|
||||
dialogVisible.value = false
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: 样式 — .tag-text 不换行
|
||||
|
||||
找到 `.tag-text` 样式(约第 311-314 行):
|
||||
|
||||
```css
|
||||
.tag-text {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 26rpx;
|
||||
}
|
||||
```
|
||||
|
||||
改为:
|
||||
|
||||
```css
|
||||
.tag-text {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: 提交
|
||||
|
||||
```bash
|
||||
git add mini-program/src/pages/onboarding/tag-manage.vue
|
||||
git commit -m "feat:管理页删除确认接入自定义弹窗,标签文字不换行"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: H5 浏览器验证
|
||||
|
||||
**目标:** 在 H5 模式下验证 spec 的 8 条验收标准。
|
||||
|
||||
### Step 1: 确认 H5 dev server 运行
|
||||
|
||||
```bash
|
||||
python dev-services.py status
|
||||
```
|
||||
|
||||
确认 `Emotion-museum-uniapp 前端` 状态为 `运行中`(端口 5175)。若未运行:
|
||||
|
||||
```bash
|
||||
cd mini-program
|
||||
npm run dev:h5
|
||||
```
|
||||
|
||||
### Step 2: 逐条验证验收标准
|
||||
|
||||
在浏览器中打开 `http://localhost:5175`,登录后进入编辑资料页(`?edit=1`):
|
||||
|
||||
| # | 验收标准 | 验证方法 |
|
||||
|---|---|---|
|
||||
| 1 | 添加按钮只显示"+ 添加" | 观察性格标签、兴趣爱好 panel 末尾占位文字 |
|
||||
| 2 | 点击"+ 添加"弹出深色太空主题弹窗 | 点击按钮,验证弹窗为毛玻璃卡片 + 紫色渐变按钮 + 金色 ✦,非白底蓝按钮 |
|
||||
| 3 | 输入 5 字 toast 提示"内容不能超过4个字" | 输入 5 个字点确定,验证 toast 文案 |
|
||||
| 4 | 输入 4 字以内新标签,确认后出现在最前 | 输入"测试"点确定,验证标签出现在列表第一位 |
|
||||
| 5 | 标签过长时省略号截断不换行 | 输入 4 字标签,观察窄屏下是否单行省略号 |
|
||||
| 6 | 管理页删除弹窗为深色主题 | 点"管理"进入管理页,点 × 或左滑,验证弹窗样式与文案 |
|
||||
| 7 | 取消按钮和遮罩层点击都能关闭弹窗 | 分别测试取消按钮、点击遮罩层,验证弹窗关闭且不触发动作 |
|
||||
| 8 | 弹窗 H5 下样式与深色主题一致 | 整体视觉检查 |
|
||||
|
||||
### Step 3: 验收通过后报告
|
||||
|
||||
所有 8 条通过后,确认工作区干净:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 执行顺序建议
|
||||
|
||||
1. Task 1(新建组件)→ Task 2(编辑页接入)→ Task 3(管理页接入)→ Task 4(验证)
|
||||
2. Task 1 必须先完成,Task 2/3 依赖组件存在。
|
||||
3. Task 2/3 完成后,H5 dev server 热加载自动生效,可直接进入 Task 4 验证。
|
||||
Reference in New Issue
Block a user