diff --git a/docs/superpowers/plans/2026-06-24-tag-add-button-simplify.md b/docs/superpowers/plans/2026-06-24-tag-add-button-simplify.md new file mode 100644 index 0000000..2bdd492 --- /dev/null +++ b/docs/superpowers/plans/2026-06-24-tag-add-button-simplify.md @@ -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 (` + + +``` + +### 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 + + 添加标签 +``` + +改为: + +```html + + 添加 +``` + +找到兴趣爱好 panel 末尾占位(约第 167 行): + +```html + + 添加兴趣 +``` + +改为: + +```html + + 添加 +``` + +### Step 2: 模板 — 挂载 TagDialog 组件 + +找到 `` 和 `` 包裹的页面结构末尾(约第 185-186 行): + +```html + + + +``` + +在 `` 之后、`` 之前插入 TagDialog 挂载: + +```html + + + + + +``` + +### 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 组件 + +找到 `` 和 `` 包裹的页面结构末尾(约第 44-46 行): + +```html + + + +``` + +在 `` 之后、`` 之前插入 TagDialog 挂载: + +```html + + + + + +``` + +### 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 验证。