hookehuyr

docs: 添加计划书方案开发规范文档

添加 PLAN_SCHEME_GUIDE.md 文档,为新增计划书方案(如 SchemeC、SchemeD)提供标准化开发指导。文档包含组件结构、表单字段、样式交互规范、代码模板和检查清单,确保新方案开发的一致性和可维护性。
# 录入计划书方案开发规范
> **版本**: v1.0
> **更新日期**: 2026-01-31
> **用途**: 指导新增计划书方案(SchemeC、SchemeD 等)的开发
## 目录
- [组件结构规范](#组件结构规范)
- [表单字段规范](#表单字段规范)
- [样式规范](#样式规范)
- [交互规范](#交互规范)
- [代码模板](#代码模板)
- [检查清单](#检查清单)
---
## 组件结构规范
### 基础结构
所有方案组件必须遵循以下三层结构:
```vue
<template>
<div class="flex flex-col h-full bg-gray-50">
<!-- 1. 顶部标题栏 -->
<div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
<span class="text-lg font-normal text-gray-900">{{ title }}</span>
<IconFont name="close" size="16" color="#9CA3AF" @click="close" />
</div>
<!-- 2. 滚动内容区 -->
<div class="flex-1 overflow-y-auto p-4">
<div class="bg-white rounded-xl p-5 shadow-sm">
<!-- 表单字段 -->
</div>
</div>
<!-- 3. 底部按钮区 -->
<div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50">
<div class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white"
@click="close">
取消
</div>
<div class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base" @click="submit">
提交申请
</div>
</div>
</div>
</template>
```
### Props 定义
```javascript
const props = defineProps({
title: {
type: String,
default: '申请计划书'
}
});
```
### Emits 定义
```javascript
const emit = defineEmits(['close', 'submit']);
```
---
## 表单字段规范
### 1. 标准文本输入框
**适用场景**: 客户姓名、年龄、金额等
```vue
<div class="text-sm text-gray-600 mb-2">字段标签</div>
<div class="border border-gray-200 rounded-lg mb-4 overflow-hidden">
<nut-input
v-model="form.fieldName"
type="text"
placeholder="请输入..."
class="!p-0 !bg-transparent !text-sm !text-gray-900"
:border="false"
/>
</div>
```
**关键样式**:
- 容器: `border border-gray-200 rounded-lg mb-4 overflow-hidden`
- 输入框: `!p-0 !bg-transparent !text-sm !text-gray-900`
- 数字类型: 使用 `type="digit"`
### 2. 带单位的输入框
**适用场景**: 年收入区间、期望收益率、年交保费等
```vue
<div class="text-sm text-gray-600 mb-2">字段标签</div>
<div class="border border-gray-200 rounded-lg mb-4 flex items-center overflow-hidden">
<nut-input
v-model="form.fieldName"
type="digit"
placeholder="请输入..."
class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900"
:border="false"
/>
<span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">单位</span>
</div>
```
**关键点**:
- 输入框: `flex-1` (占据剩余空间)
- 单位文字: `shrink-0 ml-2` (防止被挤压)
- 可选: `mr-5` (右侧留白,可选)
### 3. 下拉选择框
**适用场景**: 行业选择等需要 Picker 的场景
```vue
<div class="text-sm text-gray-600 mb-2">字段标签</div>
<div
class="flex justify-between items-center border border-gray-200 rounded-lg p-3 mb-4 overflow-hidden"
@click="showPicker = true"
>
<span :class="form.fieldName ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
{{ form.fieldName || '请选择...' }}
</span>
<IconFont name="right" size="14" color="#9CA3AF" />
</div>
<!-- Picker Popup -->
<nut-popup position="bottom" v-model:visible="showPicker">
<nut-picker
:columns="pickerColumns"
title="选择标题"
@confirm="confirmPicker"
@cancel="showPicker = false"
/>
</nut-popup>
```
**关键点**:
- 容器保留 `p-3` 内边距(因为有可点击内容)
- 右侧箭头图标: `IconFont name="right"`
- 占位文字颜色: `text-gray-400`
- 已选文字颜色: `text-gray-900`
### 4. 单选字段
**适用场景**: 性别、交费期间等
```vue
<div class="text-sm text-gray-600 mb-2">字段标签</div>
<nut-radio-group v-model="form.fieldName" direction="horizontal" class="mb-4">
<nut-radio
v-for="option in options"
:key="option.value"
:label="option.value"
class="mr-8"
>
{{ option.label }}
</nut-radio>
</nut-radio-group>
```
**关键点**:
- `direction="horizontal"` - 水平排列
- `class="mb-4"` - 底部间距
- `class="mr-8"` - 选项间距(两个选项)
- `class="mr-6"` - 选项间距(三个及以上选项)
### 5. 多选字段
**适用场景**: 家庭结构、保险需求等
```vue
<div class="text-sm text-gray-600 mb-3">字段标签(多选)</div>
<div class="flex flex-wrap gap-3 mb-5">
<div
v-for="option in options"
:key="option.value"
class="px-4 py-2 rounded-lg text-sm cursor-pointer transition-colors border"
:class="form.fieldName.includes(option.value) ? 'bg-blue-600 text-white border-blue-600' : 'bg-gray-50 text-gray-600 border-gray-200'"
@click="toggleSelection('fieldName', option.value)"
>
{{ option.label }}
</div>
</div>
```
**关键点**:
- 标签使用 `mb-3`,容器使用 `mb-5`
- `flex flex-wrap gap-3` - 自动换行
- `cursor-pointer transition-colors` - 交互效果
- 选中状态: `bg-blue-600 text-white border-blue-600`
- 未选中状态: `bg-gray-50 text-gray-600 border-gray-200`
**多选切换逻辑**:
```javascript
const toggleSelection = (field, value) => {
const index = form[field].indexOf(value);
if (index === -1) {
form[field].push(value); // 添加选中
} else {
form[field].splice(index, 1); // 取消选中
}
};
```
### 6. 只读展示字段
**适用场景**: 币种、计划、保险期间等固定值展示
```vue
<div class="flex justify-between items-start mb-5">
<span class="text-sm text-gray-600 mt-1.5">字段标签</span>
<div class="bg-blue-50 rounded-md px-3 py-1.5">
<span class="text-sm text-blue-600">显示值</span>
</div>
</div>
```
**关键点**:
- `flex justify-between items-start` - 标签和内容两端对齐
- 标签: `text-sm text-gray-600 mt-1.5` (顶部对齐)
- 内容: `bg-blue-50 rounded-md px-3 py-1.5`
- 文字: `text-sm text-blue-600`
---
## 样式规范
### 颜色系统
| 用途 | 颜色值 | Tailwind 类 |
|------|--------|-------------|
| **主色** | #2563EB | `blue-600` |
| **标签文字** | #4B5563 | `gray-600` |
| **输入文字** | #111827 | `gray-900` |
| **占位文字** | #9CA3AF | `gray-400` |
| **边框** | #E5E7EB | `gray-200` |
| **背景** | #F9FAFB | `gray-50` |
| **图标** | #9CA3AF | `gray-400` |
### 间距系统
| 位置 | 间距 | Tailwind 类 |
|------|------|-------------|
| **标签与输入框** | 8px | `mb-2` |
| **输入框底部** | 16px | `mb-4` |
| **多选按钮底部** | 20px | `mb-5` |
| **单选选项间距** | 32px | `mr-8` |
| **多选按钮间距** | 12px | `gap-3` |
| **顶部标题栏** | 20px | `px-5 py-5` |
| **内容卡片** | 20px | `p-5` |
### 圆角系统
| 元素 | 圆角 | Tailwind 类 |
|------|------|-------------|
| **卡片** | 12px | `rounded-xl` |
| **输入框/按钮** | 8px | `rounded-lg` |
| **标签** | 6px | `rounded-md` |
### 文字大小
| 用途 | 大小 | Tailwind 类 |
|------|------|-------------|
| **标题** | 18px | `text-lg` |
| **正文** | 14px | `text-sm` |
| **按钮** | 16px | `text-base` |
---
## 交互规范
### 全局样式覆盖
所有方案组件必须包含以下样式:
```vue
<style lang="less" scoped>
/* Override NutUI input styles to match design */
:deep(.nut-input) {
padding: 0;
background: transparent;
border-radius: inherit;
}
</style>
```
### 图标使用
**关闭图标**:
```vue
<IconFont name="close" size="16" color="#9CA3AF" @click="close" />
```
**右侧箭头**:
```vue
<IconFont name="right" size="14" color="#9CA3AF" />
```
**提示图标**:
```vue
<IconFont name="tips" size="14" color="#9CA3AF" />
```
### 数据结构
#### Reactive 表单对象
```javascript
const form = reactive({
// 文本字段
textField: '',
// 数字字段
numberField: '',
// 单选字段
radioField: 'defaultValue',
// 多选字段
multiSelectField: [],
// 带单位字段
amountField: ''
});
```
#### 选项数据结构
**单选选项**:
```javascript
const options = [
{ label: '显示文本', value: 'value1' },
{ label: '显示文本', value: 'value2' }
];
```
**多选选项**:
```javascript
const options = [
{ label: '显示文本', value: 'value1' },
{ label: '显示文本', value: 'value2' }
];
```
**Picker 数据**:
```javascript
const pickerColumns = [
{ text: '显示文本', value: 'value1' },
{ text: '显示文本', value: 'value2' }
];
```
---
## 代码模板
### 完整组件模板
```vue
<template>
<div class="flex flex-col h-full bg-gray-50">
<!-- Header -->
<div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
<span class="text-lg font-normal text-gray-900">{{ title }}</span>
<IconFont name="close" size="16" color="#9CA3AF" @click="close" />
</div>
<!-- Content -->
<div class="flex-1 overflow-y-auto p-4">
<div class="bg-white rounded-xl p-5 shadow-sm">
<!-- 表单字段区域 -->
</div>
</div>
<!-- Footer -->
<div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50">
<div
class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white"
@click="close"
>
取消
</div>
<div
class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base"
@click="submit"
>
提交申请
</div>
</div>
</div>
</template>
<script setup>
/**
* @description 录入计划书 - 方案X 内容组件
* @emits close - 关闭弹窗事件
* @emits submit - 提交事件,携带表单数据
*/
import { reactive } from 'vue';
const props = defineProps({
title: {
type: String,
default: '申请计划书'
}
});
const emit = defineEmits(['close', 'submit']);
const form = reactive({
// 定义表单字段
fieldName: ''
});
const close = () => {
emit('close');
};
const submit = () => {
console.log('Submit form:', form);
emit('submit', form);
};
</script>
<style lang="less" scoped>
/* Override NutUI input styles to match design */
:deep(.nut-input) {
padding: 0;
background: transparent;
border-radius: inherit;
}
</style>
```
---
## 检查清单
### 开发前检查
- [ ] 阅读本文档
- [ ] 确认字段类型(输入/单选/多选/下拉)
- [ ] 准备选项数据
- [ ] 确认是否有 Picker 弹窗
### 代码规范检查
- [ ] 使用 `<script setup>` 语法
- [ ] Props/Emits 定义完整
- [ ] 表单对象使用 `reactive`
- [ ] 多选逻辑使用 `toggleSelection` 函数
- [ ] 包含 JSDoc 注释
### 样式检查
- [ ] 所有输入框容器有 `overflow-hidden`
- [ ] 输入框使用 `!p-0 !bg-transparent`
- [ ] 圆角使用 `rounded-lg`
- [ ] 边框颜色使用 `border-gray-200`
- [ ] 带单位输入框使用 `flex-1``shrink-0`
### 组件检查
- [ ] 关闭按钮使用 `IconFont`
- [ ] 单选使用 `nut-radio-group`
- [ ] 多选使用自定义按钮样式
- [ ] Picker 使用 `nut-popup` + `nut-picker`
### 测试检查
- [ ] 输入框文字不贴边(有适当间距)
- [ ] 圆角显示正常
- [ ] 单选只能选中一个
- [ ] 多选可以选中多个
- [ ] Picker 弹窗正常弹出和选择
- [ ] 提交按钮触发 emit 事件
---
## 常见问题
### Q: 输入框圆角被截掉?
**A**: 确保容器添加了 `overflow-hidden`,并且样式覆盖中有 `border-radius: inherit`
### Q: 带单位的输入框,单位文字被挤压?
**A**: 确保:
- 输入框有 `flex-1`
- 单位文字有 `shrink-0`
- 可选:添加右侧留白 `mr-5`
### Q: 多选按钮样式不一致?
**A**: 复制标准模板,检查:
- 未选中状态:`bg-gray-50 text-gray-600 border-gray-200`
- 选中状态:`bg-blue-600 text-white border-blue-600`
- 必须包含:`cursor-pointer transition-colors`
### Q: Picker 弹窗不显示?
**A**: 检查:
- `v-model:visible` 绑定正确
- Picker 数据格式正确:`{ text: '', value: '' }`
- 确认事件处理正确
### Q: 单选/多选数据格式?
**A**:
- 单选:字符串 `form.field = 'value'`
- 多选:数组 `form.field = ['value1', 'value2']`
---
## 版本历史
| 版本 | 日期 | 更新内容 | 作者 |
|------|------|----------|------|
| v1.0 | 2026-01-31 | 初始版本,基于 SchemeA 和 SchemeB 总结 | Claude Code |
---
## 相关文件
- [SchemeA.vue](../src/components/PlanSchemes/SchemeA.vue) - 方案A 实现
- [SchemeB.vue](../src/components/PlanSchemes/SchemeB.vue) - 方案B 实现
- [IconFont.vue](../src/components/IconFont.vue) - 图标组件
- [项目 CLAUDE.md](../CLAUDE.md) - 项目整体规范