hookehuyr

feat(components): 新增可复用的 SectionCard 和 SectionItem 组件

- 创建 SectionItem 组件用于展示单个项目,包含图标、标题、副标题和点击事件
- 创建 SectionCard 组件作为分组卡片容器,可配置标题、背景和项目列表
- 在 signing、onboarding 和 family-office 页面中使用新组件替换重复的 UI 代码
- 更新 components.d.ts 类型声明文件以包含新组件
...@@ -21,6 +21,8 @@ declare module 'vue' { ...@@ -21,6 +21,8 @@ declare module 'vue' {
21 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default'] 21 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default']
22 RouterLink: typeof import('vue-router')['RouterLink'] 22 RouterLink: typeof import('vue-router')['RouterLink']
23 RouterView: typeof import('vue-router')['RouterView'] 23 RouterView: typeof import('vue-router')['RouterView']
24 + SectionCard: typeof import('./src/components/SectionCard.vue')['default']
25 + SectionItem: typeof import('./src/components/SectionItem.vue')['default']
24 TabBar: typeof import('./src/components/TabBar.vue')['default'] 26 TabBar: typeof import('./src/components/TabBar.vue')['default']
25 } 27 }
26 } 28 }
......
1 +<template>
2 + <view class="bg-white rounded-[32rpx] mb-[32rpx] pb-[56rpx] overflow-hidden shadow-sm">
3 + <!-- Section Header -->
4 + <view v-if="title" class="px-[40rpx] py-[32rpx]" :style="{ background: bgGradient }">
5 + <text class="text-[#1f2937] text-[32rpx] font-normal">{{ title }}</text>
6 + </view>
7 +
8 + <!-- Section Items -->
9 + <view class="flex flex-col">
10 + <view v-for="(item, index) in items" :key="index" class="flex flex-col">
11 + <SectionItem
12 + :icon="item.icon"
13 + :title="item.title"
14 + :subtitle="item.subtitle"
15 + :route="item.route"
16 + @click="handleItemClick(item)"
17 + />
18 + <!-- Divider -->
19 + <view v-if="index < items.length - 1" class="w-[626rpx] h-[2rpx] bg-[#e5e7eb] mx-auto mt-[32rpx]"></view>
20 + </view>
21 + </view>
22 + </view>
23 +</template>
24 +
25 +<script setup>
26 +import SectionItem from './SectionItem.vue'
27 +
28 +/**
29 + * Section Card Component
30 + * @description 可复用的分组卡片组件,用于展示带标题的项目列表
31 + */
32 +defineProps({
33 + /**
34 + * Section 标题
35 + */
36 + title: {
37 + type: String,
38 + default: ''
39 + },
40 + /**
41 + * Section 背景渐变色
42 + * @example 'linear-gradient(90deg, #F3E8FF 0%, #E9D5FF 100%)'
43 + */
44 + bgGradient: {
45 + type: String,
46 + default: ''
47 + },
48 + /**
49 + * 项目列表
50 + * @example [{ icon: 'Edit', title: '标题', subtitle: '副标题', route: '/page/path' }]
51 + */
52 + items: {
53 + type: Array,
54 + required: true,
55 + default: () => []
56 + }
57 +})
58 +
59 +const emit = defineEmits(['item-click'])
60 +
61 +/**
62 + * 处理项目点击
63 + * @param {Object} item - 点击的项目数据
64 + */
65 +const handleItemClick = (item) => {
66 + emit('item-click', item)
67 +}
68 +</script>
69 +
70 +<script>
71 +export default {
72 + name: 'SectionCard'
73 +}
74 +</script>
1 +<template>
2 + <view class="flex items-center justify-between px-[40rpx] mt-[56rpx] cursor-pointer" @tap="handleClick">
3 + <view class="flex items-center">
4 + <view class="w-[96rpx] h-[96rpx] mr-[32rpx] flex items-center justify-center bg-gray-50 rounded-full">
5 + <IconFont :name="icon" class="text-blue-600" size="32" />
6 + </view>
7 + <view class="flex flex-col">
8 + <text class="text-[#1f2937] text-[28rpx] font-normal leading-[40rpx]">{{ title }}</text>
9 + <text class="text-[#6b7280] text-[24rpx] mt-[8rpx] leading-[32rpx]">{{ subtitle }}</text>
10 + </view>
11 + </view>
12 + <IconFont name="RectRight" class="text-gray-400" size="16" />
13 + </view>
14 +</template>
15 +
16 +<script setup>
17 +import IconFont from './IconFont.vue'
18 +
19 +/**
20 + * Section Item Component
21 + * @description 单个展示项目组件,包含图标、标题、副标题和右侧箭头
22 + */
23 +defineProps({
24 + /**
25 + * 图标名称(对应 IconFont 组件的 name)
26 + * @example 'Edit', 'Check', 'Order'
27 + */
28 + icon: {
29 + type: String,
30 + required: true
31 + },
32 + /**
33 + * 标题
34 + */
35 + title: {
36 + type: String,
37 + required: true
38 + },
39 + /**
40 + * 副标题/描述
41 + */
42 + subtitle: {
43 + type: String,
44 + default: ''
45 + },
46 + /**
47 + * 路由路径(可选,用于跳转)
48 + */
49 + route: {
50 + type: String,
51 + default: ''
52 + }
53 +})
54 +
55 +const emit = defineEmits(['click'])
56 +
57 +/**
58 + * 处理点击事件
59 + */
60 +const handleClick = () => {
61 + emit('click')
62 +}
63 +</script>
64 +
65 +<script>
66 +export default {
67 + name: 'SectionItem'
68 +}
69 +</script>
...@@ -5,35 +5,14 @@ ...@@ -5,35 +5,14 @@
5 5
6 <!-- Content List --> 6 <!-- Content List -->
7 <div class="px-[40rpx] mt-[40rpx] relative z-10"> 7 <div class="px-[40rpx] mt-[40rpx] relative z-10">
8 - <div v-for="(section, index) in sections" :key="index" 8 + <SectionCard
9 - class="bg-white rounded-[32rpx] mb-[32rpx] pb-[56rpx] overflow-hidden shadow-sm"> 9 + v-for="(section, index) in sections"
10 - <!-- Section Header --> 10 + :key="index"
11 - <div class="px-[40rpx] py-[32rpx]" :style="{ background: section.bgGradient }"> 11 + :title="section.title"
12 - <span class="text-[#1f2937] text-[32rpx] font-normal">{{ section.title }}</span> 12 + :bg-gradient="section.bgGradient"
13 - </div> 13 + :items="section.items"
14 - 14 + @item-click="handleItemClick"
15 - <!-- Section Items --> 15 + />
16 - <div class="flex flex-col">
17 - <div v-for="(item, itemIndex) in section.items" :key="itemIndex" class="flex flex-col">
18 - <div class="flex items-center justify-between px-[40rpx] mt-[56rpx] cursor-pointer"
19 - @tap="handleItemClick(item)">
20 - <div class="flex items-center">
21 - <div class="w-[96rpx] h-[96rpx] mr-[32rpx] flex items-center justify-center bg-gray-50 rounded-full">
22 - <IconFont :name="item.icon" class="text-blue-600" size="32" />
23 - </div>
24 - <div class="flex flex-col">
25 - <span class="text-[#1f2937] text-[28rpx] font-normal leading-[40rpx]">{{ item.title }}</span>
26 - <span class="text-[#6b7280] text-[24rpx] mt-[8rpx] leading-[32rpx]">{{ item.subtitle }}</span>
27 - </div>
28 - </div>
29 - <IconFont name="RectRight" class="text-gray-400" size="16" />
30 - </div>
31 - <!-- Divider -->
32 - <div v-if="itemIndex < section.items.length - 1"
33 - class="w-[626rpx] h-[2rpx] bg-[#e5e7eb] mx-auto mt-[32rpx]"></div>
34 - </div>
35 - </div>
36 - </div>
37 </div> 16 </div>
38 17
39 <!-- Tab Bar --> 18 <!-- Tab Bar -->
...@@ -46,7 +25,7 @@ import { shallowRef } from 'vue' ...@@ -46,7 +25,7 @@ import { shallowRef } from 'vue'
46 import { useGo } from '@/hooks/useGo' 25 import { useGo } from '@/hooks/useGo'
47 import TabBar from '@/components/TabBar.vue' 26 import TabBar from '@/components/TabBar.vue'
48 import NavHeader from '@/components/NavHeader.vue' 27 import NavHeader from '@/components/NavHeader.vue'
49 -import IconFont from '@/components/IconFont.vue' 28 +import SectionCard from '@/components/SectionCard.vue'
50 29
51 const go = useGo() 30 const go = useGo()
52 31
......
...@@ -5,32 +5,14 @@ ...@@ -5,32 +5,14 @@
5 5
6 <!-- Content List --> 6 <!-- Content List -->
7 <div class="px-[40rpx] mt-[40rpx] relative z-10"> 7 <div class="px-[40rpx] mt-[40rpx] relative z-10">
8 - <div v-for="(section, index) in sections" :key="index" class="bg-white rounded-[32rpx] mb-[32rpx] pb-[56rpx] overflow-hidden shadow-sm"> 8 + <SectionCard
9 - <!-- Section Header --> 9 + v-for="(section, index) in sections"
10 - <div class="px-[40rpx] py-[32rpx]" :style="{ background: section.bgGradient }"> 10 + :key="index"
11 - <span class="text-[#1f2937] text-[32rpx] font-normal">{{ section.title }}</span> 11 + :title="section.title"
12 - </div> 12 + :bg-gradient="section.bgGradient"
13 - 13 + :items="section.items"
14 - <!-- Section Items --> 14 + @item-click="handleItemClick"
15 - <div class="flex flex-col"> 15 + />
16 - <div v-for="(item, itemIndex) in section.items" :key="itemIndex" class="flex flex-col">
17 - <div class="flex items-center justify-between px-[40rpx] mt-[56rpx] cursor-pointer" @tap="handleItemClick(item)">
18 - <div class="flex items-center">
19 - <div class="w-[96rpx] h-[96rpx] mr-[32rpx] flex items-center justify-center bg-gray-50 rounded-full">
20 - <IconFont :name="item.icon" class="text-blue-600" size="32" />
21 - </div>
22 - <div class="flex flex-col">
23 - <span class="text-[#1f2937] text-[28rpx] font-normal leading-[40rpx]">{{ item.title }}</span>
24 - <span class="text-[#6b7280] text-[24rpx] mt-[8rpx] leading-[32rpx]">{{ item.subtitle }}</span>
25 - </div>
26 - </div>
27 - <IconFont name="RectRight" class="text-gray-400" size="16" />
28 - </div>
29 - <!-- Divider -->
30 - <div v-if="itemIndex < section.items.length - 1" class="w-[626rpx] h-[2rpx] bg-[#e5e7eb] mx-auto mt-[32rpx]"></div>
31 - </div>
32 - </div>
33 - </div>
34 </div> 16 </div>
35 17
36 <!-- Tab Bar --> 18 <!-- Tab Bar -->
...@@ -43,7 +25,7 @@ import { shallowRef } from 'vue' ...@@ -43,7 +25,7 @@ import { shallowRef } from 'vue'
43 import { useGo } from '@/hooks/useGo' 25 import { useGo } from '@/hooks/useGo'
44 import TabBar from '@/components/TabBar.vue' 26 import TabBar from '@/components/TabBar.vue'
45 import NavHeader from '@/components/NavHeader.vue' 27 import NavHeader from '@/components/NavHeader.vue'
46 -import IconFont from '@/components/IconFont.vue' 28 +import SectionCard from '@/components/SectionCard.vue'
47 29
48 const go = useGo() 30 const go = useGo()
49 31
......
...@@ -5,32 +5,14 @@ ...@@ -5,32 +5,14 @@
5 5
6 <!-- Content List --> 6 <!-- Content List -->
7 <div class="px-[40rpx] mt-[40rpx] relative z-10"> 7 <div class="px-[40rpx] mt-[40rpx] relative z-10">
8 - <div v-for="(section, index) in sections" :key="index" class="bg-white rounded-[32rpx] mb-[32rpx] pb-[56rpx] overflow-hidden shadow-sm"> 8 + <SectionCard
9 - <!-- Section Header --> 9 + v-for="(section, index) in sections"
10 - <div class="px-[40rpx] py-[32rpx]" :style="{ background: section.bgGradient }"> 10 + :key="index"
11 - <span class="text-[#1f2937] text-[32rpx] font-normal">{{ section.title }}</span> 11 + :title="section.title"
12 - </div> 12 + :bg-gradient="section.bgGradient"
13 - 13 + :items="section.items"
14 - <!-- Section Items --> 14 + @item-click="handleItemClick"
15 - <div class="flex flex-col"> 15 + />
16 - <div v-for="(item, itemIndex) in section.items" :key="itemIndex" class="flex flex-col">
17 - <div class="flex items-center justify-between px-[40rpx] mt-[56rpx] cursor-pointer" @tap="handleItemClick(item)">
18 - <div class="flex items-center">
19 - <div class="w-[96rpx] h-[96rpx] mr-[32rpx] flex items-center justify-center bg-gray-50 rounded-full">
20 - <IconFont :name="item.icon" class="text-blue-600" size="32" />
21 - </div>
22 - <div class="flex flex-col">
23 - <span class="text-[#1f2937] text-[28rpx] font-normal leading-[40rpx]">{{ item.title }}</span>
24 - <span class="text-[#6b7280] text-[24rpx] mt-[8rpx] leading-[32rpx]">{{ item.subtitle }}</span>
25 - </div>
26 - </div>
27 - <IconFont name="RectRight" class="text-gray-400" size="16" />
28 - </div>
29 - <!-- Divider -->
30 - <div v-if="itemIndex < section.items.length - 1" class="w-[626rpx] h-[2rpx] bg-[#e5e7eb] mx-auto mt-[32rpx]"></div>
31 - </div>
32 - </div>
33 - </div>
34 </div> 16 </div>
35 17
36 <!-- Tab Bar --> 18 <!-- Tab Bar -->
...@@ -43,7 +25,7 @@ import { shallowRef } from 'vue' ...@@ -43,7 +25,7 @@ import { shallowRef } from 'vue'
43 import { useGo } from '@/hooks/useGo' 25 import { useGo } from '@/hooks/useGo'
44 import TabBar from '@/components/TabBar.vue' 26 import TabBar from '@/components/TabBar.vue'
45 import NavHeader from '@/components/NavHeader.vue' 27 import NavHeader from '@/components/NavHeader.vue'
46 -import IconFont from '@/components/IconFont.vue' 28 +import SectionCard from '@/components/SectionCard.vue'
47 29
48 const go = useGo() 30 const go = useGo()
49 31
......