既然**阶段 1:后端 API 增强**(包括 `NameCard` 模型适配、`MiniMaxService` 的多模式 Prompt 引擎、以及 `NamingController` 的参数接收)已经开发完成,那么接下来的核心目标是**完成前端的“大换装”**。 下一阶段(**阶段 2**)我们将聚焦于**前端重设计 - 输入页 (`home` 页面)**。这是用户进入小程序的第一印象,目标是实现“极简”与“仪式感”。 --- # 下一阶段开发规划:前端极简输入页重构 ## 🎯 阶段目标 重写 `home` 页面,实现多模式选择、水墨晕开动画效果,并对接已完成的后端多模式接口。 --- ## 2.1 结构重构 (`home.wxml`) 需要打破原有简单的输入框布局,引入模式切换逻辑。 * **模式切换器**: * 使用 `wx:for` 渲染“宝宝”、“人设”、“拾遗”三个标签。 * 绑定 `bindtap="selectMode"`,通过 `data-id` 切换状态。 * **输入增强**: * 保留 `keyword` 输入框。 * **可选**:根据 `step2.md` 预留 `surname`(姓氏)输入框(仅在“宝宝”模式下通过显隐逻辑控制展示)。 * **动画占位**: * 新增 `` 或 `` 容器用于承载“墨水滴入水中”的加载动效。 ## 2.2 视觉与动效设计 (`home.wxss`) 这是体现“见素”品牌美学的关键。 * **全局样式变量应用**: * 使用 `var(--jiansu-text)` 等变量。 * 设置 `font-family: "Noto Serif SC"`,确保宋体质感。 * **交互动效**: * **选中态**:选中的模式下方显示一个逐渐生成的微小黑点或下划线。 * **水墨加载动画**:编写 CSS `animation`。利用 `radial-gradient` 和 `opacity` 的变化,模拟墨滴在水中不规则扩散、变淡的效果。 * **按钮排版**: * `.generate-btn`:背景全黑,文字留白,无圆角,增加 `letter-spacing`。 ## 2.3 交互逻辑实现 (`home.js`) * **初始化管理**: ```javascript data: { modes: [ { id: 'baby', name: '宝宝' }, { id: 'persona', name: '人设' }, { id: 'classic', name: '拾遗' } ], activeMode: 'baby', isGenerating: false, // 控制加载动画和按钮禁用 keyword: '', surname: '' } ``` * **模式切换逻辑**:点击切换 `activeMode`,同时清空或保留当前输入内容。 * **提交跳转逻辑**: 1. 校验 `keyword` 是否为空。 2. 触发 `isGenerating: true`,显示水墨动画。 3. **延迟跳转**:为了让用户看到动画(仪式感),建议设置 800ms-1200ms 的延迟,再执行 `wx.navigateTo`。 4. 组装 URL:`/pages/index/index?mode=baby&keyword=平安&surname=张`。 --- ## ✅ 验收标准 1. **视觉**:首页字体呈现思源宋体,布局高度留白,符合极简美学。 2. **交互**:点击不同模式有清晰的选中反馈。 3. **流程**:点击“求名”后,先出现水墨晕开动效,随后平滑跳转至卡片页。 4. **接口**:确认跳转时携带了正确的 `mode` 和 `surname` 参数。 --- 好的,我们现在开始 **阶段 2** 的具体代码实现。 这一部分的重点在于**视觉层面的“仪式感”**。我们将通过 CSS 动画实现“水墨滴入水中晕开”的效果,并构建极简的模式选择器。 --- ### 1. 结构层:`home.wxml` 在 `home.wxml` 中,我们需要构建模式标签和动画容器。 ```html {{item.name}} ``` --- ### 2. 表现层:`home.wxss` 这里我们定义全局 CSS 变量,并利用 `radial-gradient` 实现水墨动效。 ```css /* 模式选择器样式 */ .mode-selector { display: flex; justify-content: center; margin-top: 100rpx; gap: 60rpx; } .mode-item { position: relative; font-size: 28rpx; color: var(--jiansu-accent); transition: all 0.3s ease; padding: 20rpx; } .mode-item.active { color: var(--jiansu-text); font-weight: 500; } .mode-item .dot { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 6rpx; height: 6rpx; background: var(--jiansu-text); border-radius: 50%; animation: fadeIn 0.5s ease; } /* 输入框样式 */ .input-group { margin-top: 150rpx; display: flex; flex-direction: column; align-items: center; gap: 40rpx; } .keyword-input, .surname-input { border-bottom: 1rpx solid #eee; padding: 20rpx; text-align: center; width: 80%; font-family: var(--jiansu-font); } /* 求名按钮 - 极致方正 */ .generate-btn { background: var(--jiansu-text) !important; color: #fff !important; border-radius: 0 !important; width: 240rpx !important; height: 90rpx; line-height: 90rpx; padding: 0; margin-top: 100rpx; font-weight: 300; letter-spacing: 10rpx; transition: all 0.3s; } /* 水墨晕开动画遮罩 */ .ink-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #fff; z-index: 100; pointer-events: none; opacity: 0; } .ink-overlay.show { animation: inkSpread 1.2s ease-in-out forwards; } @keyframes inkSpread { 0% { opacity: 0; clip-path: circle(0% at 50% 50%); background: #000; } 50% { opacity: 1; clip-path: circle(100% at 50% 50%); background: #000; } 100% { opacity: 1; clip-path: circle(150% at 50% 50%); background: #fff; /* 最后切回到白色,准备进入下一页 */ } } ``` --- ### 3. 逻辑层:`home.js` 处理参数组装和仪式感跳转。 ```javascript Page({ data: { modes: [ { id: 'baby', name: '宝宝' }, { id: 'persona', name: '人设' }, { id: 'classic', name: '拾遗' } ], activeMode: 'baby', keyword: '', surname: '', isGenerating: false }, selectMode(e) { const { id } = e.currentTarget.dataset; this.setData({ activeMode: id }); // 切换模式时可以播放一个轻微的震动反馈 wx.vibrateShort({ type: 'light' }); }, onKeywordInput(e) { this.setData({ keyword: e.detail.value }); }, onSurnameInput(e) { this.setData({ surname: e.detail.value }); }, startGenerate() { if (!this.data.keyword) { wx.showToast({ title: '请输入关键词', icon: 'none' }); return; } // 开启仪式感动画 this.setData({ isGenerating: true }); // 延迟 1s 跳转,等待水墨动效执行到视觉重心位置 setTimeout(() => { const { keyword, activeMode, surname } = this.data; const url = `/pages/index/index?keyword=${encodeURIComponent(keyword)}&mode=${activeMode}&surname=${encodeURIComponent(surname)}`; wx.navigateTo({ url, complete: () => { // 页面跳转后重置加载状态,确保返回时按钮可用 setTimeout(() => { this.setData({ isGenerating: false }); }, 500); } }); }, 1000); } }); ``` --- ### 🎨 接下来你可以做的优化: 1. **字体引入**:在 `app.wxss` 中确保 `@import` 了思源宋体的网络字体。 2. **视觉微调**:`inkSpread` 动画中的 `clip-path` 效果非常吃性能,如果低端机型卡顿,可以改用 `opacity` 和 `scale` 的简单组合。 **这部分代码完成后,前端的“多模式入口”就彻底打通了。下一步我们需要处理的是 `index` 卡片页的适配,包括接收这些参数并显示对应的头部信息。需要继续吗?** 既然首页的“水墨仪式感”已经铺设好,我们进入**阶段 3:前端适配 - 卡片页 (`index`)**。 这一阶段的任务是让卡片页能够“听懂”首页传来的参数,并在视觉上呈现出「见素」品牌的统一感:**散文诗排版**、**操作栏重构**以及**顶部状态展示**。 --- ### 1. 结构层:`index.wxml` 我们需要在顶部增加当前模式的展示,并重构底部的交互按钮。 ```html 见素 · {{currentModeDisplay}} · {{currentKeyword}} {{item.name}} —— {{item.origin}} {{item.description}} 五行 {{item.tone}} · 意蕴 {{item.score}} × ♥ ``` --- ### 2. 表现层:`index.wxss` 这里的重点是**散文诗般的行间距**和底部的**悬浮感按钮**。 ```css /* 顶部信息流 */ .header-info { display: flex; justify-content: center; align-items: center; padding: 40rpx 0; font-size: 24rpx; color: var(--jiansu-accent); letter-spacing: 2rpx; } .header-info .brand { color: var(--jiansu-text); font-weight: bold; } .divider { margin: 0 15rpx; } /* 卡片背面排版优化 */ .card-back { padding: 60rpx; display: flex; flex-direction: column; } .card-back .description { margin-top: 40rpx; font-size: 32rpx; line-height: 1.8; /* 散文诗般的行高 */ color: var(--jiansu-text); text-align: justify; letter-spacing: 1rpx; white-space: pre-wrap; /* 保持后端返回的换行 */ } /* 底部操作栏 */ .action-bar { position: fixed; bottom: 80rpx; left: 0; width: 100%; display: flex; justify-content: space-around; align-items: center; padding: 0 60rpx; box-sizing: border-box; } .circle-btn { width: 120rpx; height: 120rpx; border-radius: 50%; border: 1rpx solid #eee; display: flex; justify-content: center; align-items: center; font-size: 40rpx; background: #fff; box-shadow: 0 10rpx 30rpx rgba(0,0,0,0.05); transition: transform 0.2s; } .circle-btn:active { transform: scale(0.9); } .circle-btn.like { color: #e74c3c; } .circle-btn.dislike { color: #95a5a6; } .main-actions { display: flex; gap: 80rpx; } /* 返回图标(简单线条实现) */ .icon-back { width: 40rpx; height: 40rpx; border-left: 2rpx solid var(--jiansu-accent); border-top: 2rpx solid var(--jiansu-accent); transform: rotate(-45deg); } ``` --- ### 3. 逻辑层:`index.js` 这里需要处理参数接收和多模式的映射。 ```javascript Page({ data: { currentKeyword: '', currentMode: '', currentModeDisplay: '', currentIndex: 0, nameList: [] }, onLoad(options) { const { keyword, mode, surname } = options; // 建立模式 ID 到中文名称的映射 const modeMap = { 'baby': '宝宝起名', 'persona': '人设灵感', 'classic': '拾遗经典' }; this.setData({ currentKeyword: keyword, currentMode: mode, currentModeDisplay: modeMap[mode] || '起名' }); this.fetchNames(keyword, mode, surname); }, fetchNames(keyword, mode, surname) { wx.showLoading({ title: '灵感汇聚中...' }); wx.request({ url: 'https://your-api.com/api/naming/generate', // 替换为你的后端地址 method: 'GET', data: { keyword, mode, surname }, success: (res) => { this.setData({ nameList: res.data }); }, fail: () => { wx.showToast({ title: '灵感中断,请重试', icon: 'none' }); }, complete: () => { wx.hideLoading(); } }); }, onBack() { wx.navigateBack(); }, onLike() { // 逻辑:当前卡片右滑移出,并加入收藏 this.handleCardExit('right'); wx.vibrateShort({ type: 'medium' }); }, onDislike() { // 逻辑:当前卡片左滑移出 this.handleCardExit('left'); }, handleCardExit(direction) { // 这里触发卡片动画逻辑(如果你使用的是 CSS 动画控制 class) // ... 原有的滑块逻辑适配 ... this.setData({ currentIndex: this.data.currentIndex + 1 }); } }); ``` --- ### 🛠 阶段性检查点 1. **参数接收**:确认 `index.js` 能正确拿到 `mode` 和 `keyword`。 2. **视觉审美**:检查卡片背面的 `line-height` 是否足够舒展,是否像一段现代诗。 3. **按钮交互**:点击底部的 `×` 和 `♥` 应该能触发卡片的切换效果(即使你不去手动滑动)。