style: 优化剧本生成器页面布局(方案 A)

主要变更:
- 修复输入框溢出问题,添加 box-sizing: border-box
- 重构参数区域为垂直堆叠布局,匹配原型图
- 添加 NPC 容器背景和边框,增强视觉层次
- 优化字体大小和间距,适配移动端屏幕
- 调整圆角尺寸,与原型保持一致

设计文档: docs/plans/2026-03-07-script-view-optimization.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 19:10:59 +08:00
parent 3a004cf704
commit fb5f3089e2
2 changed files with 315 additions and 104 deletions
@@ -0,0 +1,185 @@
# 剧本生成器页面优化设计
## 概述
优化小程序剧本生成器页面(ScriptView.vue)的布局和样式,解决以下问题:
1. 输入框宽度过大导致超出屏幕
2. 叙事风格和篇幅参数排列过于紧凑(原型为上下两行,实现为并排)
3. NPC 表单缺少外层容器和 padding
4. 字体大小、间距与原型不一致
5. 整体视觉不够精致
## 问题清单
### 1. 输入框溢出
- **问题**`.glass-input``.glass-picker` 设置 `width: 100%` 但未正确处理 box-sizing
- **影响**:在小屏幕设备上输入框可能超出容器
### 2. 参数行布局错误
- **当前实现**`.params-row` 使用 `grid-template-columns: 1fr 1fr` 并排显示
- **原型设计**:使用两个独立的 `space-y-2` 垂直堆叠区域
- **差异**:原型中"叙事风格"和"故事篇幅"是上下两个独立区域,每个区域内标签和选项垂直排列
### 3. NPC 表单缺少容器
- **当前实现**`.npc-form` 直接裸露,无边框背景
- **原型设计**:使用带 `bg-white/5 border border-white/10 rounded-2xl p-4` 的容器包裹
- **影响**:视觉层次感缺失,与原型不一致
### 4. 字体尺寸过大
| 元素 | 当前 | 原型 | 建议 |
|------|------|------|------|
| section-title | 22rpx | ~10px (约 18rpx) | 18rpx |
| label | 18rpx | ~10px (约 18rpx) | 16rpx |
| input text | 26rpx | ~11px (约 20rpx) | 22rpx |
| param-label | 18rpx | ~9px (约 16rpx) | 16rpx |
| param-option | 22rpx | ~10px (约 18rpx) | 18rpx |
### 5. 圆角不一致
| 元素 | 当前 | 原型 | 建议 |
|------|------|------|------|
| section-card | 默认 | rounded-3xl (约 48rpx) | 48rpx |
| 主容器 | 默认 | rounded-[2rem] (约 64rpx) | 64rpx |
| 输入框 | 16rpx | rounded-xl (约 24rpx) | 24rpx |
| 参数选项 | 16rpx | rounded-xl | 24rpx |
## 设计方案
### 1. 布局结构调整
#### params-row 重构
```css
/* 原方案:并排 grid */
.params-row {
display: grid;
grid-template-columns: 1fr 1fr;
}
/* 新方案:垂直堆叠 */
.params-section {
margin-bottom: 24rpx;
}
.param-row {
display: flex;
flex-direction: column;
gap: 12rpx;
}
```
#### NPC 表单添加容器
```html
<!-- 新增 wrapper -->
<view class="npc-container">
<view class="npc-form">
<!-- 原有内容 -->
</view>
</view>
```
### 2. 尺寸优化
| 属性 | 原值 | 新值 |
|------|------|------|
| `.section-card` padding | 32rpx | 40rpx |
| `.section-card` border-radius | 默认 | 48rpx |
| 主容器 border-radius | 默认 | 64rpx |
| `.glass-input` height | 80rpx | 72rpx |
| `.glass-input` font-size | 26rpx | 22rpx |
| `.glass-input` padding | 0 24rpx | 0 20rpx |
| `.label` font-size | 18rpx | 16rpx |
| `.param-label` font-size | 18rpx | 16rpx |
| `.param-option` font-size | 22rpx | 18rpx |
| `.param-option` padding | 10rpx 20rpx | 8rpx 16rpx |
### 3. 间距优化
| 元素 | 原间距 | 新间距 |
|------|--------|--------|
| `.section-card` gap | 32rpx | 40rpx |
| `.input-group` margin-bottom | 24rpx | 32rpx |
| `.label` margin-bottom | 16rpx | 12rpx |
| `.param-option` gap | 12rpx | 8rpx |
| NPC 容器内间距 | 无 | 16rpx |
### 4. 视觉优化
- 添加 `box-sizing: border-box` 到所有输入元素
- NPC 容器添加玻璃态背景边框
- 参数选项使用更紧凑的排列
- 调整生成按钮高度:96rpx → 88rpx
## 修改文件
- `mini-program/src/pages/main/ScriptView.vue`
## 关键代码变更
### 1. params-row 结构变更
```html
<!-- 原结构 -->
<view class="params-row">
<view class="param-group">...</view>
<view class="param-group">...</view>
</view>
<!-- 新结构 -->
<view class="params-section">
<view class="param-row">
<text class="param-label">叙事风格</text>
<view class="param-options">...</view>
</view>
</view>
<view class="params-section">
<view class="param-row">
<text class="param-label">故事篇幅</text>
<view class="param-options">...</view>
</view>
</view>
```
### 2. NPC 容器添加
```html
<view class="npc-container">
<view class="npc-form">
<!-- 三列输入 -->
</view>
<textarea class="glass-textarea">...</textarea>
</view>
```
### 3. CSS 关键修改
```css
/* 所有输入元素添加 */
*, *::before, *::after {
box-sizing: border-box;
}
/* NPC 容器样式 */
.npc-container {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 32rpx;
padding: 32rpx;
margin-bottom: 16rpx;
}
/* 参数行垂直排列 */
.params-section {
margin-bottom: 24rpx;
}
.param-row {
display: flex;
flex-direction: column;
gap: 12rpx;
}
```
## 验收标准
- [ ] 所有输入框在小屏幕(320px 宽度)下不溢出
- [ ] 叙事风格和篇幅参数垂直排列,各有独立标签
- [ ] NPC 表单有明显的容器边框和背景
- [ ] 字体大小适配移动端,无明显过大
- [ ] 圆角与原型一致,视觉精致
- [ ] 整体布局紧凑,层次分明
+115 -89
View File
@@ -28,11 +28,11 @@
</view>
</view>
<view class="section-card glass-card">
<view class="section-card glass-card-main">
<view class="input-group">
<text class="label">剧本主题</text>
<input
class="glass-input"
class="glass-input theme-input"
placeholder="如:巅峰重现、治愈之旅、赛博觉醒..."
v-model="scriptConfig.theme"
/>
@@ -44,26 +44,28 @@
<button class="add-btn" @click="addNpc">+ 添加</button>
</view>
<view class="npc-form">
<input
class="glass-input npc-input"
placeholder="姓名"
v-model="npcConfig.name"
/>
<picker class="glass-picker npc-picker" mode="selector" :range="npcRoleOptions" :value="npcRoleIndex" @change="onNpcRoleChange">
<view class="picker-value">{{ npcConfig.role || '角色' }}</view>
</picker>
<picker class="glass-picker npc-picker" mode="selector" :range="npcRelationOptions" :value="npcRelationIndex" @change="onNpcRelationChange">
<view class="picker-value">{{ npcConfig.relation || '关系' }}</view>
</picker>
</view>
<view class="npc-container">
<view class="npc-form">
<input
class="glass-input npc-input"
placeholder="姓名"
v-model="npcConfig.name"
/>
<picker class="glass-picker npc-picker" mode="selector" :range="npcRoleOptions" :value="npcRoleIndex" @change="onNpcRoleChange">
<view class="picker-value">{{ npcConfig.role || '角色' }}</view>
</picker>
<picker class="glass-picker npc-picker" mode="selector" :range="npcRelationOptions" :value="npcRelationIndex" @change="onNpcRelationChange">
<view class="picker-value">{{ npcConfig.relation || '关系' }}</view>
</picker>
</view>
<textarea
class="glass-textarea"
placeholder="自由描述TA的人设特点或关键剧情点..."
v-model="npcConfig.desc"
rows="2"
/>
<textarea
class="glass-textarea"
placeholder="自由描述 TA 的人设特点或关键剧情点..."
v-model="npcConfig.desc"
rows="2"
/>
</view>
<view class="npc-list">
<view
@@ -77,8 +79,8 @@
</view>
</view>
<view class="params-row">
<view class="param-group">
<view class="params-section">
<view class="param-row">
<text class="param-label">叙事风格</text>
<view class="param-options">
<text
@@ -92,8 +94,10 @@
</text>
</view>
</view>
</view>
<view class="param-group">
<view class="params-section">
<view class="param-row">
<text class="param-label">故事篇幅</text>
<view class="param-options">
<text
@@ -271,39 +275,44 @@ onMounted(async () => {
.script-view {
display: flex;
flex-direction: column;
gap: 32rpx;
gap: 24rpx;
min-height: 100%;
}
.page-title {
font-size: 36rpx;
font-weight: 400;
font-size: 32rpx;
font-weight: 300;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 8rpx;
letter-spacing: 4rpx;
}
.section-card {
padding: 24rpx;
border-radius: 32rpx;
}
.glass-card-main {
padding: 32rpx;
border-radius: 48rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 22rpx;
font-size: 18rpx;
color: rgba(192, 132, 252, 0.6);
font-weight: 600;
letter-spacing: 4rpx;
letter-spacing: 3rpx;
text-transform: uppercase;
}
.section-hint {
font-size: 16rpx;
font-size: 14rpx;
color: rgba(255, 255, 255, 0.35);
font-style: italic;
}
@@ -311,59 +320,73 @@ onMounted(async () => {
.profile-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
gap: 12rpx;
}
.glass-input, .glass-picker {
width: 100%;
height: 80rpx;
height: 72rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 0 24rpx;
border-radius: 24rpx;
padding: 0 20rpx;
color: rgba(255, 255, 255, 0.9);
font-size: 26rpx;
font-size: 22rpx;
box-sizing: border-box;
}
.glass-input::placeholder {
color: rgba(255, 255, 255, 0.3);
}
.theme-input {
height: 80rpx;
font-size: 24rpx;
}
.picker-value {
line-height: 80rpx;
line-height: 72rpx;
color: rgba(255, 255, 255, 0.9);
}
.input-group {
margin-bottom: 24rpx;
margin-bottom: 28rpx;
}
.label {
display: block;
font-size: 18rpx;
font-size: 16rpx;
color: rgba(255, 255, 255, 0.35);
font-weight: 600;
letter-spacing: 4rpx;
letter-spacing: 3rpx;
text-transform: uppercase;
margin-bottom: 16rpx;
margin-bottom: 12rpx;
}
.npc-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
margin-bottom: 12rpx;
}
.add-btn {
font-size: 20rpx;
font-size: 18rpx;
color: #C084FC;
border: 1px solid rgba(192, 132, 252, 0.3);
padding: 8rpx 16rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
background: transparent;
}
.npc-container {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 32rpx;
padding: 24rpx;
margin-bottom: 16rpx;
}
.npc-form {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
@@ -372,26 +395,26 @@ onMounted(async () => {
}
.npc-input, .npc-picker {
height: 72rpx;
height: 64rpx;
padding: 0 16rpx;
font-size: 24rpx;
font-size: 20rpx;
}
.npc-picker .picker-value {
line-height: 72rpx;
font-size: 24rpx;
line-height: 64rpx;
font-size: 20rpx;
}
.glass-textarea {
width: 100%;
height: 120rpx;
height: 100rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 20rpx;
border-radius: 24rpx;
padding: 16rpx;
color: rgba(255, 255, 255, 0.9);
font-size: 24rpx;
margin-bottom: 16rpx;
font-size: 20rpx;
box-sizing: border-box;
}
.npc-list {
@@ -414,41 +437,38 @@ onMounted(async () => {
.delete-btn {
color: rgba(255, 255, 255, 0.4);
font-size: 28rpx;
font-size: 24rpx;
padding: 0 4rpx;
}
.params-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
margin-bottom: 32rpx;
.params-section {
margin-bottom: 24rpx;
}
.param-group {
.param-row {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.param-label {
font-size: 18rpx;
font-size: 16rpx;
color: rgba(255, 255, 255, 0.35);
margin-left: 8rpx;
margin-left: 4rpx;
}
.param-options {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
gap: 8rpx;
}
.param-option {
padding: 10rpx 20rpx;
padding: 8rpx 16rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
font-size: 22rpx;
border-radius: 20rpx;
font-size: 18rpx;
color: rgba(255, 255, 255, 0.5);
}
@@ -460,19 +480,21 @@ onMounted(async () => {
.generate-btn {
width: 100%;
height: 96rpx;
box-shadow: 0 8rpx 40rpx rgba(168, 85, 247, 0.3);
height: 88rpx;
border-radius: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(168, 85, 247, 0.3);
}
.scripts-list {
display: flex;
flex-direction: column;
gap: 24rpx;
gap: 20rpx;
}
.script-card {
padding: 32rpx;
padding: 24rpx;
border-left: 4rpx solid transparent;
border-radius: 32rpx;
}
.script-card.selected {
@@ -483,30 +505,30 @@ onMounted(async () => {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
margin-bottom: 12rpx;
}
.script-title {
font-size: 32rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
.script-persona {
font-size: 18rpx;
font-size: 16rpx;
color: rgba(168, 85, 247, 0.6);
background: rgba(168, 85, 247, 0.1);
padding: 6rpx 16rpx;
padding: 6rpx 12rpx;
border-radius: 12rpx;
border: 1px solid rgba(168, 85, 247, 0.2);
}
.script-summary {
display: block;
font-size: 24rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
line-height: 1.6;
margin-bottom: 24rpx;
margin-bottom: 16rpx;
}
.script-footer {
@@ -516,48 +538,52 @@ onMounted(async () => {
}
.script-style {
font-size: 20rpx;
font-size: 18rpx;
color: #C084FC;
}
.select-btn {
font-size: 24rpx;
font-size: 20rpx;
color: #C084FC;
font-weight: 600;
background: transparent;
border: none;
padding: 0;
}
.empty-state {
padding: 80rpx 48rpx;
padding: 64rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
gap: 20rpx;
opacity: 0.5;
border-radius: 48rpx;
}
.empty-icon {
font-size: 64rpx;
font-size: 56rpx;
}
.empty-text {
font-size: 24rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.5);
text-align: center;
}
.generating-state {
padding: 80rpx 48rpx;
padding: 64rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 32rpx;
gap: 24rpx;
border-radius: 48rpx;
}
.spinner {
width: 64rpx;
height: 64rpx;
border: 4rpx solid #A855F7;
width: 56rpx;
height: 56rpx;
border: 3rpx solid #A855F7;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
@@ -569,7 +595,7 @@ onMounted(async () => {
}
.generating-text {
font-size: 26rpx;
font-size: 24rpx;
color: rgba(192, 132, 252, 0.6);
font-style: italic;
letter-spacing: 2rpx;