Files
JianSu-Naming/step3.md
王鹏 be1f5722ab feat: 完成见素起名小程序核心功能
- 实现 AI 起名功能(Kimi API 接入)
- 添加用户收藏功能(MySQL 数据库)
- 实现海报生成与分享
- 添加音效和触觉反馈
- 配置生产环境部署(WAR 包 + Nginx)
- 支持多种起名模式(经典、诗词、自然、现代)
- 实现分批加载优化体验
2026-04-17 15:34:51 +08:00

541 lines
14 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.

既然**阶段 1后端 API 增强**(包括 `NameCard` 模型适配、`MiniMaxService` 的多模式 Prompt 引擎、以及 `NamingController` 的参数接收)已经开发完成,那么接下来的核心目标是**完成前端的“大换装”**。
下一阶段(**阶段 2**)我们将聚焦于**前端重设计 - 输入页 (`home` 页面)**。这是用户进入小程序的第一印象,目标是实现“极简”与“仪式感”。
---
# 下一阶段开发规划:前端极简输入页重构
## 🎯 阶段目标
重写 `home` 页面,实现多模式选择、水墨晕开动画效果,并对接已完成的后端多模式接口。
---
## 2.1 结构重构 (`home.wxml`)
需要打破原有简单的输入框布局,引入模式切换逻辑。
* **模式切换器**
* 使用 `wx:for` 渲染“宝宝”、“人设”、“拾遗”三个标签。
* 绑定 `bindtap="selectMode"`,通过 `data-id` 切换状态。
* **输入增强**
* 保留 `keyword` 输入框。
* **可选**:根据 `step2.md` 预留 `surname`(姓氏)输入框(仅在“宝宝”模式下通过显隐逻辑控制展示)。
* **动画占位**
* 新增 `<canvas>``<view>` 容器用于承载“墨水滴入水中”的加载动效。
## 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
<view class="container">
<view class="mode-selector">
<view
wx:for="{{modes}}"
wx:key="id"
class="mode-item {{activeMode === item.id ? 'active' : ''}}"
bindtap="selectMode"
data-id="{{item.id}}"
>
<text>{{item.name}}</text>
<view class="dot" wx:if="{{activeMode === item.id}}"></view>
</view>
</view>
<view class="main-content">
<view class="input-group">
<input
wx:if="{{activeMode === 'baby'}}"
class="surname-input"
placeholder="姓"
placeholder-class="p-class"
bindinput="onSurnameInput"
maxlength="2"
/>
<input
class="keyword-input"
placeholder="{{activeMode === 'persona' ? '输入你的职业、性格或期望' : '输入意向字或意境'}}"
placeholder-class="p-class"
bindinput="onKeywordInput"
/>
</view>
<view class="btn-container">
<button class="generate-btn" bindtap="startGenerate" disabled="{{isGenerating}}">
<text wx:if="{{!isGenerating}}">求名</text>
<view wx:else class="ink-loading"></view>
</button>
</view>
</view>
<view class="ink-overlay {{isGenerating ? 'show' : ''}}"></view>
</view>
```
---
### 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
<view class="container">
<view class="header-info">
<text class="brand">见素</text>
<text class="divider">·</text>
<text class="mode-tag">{{currentModeDisplay}}</text>
<text class="divider">·</text>
<text class="keyword-tag">{{currentKeyword}}</text>
</view>
<view class="card-stack">
<view
wx:for="{{nameList}}"
wx:key="id"
class="card {{index === currentIndex ? 'active' : ''}}"
style="z-index: {{nameList.length - index}}"
>
<view class="card-front">
<text class="name-text">{{item.name}}</text>
</view>
<view class="card-back">
<view class="origin">—— {{item.origin}}</view>
<view class="description">
<text>{{item.description}}</text>
</view>
<view class="score-badge">五行 {{item.tone}} · 意蕴 {{item.score}}</view>
</view>
</view>
</view>
<view class="action-bar">
<view class="action-btn back-home" bindtap="onBack">
<view class="icon-back"></view>
</view>
<view class="main-actions">
<view class="circle-btn dislike" bindtap="onDislike">
<text>×</text>
</view>
<view class="circle-btn like" bindtap="onLike">
<text>♥</text>
</view>
</view>
<view class="action-btn share" bindtap="onSavePoster">
<view class="icon-share"></view>
</view>
</view>
</view>
```
---
### 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. **按钮交互**:点击底部的 `×` 和 `♥` 应该能触发卡片的切换效果(即使你不去手动滑动)。