feat(components): 新增可复用的 SectionCard 和 SectionItem 组件
- 创建 SectionItem 组件用于展示单个项目,包含图标、标题、副标题和点击事件 - 创建 SectionCard 组件作为分组卡片容器,可配置标题、背景和项目列表 - 在 signing、onboarding 和 family-office 页面中使用新组件替换重复的 UI 代码 - 更新 components.d.ts 类型声明文件以包含新组件
Showing
6 changed files
with
172 additions
and
84 deletions
| ... | @@ -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 | } | ... | ... |
src/components/SectionCard.vue
0 → 100644
| 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> |
src/components/SectionItem.vue
0 → 100644
| 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 | ... | ... |
-
Please register or login to post a comment