hookehuyr

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

添加 PLAN_SCHEME_GUIDE.md 文档,为新增计划书方案(如 SchemeC、SchemeD)提供标准化开发指导。文档包含组件结构、表单字段、样式交互规范、代码模板和检查清单,确保新方案开发的一致性和可维护性。
1 +# 录入计划书方案开发规范
2 +
3 +> **版本**: v1.0
4 +> **更新日期**: 2026-01-31
5 +> **用途**: 指导新增计划书方案(SchemeC、SchemeD 等)的开发
6 +
7 +## 目录
8 +
9 +- [组件结构规范](#组件结构规范)
10 +- [表单字段规范](#表单字段规范)
11 +- [样式规范](#样式规范)
12 +- [交互规范](#交互规范)
13 +- [代码模板](#代码模板)
14 +- [检查清单](#检查清单)
15 +
16 +---
17 +
18 +## 组件结构规范
19 +
20 +### 基础结构
21 +
22 +所有方案组件必须遵循以下三层结构:
23 +
24 +```vue
25 +<template>
26 + <div class="flex flex-col h-full bg-gray-50">
27 + <!-- 1. 顶部标题栏 -->
28 + <div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
29 + <span class="text-lg font-normal text-gray-900">{{ title }}</span>
30 + <IconFont name="close" size="16" color="#9CA3AF" @click="close" />
31 + </div>
32 +
33 + <!-- 2. 滚动内容区 -->
34 + <div class="flex-1 overflow-y-auto p-4">
35 + <div class="bg-white rounded-xl p-5 shadow-sm">
36 + <!-- 表单字段 -->
37 + </div>
38 + </div>
39 +
40 + <!-- 3. 底部按钮区 -->
41 + <div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50">
42 + <div class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white"
43 + @click="close">
44 + 取消
45 + </div>
46 + <div class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base" @click="submit">
47 + 提交申请
48 + </div>
49 + </div>
50 + </div>
51 +</template>
52 +```
53 +
54 +### Props 定义
55 +
56 +```javascript
57 +const props = defineProps({
58 + title: {
59 + type: String,
60 + default: '申请计划书'
61 + }
62 +});
63 +```
64 +
65 +### Emits 定义
66 +
67 +```javascript
68 +const emit = defineEmits(['close', 'submit']);
69 +```
70 +
71 +---
72 +
73 +## 表单字段规范
74 +
75 +### 1. 标准文本输入框
76 +
77 +**适用场景**: 客户姓名、年龄、金额等
78 +
79 +```vue
80 +<div class="text-sm text-gray-600 mb-2">字段标签</div>
81 +<div class="border border-gray-200 rounded-lg mb-4 overflow-hidden">
82 + <nut-input
83 + v-model="form.fieldName"
84 + type="text"
85 + placeholder="请输入..."
86 + class="!p-0 !bg-transparent !text-sm !text-gray-900"
87 + :border="false"
88 + />
89 +</div>
90 +```
91 +
92 +**关键样式**:
93 +- 容器: `border border-gray-200 rounded-lg mb-4 overflow-hidden`
94 +- 输入框: `!p-0 !bg-transparent !text-sm !text-gray-900`
95 +- 数字类型: 使用 `type="digit"`
96 +
97 +### 2. 带单位的输入框
98 +
99 +**适用场景**: 年收入区间、期望收益率、年交保费等
100 +
101 +```vue
102 +<div class="text-sm text-gray-600 mb-2">字段标签</div>
103 +<div class="border border-gray-200 rounded-lg mb-4 flex items-center overflow-hidden">
104 + <nut-input
105 + v-model="form.fieldName"
106 + type="digit"
107 + placeholder="请输入..."
108 + class="!p-0 !bg-transparent flex-1 !text-sm !text-gray-900"
109 + :border="false"
110 + />
111 + <span class="text-sm text-gray-500 shrink-0 ml-2 mr-5">单位</span>
112 +</div>
113 +```
114 +
115 +**关键点**:
116 +- 输入框: `flex-1` (占据剩余空间)
117 +- 单位文字: `shrink-0 ml-2` (防止被挤压)
118 +- 可选: `mr-5` (右侧留白,可选)
119 +
120 +### 3. 下拉选择框
121 +
122 +**适用场景**: 行业选择等需要 Picker 的场景
123 +
124 +```vue
125 +<div class="text-sm text-gray-600 mb-2">字段标签</div>
126 +<div
127 + class="flex justify-between items-center border border-gray-200 rounded-lg p-3 mb-4 overflow-hidden"
128 + @click="showPicker = true"
129 +>
130 + <span :class="form.fieldName ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
131 + {{ form.fieldName || '请选择...' }}
132 + </span>
133 + <IconFont name="right" size="14" color="#9CA3AF" />
134 +</div>
135 +
136 +<!-- Picker Popup -->
137 +<nut-popup position="bottom" v-model:visible="showPicker">
138 + <nut-picker
139 + :columns="pickerColumns"
140 + title="选择标题"
141 + @confirm="confirmPicker"
142 + @cancel="showPicker = false"
143 + />
144 +</nut-popup>
145 +```
146 +
147 +**关键点**:
148 +- 容器保留 `p-3` 内边距(因为有可点击内容)
149 +- 右侧箭头图标: `IconFont name="right"`
150 +- 占位文字颜色: `text-gray-400`
151 +- 已选文字颜色: `text-gray-900`
152 +
153 +### 4. 单选字段
154 +
155 +**适用场景**: 性别、交费期间等
156 +
157 +```vue
158 +<div class="text-sm text-gray-600 mb-2">字段标签</div>
159 +<nut-radio-group v-model="form.fieldName" direction="horizontal" class="mb-4">
160 + <nut-radio
161 + v-for="option in options"
162 + :key="option.value"
163 + :label="option.value"
164 + class="mr-8"
165 + >
166 + {{ option.label }}
167 + </nut-radio>
168 +</nut-radio-group>
169 +```
170 +
171 +**关键点**:
172 +- `direction="horizontal"` - 水平排列
173 +- `class="mb-4"` - 底部间距
174 +- `class="mr-8"` - 选项间距(两个选项)
175 +- `class="mr-6"` - 选项间距(三个及以上选项)
176 +
177 +### 5. 多选字段
178 +
179 +**适用场景**: 家庭结构、保险需求等
180 +
181 +```vue
182 +<div class="text-sm text-gray-600 mb-3">字段标签(多选)</div>
183 +<div class="flex flex-wrap gap-3 mb-5">
184 + <div
185 + v-for="option in options"
186 + :key="option.value"
187 + class="px-4 py-2 rounded-lg text-sm cursor-pointer transition-colors border"
188 + :class="form.fieldName.includes(option.value) ? 'bg-blue-600 text-white border-blue-600' : 'bg-gray-50 text-gray-600 border-gray-200'"
189 + @click="toggleSelection('fieldName', option.value)"
190 + >
191 + {{ option.label }}
192 + </div>
193 +</div>
194 +```
195 +
196 +**关键点**:
197 +- 标签使用 `mb-3`,容器使用 `mb-5`
198 +- `flex flex-wrap gap-3` - 自动换行
199 +- `cursor-pointer transition-colors` - 交互效果
200 +- 选中状态: `bg-blue-600 text-white border-blue-600`
201 +- 未选中状态: `bg-gray-50 text-gray-600 border-gray-200`
202 +
203 +**多选切换逻辑**:
204 +```javascript
205 +const toggleSelection = (field, value) => {
206 + const index = form[field].indexOf(value);
207 + if (index === -1) {
208 + form[field].push(value); // 添加选中
209 + } else {
210 + form[field].splice(index, 1); // 取消选中
211 + }
212 +};
213 +```
214 +
215 +### 6. 只读展示字段
216 +
217 +**适用场景**: 币种、计划、保险期间等固定值展示
218 +
219 +```vue
220 +<div class="flex justify-between items-start mb-5">
221 + <span class="text-sm text-gray-600 mt-1.5">字段标签</span>
222 + <div class="bg-blue-50 rounded-md px-3 py-1.5">
223 + <span class="text-sm text-blue-600">显示值</span>
224 + </div>
225 +</div>
226 +```
227 +
228 +**关键点**:
229 +- `flex justify-between items-start` - 标签和内容两端对齐
230 +- 标签: `text-sm text-gray-600 mt-1.5` (顶部对齐)
231 +- 内容: `bg-blue-50 rounded-md px-3 py-1.5`
232 +- 文字: `text-sm text-blue-600`
233 +
234 +---
235 +
236 +## 样式规范
237 +
238 +### 颜色系统
239 +
240 +| 用途 | 颜色值 | Tailwind 类 |
241 +|------|--------|-------------|
242 +| **主色** | #2563EB | `blue-600` |
243 +| **标签文字** | #4B5563 | `gray-600` |
244 +| **输入文字** | #111827 | `gray-900` |
245 +| **占位文字** | #9CA3AF | `gray-400` |
246 +| **边框** | #E5E7EB | `gray-200` |
247 +| **背景** | #F9FAFB | `gray-50` |
248 +| **图标** | #9CA3AF | `gray-400` |
249 +
250 +### 间距系统
251 +
252 +| 位置 | 间距 | Tailwind 类 |
253 +|------|------|-------------|
254 +| **标签与输入框** | 8px | `mb-2` |
255 +| **输入框底部** | 16px | `mb-4` |
256 +| **多选按钮底部** | 20px | `mb-5` |
257 +| **单选选项间距** | 32px | `mr-8` |
258 +| **多选按钮间距** | 12px | `gap-3` |
259 +| **顶部标题栏** | 20px | `px-5 py-5` |
260 +| **内容卡片** | 20px | `p-5` |
261 +
262 +### 圆角系统
263 +
264 +| 元素 | 圆角 | Tailwind 类 |
265 +|------|------|-------------|
266 +| **卡片** | 12px | `rounded-xl` |
267 +| **输入框/按钮** | 8px | `rounded-lg` |
268 +| **标签** | 6px | `rounded-md` |
269 +
270 +### 文字大小
271 +
272 +| 用途 | 大小 | Tailwind 类 |
273 +|------|------|-------------|
274 +| **标题** | 18px | `text-lg` |
275 +| **正文** | 14px | `text-sm` |
276 +| **按钮** | 16px | `text-base` |
277 +
278 +---
279 +
280 +## 交互规范
281 +
282 +### 全局样式覆盖
283 +
284 +所有方案组件必须包含以下样式:
285 +
286 +```vue
287 +<style lang="less" scoped>
288 +/* Override NutUI input styles to match design */
289 +:deep(.nut-input) {
290 + padding: 0;
291 + background: transparent;
292 + border-radius: inherit;
293 +}
294 +</style>
295 +```
296 +
297 +### 图标使用
298 +
299 +**关闭图标**:
300 +```vue
301 +<IconFont name="close" size="16" color="#9CA3AF" @click="close" />
302 +```
303 +
304 +**右侧箭头**:
305 +```vue
306 +<IconFont name="right" size="14" color="#9CA3AF" />
307 +```
308 +
309 +**提示图标**:
310 +```vue
311 +<IconFont name="tips" size="14" color="#9CA3AF" />
312 +```
313 +
314 +### 数据结构
315 +
316 +#### Reactive 表单对象
317 +
318 +```javascript
319 +const form = reactive({
320 + // 文本字段
321 + textField: '',
322 +
323 + // 数字字段
324 + numberField: '',
325 +
326 + // 单选字段
327 + radioField: 'defaultValue',
328 +
329 + // 多选字段
330 + multiSelectField: [],
331 +
332 + // 带单位字段
333 + amountField: ''
334 +});
335 +```
336 +
337 +#### 选项数据结构
338 +
339 +**单选选项**:
340 +```javascript
341 +const options = [
342 + { label: '显示文本', value: 'value1' },
343 + { label: '显示文本', value: 'value2' }
344 +];
345 +```
346 +
347 +**多选选项**:
348 +```javascript
349 +const options = [
350 + { label: '显示文本', value: 'value1' },
351 + { label: '显示文本', value: 'value2' }
352 +];
353 +```
354 +
355 +**Picker 数据**:
356 +```javascript
357 +const pickerColumns = [
358 + { text: '显示文本', value: 'value1' },
359 + { text: '显示文本', value: 'value2' }
360 +];
361 +```
362 +
363 +---
364 +
365 +## 代码模板
366 +
367 +### 完整组件模板
368 +
369 +```vue
370 +<template>
371 + <div class="flex flex-col h-full bg-gray-50">
372 + <!-- Header -->
373 + <div class="flex justify-between items-center px-5 py-5 bg-white rounded-t-xl">
374 + <span class="text-lg font-normal text-gray-900">{{ title }}</span>
375 + <IconFont name="close" size="16" color="#9CA3AF" @click="close" />
376 + </div>
377 +
378 + <!-- Content -->
379 + <div class="flex-1 overflow-y-auto p-4">
380 + <div class="bg-white rounded-xl p-5 shadow-sm">
381 + <!-- 表单字段区域 -->
382 + </div>
383 + </div>
384 +
385 + <!-- Footer -->
386 + <div class="p-4 pt-2 pb-8 flex justify-between gap-4 bg-gray-50">
387 + <div
388 + class="flex-1 py-3 text-center border border-blue-600 text-blue-600 rounded-lg text-base bg-white"
389 + @click="close"
390 + >
391 + 取消
392 + </div>
393 + <div
394 + class="flex-1 py-3 text-center bg-blue-600 text-white rounded-lg text-base"
395 + @click="submit"
396 + >
397 + 提交申请
398 + </div>
399 + </div>
400 + </div>
401 +</template>
402 +
403 +<script setup>
404 +/**
405 + * @description 录入计划书 - 方案X 内容组件
406 + * @emits close - 关闭弹窗事件
407 + * @emits submit - 提交事件,携带表单数据
408 + */
409 +import { reactive } from 'vue';
410 +
411 +const props = defineProps({
412 + title: {
413 + type: String,
414 + default: '申请计划书'
415 + }
416 +});
417 +
418 +const emit = defineEmits(['close', 'submit']);
419 +
420 +const form = reactive({
421 + // 定义表单字段
422 + fieldName: ''
423 +});
424 +
425 +const close = () => {
426 + emit('close');
427 +};
428 +
429 +const submit = () => {
430 + console.log('Submit form:', form);
431 + emit('submit', form);
432 +};
433 +</script>
434 +
435 +<style lang="less" scoped>
436 +/* Override NutUI input styles to match design */
437 +:deep(.nut-input) {
438 + padding: 0;
439 + background: transparent;
440 + border-radius: inherit;
441 +}
442 +</style>
443 +```
444 +
445 +---
446 +
447 +## 检查清单
448 +
449 +### 开发前检查
450 +
451 +- [ ] 阅读本文档
452 +- [ ] 确认字段类型(输入/单选/多选/下拉)
453 +- [ ] 准备选项数据
454 +- [ ] 确认是否有 Picker 弹窗
455 +
456 +### 代码规范检查
457 +
458 +- [ ] 使用 `<script setup>` 语法
459 +- [ ] Props/Emits 定义完整
460 +- [ ] 表单对象使用 `reactive`
461 +- [ ] 多选逻辑使用 `toggleSelection` 函数
462 +- [ ] 包含 JSDoc 注释
463 +
464 +### 样式检查
465 +
466 +- [ ] 所有输入框容器有 `overflow-hidden`
467 +- [ ] 输入框使用 `!p-0 !bg-transparent`
468 +- [ ] 圆角使用 `rounded-lg`
469 +- [ ] 边框颜色使用 `border-gray-200`
470 +- [ ] 带单位输入框使用 `flex-1``shrink-0`
471 +
472 +### 组件检查
473 +
474 +- [ ] 关闭按钮使用 `IconFont`
475 +- [ ] 单选使用 `nut-radio-group`
476 +- [ ] 多选使用自定义按钮样式
477 +- [ ] Picker 使用 `nut-popup` + `nut-picker`
478 +
479 +### 测试检查
480 +
481 +- [ ] 输入框文字不贴边(有适当间距)
482 +- [ ] 圆角显示正常
483 +- [ ] 单选只能选中一个
484 +- [ ] 多选可以选中多个
485 +- [ ] Picker 弹窗正常弹出和选择
486 +- [ ] 提交按钮触发 emit 事件
487 +
488 +---
489 +
490 +## 常见问题
491 +
492 +### Q: 输入框圆角被截掉?
493 +
494 +**A**: 确保容器添加了 `overflow-hidden`,并且样式覆盖中有 `border-radius: inherit`
495 +
496 +### Q: 带单位的输入框,单位文字被挤压?
497 +
498 +**A**: 确保:
499 +- 输入框有 `flex-1`
500 +- 单位文字有 `shrink-0`
501 +- 可选:添加右侧留白 `mr-5`
502 +
503 +### Q: 多选按钮样式不一致?
504 +
505 +**A**: 复制标准模板,检查:
506 +- 未选中状态:`bg-gray-50 text-gray-600 border-gray-200`
507 +- 选中状态:`bg-blue-600 text-white border-blue-600`
508 +- 必须包含:`cursor-pointer transition-colors`
509 +
510 +### Q: Picker 弹窗不显示?
511 +
512 +**A**: 检查:
513 +- `v-model:visible` 绑定正确
514 +- Picker 数据格式正确:`{ text: '', value: '' }`
515 +- 确认事件处理正确
516 +
517 +### Q: 单选/多选数据格式?
518 +
519 +**A**:
520 +- 单选:字符串 `form.field = 'value'`
521 +- 多选:数组 `form.field = ['value1', 'value2']`
522 +
523 +---
524 +
525 +## 版本历史
526 +
527 +| 版本 | 日期 | 更新内容 | 作者 |
528 +|------|------|----------|------|
529 +| v1.0 | 2026-01-31 | 初始版本,基于 SchemeA 和 SchemeB 总结 | Claude Code |
530 +
531 +---
532 +
533 +## 相关文件
534 +
535 +- [SchemeA.vue](../src/components/PlanSchemes/SchemeA.vue) - 方案A 实现
536 +- [SchemeB.vue](../src/components/PlanSchemes/SchemeB.vue) - 方案B 实现
537 +- [IconFont.vue](../src/components/IconFont.vue) - 图标组件
538 +- [项目 CLAUDE.md](../CLAUDE.md) - 项目整体规范