移动端更新

This commit is contained in:
2026-02-21 14:47:54 +08:00
parent 7f8e68bb0a
commit c0d27be99b
432 changed files with 100843 additions and 0 deletions

View File

@@ -0,0 +1,315 @@
# sc-pages 组件
功能完善的移动端页面框架组件,基于 Vue 3 + Composition API 开发,集成了 uni-nav-bar 和 sc-tabbar 组件。
## 功能特性
- ✅ 顶部导航栏(基于 uni-nav-bar
- 支持自定义标题、左右按钮
- 支持沉浸式状态栏
- 支持自定义主题色
- 支持自定义插槽
- ✅ 中间内容区域
- Flex 布局,占满剩余高度
- 滚动条只在内容区域显示
- 支持下拉刷新
- 支持滚动事件监听
- ✅ 底部 TabBar基于 sc-tabbar
- 支持图标、徽标、红点
- 可在不同页面移除
- 支持主题自定义
- ✅ 响应式布局
- 自动适配不同屏幕
- 支持底部安全区适配
## 安装使用
由于项目已配置 easycom 组件自动引入,无需手动 import直接在模板中使用即可。
### 基础用法
```vue
<template>
<sc-pages
title="页面标题"
show-back
>
<!-- 内容区域 -->
<view class="content">
<text>页面内容</text>
</view>
</sc-pages>
</template>
<script setup>
// 无需额外代码
</script>
```
## Props 属性
### 页面基础配置
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| title | String | '' | 页面标题 |
| pageStyle | String, Object | '' | 页面样式 |
| fixed | Boolean | true | 是否固定定位 |
### 导航栏配置
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| showNavbar | Boolean | true | 是否显示导航栏 |
| showBack | Boolean | true | 是否显示返回按钮 |
| navbarBgColor | String | '#FFFFFF' | 导航栏背景色 |
| navbarColor | String | '#000000' | 导航栏文字颜色 |
| statusBar | Boolean | true | 是否沉浸式状态栏 |
| shadow | Boolean | false | 是否显示阴影 |
| border | Boolean | true | 是否显示边框 |
| leftIcon | String | 'left' | 左侧图标 |
| rightIcon | String | '' | 右侧图标 |
| titleWidth | String | '' | 标题宽度 |
| dark | Boolean | false | 暗黑模式 |
### 内容区域配置
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| scrollTop | Number | 0 | 滚动条位置 |
| scrollWithAnimation | Boolean | false | 是否使用滚动动画 |
| showScrollbar | Boolean | false | 是否显示滚动条 |
| refresherEnabled | Boolean | false | 是否开启下拉刷新 |
| refresherTriggered | Boolean | false | 下拉刷新状态 |
| refresherBackground | String | '#FFFFFF' | 下拉刷新背景色 |
### TabBar 配置
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| showTabbar | Boolean | false | 是否显示 TabBar |
| tabbarList | Array | [] | TabBar 列表 |
| currentTab | Number | 0 | 当前选中的 Tab |
| safeAreaInsetBottom | Boolean | true | 是否开启底部安全区适配 |
| tabbarActiveColor | String | '#007AFF' | TabBar 选中颜色 |
| tabbarInactiveColor | String | '#999999' | TabBar 未选中颜色 |
| tabbarBgColor | String | '#FFFFFF' | TabBar 背景颜色 |
| tabbarBorderColor | String | '#E5E5E5' | TabBar 边框颜色 |
## Events 事件
### 导航栏事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| back | - | 返回按钮点击时触发 |
| left-click | - | 左侧按钮点击时触发 |
| right-click | - | 右侧按钮点击时触发 |
### 内容区域事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| scroll | e | 滚动时触发 |
| scrolltoupper | e | 滚动到顶部时触发 |
| scrolltolower | e | 滚动到底部时触发 |
| refresherrefresh | e | 下拉刷新时触发 |
| refresherrestore | e | 下拉刷新结束时触发 |
### TabBar 事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| tabbar-change | (index, item) | Tab 切换时触发 |
| tabbar-click | (index, item) | Tab 点击时触发 |
## Slots 插槽
| 插槽名 | 说明 | 参数 |
|--------|------|------|
| default | 默认内容(页面内容) | - |
| navbar-left | 自定义导航栏左侧内容 | - |
| navbar-title | 自定义导航栏标题 | - |
| navbar-right | 自定义导航栏右侧内容 | - |
| tabbar-icon | 自定义 TabBar 图标 | { item, index, active } |
## 使用示例
### 1. 基础用法(带导航栏)
```vue
<template>
<sc-pages title="基础用法" show-back>
<view class="content">
<text>页面内容</text>
</view>
</sc-pages>
</template>
```
### 2. 完整页面(带 TabBar
```vue
<template>
<sc-pages
title="完整页面"
:show-tabbar="true"
:tabbar-list="tabbarList"
:current-tab="currentTab"
@tabbar-change="handleTabChange"
>
<view class="content">
<text>页面内容</text>
</view>
</sc-pages>
</template>
<script setup>
import { ref } from 'vue'
const currentTab = ref(0)
const tabbarList = ref([
{ text: '首页', iconPath: 'home', selectedIconPath: 'home-fill' },
{ text: '消息', iconPath: 'message', selectedIconPath: 'message-fill', badge: '5' }
])
const handleTabChange = (index, item) => {
console.log('切换到 tab:', index, item)
}
</script>
```
### 3. 下拉刷新
```vue
<template>
<sc-pages
title="下拉刷新"
:refresher-enabled="true"
:refresher-triggered="isRefreshing"
@refresherrefresh="handleRefresh"
>
<view class="content">
<text>页面内容</text>
</view>
</sc-pages>
</template>
<script setup>
import { ref } from 'vue'
const isRefreshing = ref(false)
const handleRefresh = async () => {
isRefreshing.value = true
// 执行刷新逻辑
setTimeout(() => {
isRefreshing.value = false
}, 1500)
}
</script>
```
### 4. 自定义导航栏
```vue
<template>
<sc-pages
:show-back="false"
:navbar-bg-color="navbarBgColor"
:navbar-color="navbarColor"
>
<!-- 自定义左侧内容 -->
<template #navbar-left>
<image src="/static/logo.png" mode="aspectFit" />
</template>
<!-- 自定义标题 -->
<template #navbar-title>
<view class="custom-title">
<text>自定义标题</text>
<text>副标题</text>
</view>
</template>
<!-- 自定义右侧内容 -->
<template #navbar-right>
<uni-icons type="more" :size="20" />
</template>
<view class="content">
<text>页面内容</text>
</view>
</sc-pages>
</template>
<script setup>
import { ref } from 'vue'
const navbarBgColor = ref('#FFFFFF')
const navbarColor = ref('#000000')
</script>
```
### 5. 无导航栏
```vue
<template>
<sc-pages :show-navbar="false" :show-tabbar="true">
<view class="content">
<text>全屏内容</text>
</view>
</sc-pages>
</template>
```
### 6. 无 TabBar
```vue
<template>
<sc-pages title="普通页面">
<view class="content">
<text>页面内容</text>
</view>
</sc-pages>
</template>
```
## 布局说明
组件采用 Flex 布局结构:
```
sc-pages (flex-direction: column, height: 100vh)
├── uni-nav-bar (固定高度)
├── scroll-view (flex: 1, 占满剩余高度)
│ └── view (内容区域)
└── sc-tabbar (可选,固定高度)
```
- 整体容器使用 `flex-direction: column` 垂直布局
- 中间内容区域使用 `flex: 1` 占据剩余全部高度
- 滚动条只在 scroll-view 区域显示
- 导航栏和 TabBar 根据配置动态显示/隐藏
## 注意事项
1. **TabBar 配置**: `show-tabbar``false`TabBar 不会渲染,可在不同页面灵活控制
2. **导航栏配置**: `show-navbar``false` 时,导航栏不会渲染,适合全屏页面
3. **下拉刷新**: 使用 `refresher-triggered` 控制刷新状态,刷新完成后需将其设为 `false`
4. **滚动事件**: 监听 `scrolltolower` 事件实现上拉加载更多
5. **安全区适配**: iPhone X 及以上设备会自动适配底部安全区
## 完整示例
查看 `resources/mobile/pages/example/pages/` 目录下的示例页面:
- `example1.vue` - 基础用法
- `example2.vue` - 完整页面(带 TabBar
- `example3.vue` - 下拉刷新
- `example4.vue` - 自定义导航栏
- `example5.vue` - 无导航栏
## License
MIT

View File

@@ -0,0 +1,393 @@
<template>
<view class="sc-pages" :style="pageStyle">
<!-- 顶部导航栏 -->
<uni-nav-bar
v-if="showNavbar"
:fixed="fixed"
:background-color="navbarBgColor"
:color="navbarColor"
:status-bar="statusBar"
:shadow="shadow"
:border="border"
:left-icon="leftIcon"
:title="title"
:title-width="titleWidth"
:dark="dark"
@click-left="handleLeftClick"
@click-right="handleRightClick"
>
<!-- 左侧插槽 -->
<template #left>
<slot name="navbar-left">
<view v-if="showBack" class="navbar-back" @click="handleBack">
<uni-icons type="back" :size="24" :color="navbarColor"></uni-icons>
</view>
</slot>
</template>
<!-- 标题插槽 -->
<template #default>
<slot name="navbar-title">
<text class="navbar-title">{{ title }}</text>
</slot>
</template>
<!-- 右侧插槽 -->
<template #right>
<slot name="navbar-right">
<view v-if="rightIcon" class="navbar-right-icon" @click="handleRightClick">
<uni-icons :type="rightIcon" :size="24" :color="navbarColor"></uni-icons>
</view>
</slot>
</template>
</uni-nav-bar>
<!-- 中间内容区域 -->
<scroll-view
class="sc-pages__content"
scroll-y
:scroll-top="scrollTop"
:scroll-with-animation="scrollWithAnimation"
:show-scrollbar="showScrollbar"
:refresher-enabled="refresherEnabled"
:refresher-triggered="refresherTriggered"
:refresher-background="refresherBackground"
@scrolltoupper="handleScrollToUpper"
@scrolltolower="handleScrollToLower"
@scroll="handleScroll"
@refresherrefresh="handleRefresherRefresh"
@refresherrestore="handleRefresherRestore"
>
<view class="sc-pages__content-inner">
<slot></slot>
</view>
</scroll-view>
<!-- 底部 TabBar -->
<sc-tabbar
v-if="showTabbar"
:list="tabbarList"
:model-value="currentTab"
:fixed="fixed"
:safe-area-inset-bottom="safeAreaInsetBottom"
:active-color="tabbarActiveColor"
:inactive-color="tabbarInactiveColor"
:bg-color="tabbarBgColor"
:border-color="tabbarBorderColor"
@change="handleTabbarChange"
@click="handleTabbarClick"
>
<template #icon="scope">
<slot name="tabbar-icon" v-bind="scope"></slot>
</template>
</sc-tabbar>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
// ===== 页面基础配置 =====
// 页面标题
title: {
type: String,
default: ''
},
// 页面样式
pageStyle: {
type: [String, Object],
default: ''
},
// 固定定位
fixed: {
type: Boolean,
default: true
},
// ===== 导航栏配置 =====
// 是否显示导航栏
showNavbar: {
type: Boolean,
default: true
},
// 是否显示返回按钮
showBack: {
type: Boolean,
default: true
},
// 导航栏背景色
navbarBgColor: {
type: String,
default: '#FFFFFF'
},
// 导航栏文字颜色
navbarColor: {
type: String,
default: '#000000'
},
// 是否沉浸式状态栏
statusBar: {
type: Boolean,
default: true
},
// 是否显示阴影
shadow: {
type: Boolean,
default: false
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 左侧图标
leftIcon: {
type: String,
default: 'left'
},
// 右侧图标
rightIcon: {
type: String,
default: ''
},
// 标题宽度
titleWidth: {
type: String,
default: ''
},
// 暗黑模式
dark: {
type: Boolean,
default: false
},
// ===== 内容区域配置 =====
// 滚动条位置
scrollTop: {
type: Number,
default: 0
},
// 是否使用滚动动画
scrollWithAnimation: {
type: Boolean,
default: false
},
// 是否显示滚动条
showScrollbar: {
type: Boolean,
default: false
},
// ===== 下拉刷新配置 =====
// 是否开启下拉刷新
refresherEnabled: {
type: Boolean,
default: false
},
// 下拉刷新状态
refresherTriggered: {
type: Boolean,
default: false
},
// 下拉刷新背景色
refresherBackground: {
type: String,
default: '#FFFFFF'
},
// ===== TabBar 配置 =====
// 是否显示 TabBar
showTabbar: {
type: Boolean,
default: false
},
// TabBar 列表
tabbarList: {
type: Array,
default: () => []
},
// 当前选中的 Tab
currentTab: {
type: Number,
default: 0
},
// 是否开启底部安全区适配
safeAreaInsetBottom: {
type: Boolean,
default: true
},
// TabBar 选中颜色
tabbarActiveColor: {
type: String,
default: '#007AFF'
},
// TabBar 未选中颜色
tabbarInactiveColor: {
type: String,
default: '#999999'
},
// TabBar 背景颜色
tabbarBgColor: {
type: String,
default: '#FFFFFF'
},
// TabBar 边框颜色
tabbarBorderColor: {
type: String,
default: '#E5E5E5'
}
})
const emit = defineEmits([
'back',
'left-click',
'right-click',
'scroll',
'scrolltoupper',
'scrolltolower',
'refresherrefresh',
'refresherrestore',
'tabbar-change',
'tabbar-click'
])
// ===== 导航栏事件处理 =====
// 左侧点击事件
const handleLeftClick = () => {
emit('left-click')
}
// 右侧点击事件
const handleRightClick = () => {
emit('right-click')
}
// 返回上一页
const handleBack = () => {
emit('back')
uni.navigateBack({
delta: 1
})
}
// ===== 内容区域事件处理 =====
// 滚动事件
const handleScroll = (e) => {
emit('scroll', e)
}
// 滚动到顶部
const handleScrollToUpper = (e) => {
emit('scrolltoupper', e)
}
// 滚动到底部
const handleScrollToLower = (e) => {
emit('scrolltolower', e)
}
// 下拉刷新
const handleRefresherRefresh = (e) => {
emit('refresherrefresh', e)
}
// 下拉刷新结束
const handleRefresherRestore = (e) => {
emit('refresherrestore', e)
}
// ===== TabBar 事件处理 =====
// TabBar 切换
const handleTabbarChange = (index, item) => {
emit('tabbar-change', index, item)
}
// TabBar 点击
const handleTabbarClick = (index, item) => {
emit('tabbar-click', index, item)
}
// ===== 暴露方法 =====
// 滚动到指定位置
const scrollTo = (top, duration = 0) => {
// 通过修改 scrollTop 实现
emit('scroll-to', top)
}
// 开始下拉刷新
const startRefresh = () => {
emit('update:refresherTriggered', true)
}
// 结束下拉刷新
const stopRefresh = () => {
emit('update:refresherTriggered', false)
}
defineExpose({
scrollTo,
startRefresh,
stopRefresh
})
</script>
<style scoped lang="scss">
.sc-pages {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
&__content {
flex: 1;
width: 100%;
overflow: hidden;
background: #f5f5f5;
}
&__content-inner {
min-height: 100%;
padding: 0;
}
}
.navbar-back {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
transition: background 0.3s;
&:active {
background: rgba(0, 0, 0, 0.05);
}
}
.navbar-right-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
transition: background 0.3s;
&:active {
background: rgba(0, 0, 0, 0.05);
}
}
.navbar-title {
font-size: 18px;
font-weight: 500;
color: inherit;
}
</style>

View File

@@ -0,0 +1,276 @@
# sc-tabbar 组件
功能完善的移动端底部导航栏组件,基于 Vue 3 + Composition API 开发。
## 功能特性
- ✅ 支持自定义图标iconfont
- ✅ 支持徽标显示badge
- ✅ 支持红点显示dot
- ✅ 支持自定义主题色
- ✅ 支持底部安全区适配
- ✅ 支持 v-model 双向绑定
- ✅ 支持固定定位
- ✅ 支持自定义图标插槽
- ✅ 流畅的过渡动画
## 安装使用
由于项目已配置 easycom 组件自动引入,无需手动 import直接在模板中使用即可。
```vue
<template>
<sc-tabbar
v-model="currentTab"
:list="tabList"
@change="handleTabChange"
/>
</template>
<script setup>
import { ref } from 'vue'
const currentTab = ref(0)
const tabList = ref([
{
text: '首页',
iconPath: 'home',
selectedIconPath: 'home-fill'
},
{
text: '消息',
iconPath: 'message',
selectedIconPath: 'message-fill',
badge: '5'
}
])
const handleTabChange = (index, item) => {
console.log('切换到 tab:', index, item)
}
</script>
```
## Props 属性
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| list | Array | [] | tab 列表数据 |
| modelValue | Number | 0 | 当前选中的索引v-model 绑定) |
| fixed | Boolean | true | 是否固定在底部 |
| safeAreaInsetBottom | Boolean | true | 是否开启底部安全区适配 |
| activeColor | String | '#007AFF' | 选中颜色 |
| inactiveColor | String | '#999999' | 未选中颜色 |
| bgColor | String | '#FFFFFF' | 背景颜色 |
| borderColor | String | '#E5E5E5' | 边框颜色 |
| customClass | String | '' | 自定义类名 |
## list 数据结构
```javascript
{
text: '首页', // tab 文本
iconPath: 'home', // 未选中图标iconfont 类名)
selectedIconPath: 'home-fill', // 选中图标iconfont 类名)
badge: '5', // 徽标数字(可选)
dot: true, // 是否显示红点(可选)
pagePath: '/pages/index/index' // 跳转路径(可选)
}
```
## Events 事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| update:modelValue | index | 当前选中索引更新时触发 |
| change | (index, item) | tab 切换时触发 |
| click | (index, item) | 点击 tab 时触发 |
## Slots 插槽
| 插槽名 | 说明 | 参数 |
|--------|------|------|
| icon | 自定义图标内容 | { item, index, active } |
## 使用示例
### 基础用法
```vue
<template>
<sc-tabbar
v-model="currentTab"
:list="tabList"
/>
</template>
<script setup>
import { ref } from 'vue'
const currentTab = ref(0)
const tabList = ref([
{ text: '首页', iconPath: 'home', selectedIconPath: 'home-fill' },
{ text: '发现', iconPath: 'compass', selectedIconPath: 'compass-fill' },
{ text: '我的', iconPath: 'user', selectedIconPath: 'user-fill' }
])
</script>
```
### 显示徽标
```vue
<template>
<sc-tabbar
v-model="currentTab"
:list="tabList"
/>
</template>
<script setup>
const tabList = ref([
{
text: '首页',
iconPath: 'home',
selectedIconPath: 'home-fill'
},
{
text: '消息',
iconPath: 'message',
selectedIconPath: 'message-fill',
badge: '5' // 显示徽标数字
},
{
text: '我的',
iconPath: 'user',
selectedIconPath: 'user-fill',
dot: true // 显示红点
}
])
</script>
```
### 自定义主题色
```vue
<template>
<sc-tabbar
v-model="currentTab"
:list="tabList"
active-color="#FF6B6B"
inactive-color="#CCCCCC"
bg-color="#2C3E50"
border-color="#34495E"
/>
</template>
```
### 自定义图标插槽
```vue
<template>
<sc-tabbar
v-model="currentTab"
:list="tabList"
>
<template #icon="{ item, index, active }">
<!-- 自定义图标内容 -->
<image
:src="active ? item.selectedIconPath : item.iconPath"
mode="aspectFit"
style="width: 48rpx; height: 48rpx;"
/>
</template>
</sc-tabbar>
</template>
```
### 页面跳转
```vue
<template>
<sc-tabbar
v-model="currentTab"
:list="tabList"
@change="handleTabChange"
/>
</template>
<script setup>
const tabList = ref([
{
text: '首页',
iconPath: 'home',
selectedIconPath: 'home-fill',
pagePath: '/pages/index/index'
},
{
text: '发现',
iconPath: 'compass',
selectedIconPath: 'compass-fill',
pagePath: '/pages/discover/index'
}
])
const handleTabChange = (index, item) => {
if (item.pagePath) {
// 如果是 tabBar 页面
uni.switchTab({
url: item.pagePath,
fail: () => {
// 如果不是 tabBar 页面,使用 navigateTo
uni.navigateTo({
url: item.pagePath
})
}
})
}
}
</script>
```
### 配置 pages.json
如果需要在 tabBar 中配置页面,需要在 `pages.json` 中配置:
```json
{
"tabBar": {
"color": "#999999",
"selectedColor": "#007AFF",
"backgroundColor": "#FFFFFF",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/icons/home.png",
"selectedIconPath": "static/icons/home-active.png"
},
{
"pagePath": "pages/discover/index",
"text": "发现",
"iconPath": "static/icons/compass.png",
"selectedIconPath": "static/icons/compass-active.png"
}
]
}
}
```
## 注意事项
1. **图标字体**:组件默认使用 iconfont 图标库,请确保已引入对应的图标字体文件
2. **页面跳转**:建议在 pages.json 中配置 tabBar 页面,使用 `uni.switchTab` 跳转
3. **徽标显示**badge 和 dot 不能同时设置,优先显示 badge
4. **安全区适配**iPhone X 及以上设备会自动适配底部安全区
5. **fixed 定位**:设置为 fixed 时,组件会固定在页面底部
## 完整示例
查看 `resources/mobile/pages/example/tabbar/index.vue` 获取完整的使用示例。
## License
MIT

View File

@@ -0,0 +1,217 @@
<template>
<view class="sc-tabbar" :class="[{ 'sc-tabbar--fixed': fixed }, customClass]">
<view
v-for="(item, index) in list"
:key="index"
class="sc-tabbar__item"
:class="{ 'sc-tabbar__item--active': currentIndex === index }"
@click="handleClick(index, item)"
>
<view class="sc-tabbar__icon">
<slot name="icon" :item="item" :index="index" :active="currentIndex === index">
<text
class="iconfont sc-tabbar__icon-text"
:class="currentIndex === index ? item.selectedIconPath || item.iconPath : item.iconPath"
></text>
</slot>
<view v-if="item.badge && item.badge !== '0'" class="sc-tabbar__badge">
<text class="sc-tabbar__badge-text">{{ item.badge }}</text>
</view>
<view v-if="item.dot" class="sc-tabbar__dot"></view>
</view>
<text class="sc-tabbar__text">{{ item.text }}</text>
</view>
<view v-if="safeAreaInsetBottom" class="sc-tabbar__safearea"></view>
</view>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
const props = defineProps({
// tab 列表
list: {
type: Array,
default: () => []
},
// 当前选中的索引
modelValue: {
type: Number,
default: 0
},
// 是否固定在底部
fixed: {
type: Boolean,
default: true
},
// 是否开启底部安全区适配
safeAreaInsetBottom: {
type: Boolean,
default: true
},
// 选中颜色
activeColor: {
type: String,
default: '#007AFF'
},
// 未选中颜色
inactiveColor: {
type: String,
default: '#999999'
},
// 背景颜色
bgColor: {
type: String,
default: '#FFFFFF'
},
// 边框颜色
borderColor: {
type: String,
default: '#E5E5E5'
},
// 自定义类名
customClass: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue', 'change', 'click'])
const currentIndex = ref(props.modelValue)
// 监听外部 modelValue 变化
watch(() => props.modelValue, (newVal) => {
currentIndex.value = newVal
})
// 处理点击事件
const handleClick = (index, item) => {
if (currentIndex.value === index) return
currentIndex.value = index
emit('update:modelValue', index)
emit('change', index, item)
emit('click', index, item)
}
// 获取当前激活的 tab
const getCurrentTab = () => {
return props.list[currentIndex.value]
}
// 暴露方法给父组件
defineExpose({
getCurrentTab
})
</script>
<style scoped lang="scss">
.sc-tabbar {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 100rpx;
background: v-bind('bgColor');
border-top: 1rpx solid v-bind('borderColor');
position: relative;
z-index: 999;
&--fixed {
position: fixed;
bottom: 0;
left: 0;
}
&__item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
position: relative;
transition: all 0.3s ease;
&--active {
.sc-tabbar__text {
color: v-bind('activeColor');
}
}
}
&__icon {
position: relative;
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 4rpx;
}
&__icon-text {
font-size: 48rpx;
color: v-bind('inactiveColor');
transition: color 0.3s ease;
.sc-tabbar__item--active & {
color: v-bind('activeColor');
}
}
&__text {
font-size: 20rpx;
line-height: 28rpx;
color: v-bind('inactiveColor');
transition: color 0.3s ease;
}
// 徽标样式
&__badge {
position: absolute;
top: 0;
right: -8rpx;
min-width: 32rpx;
height: 32rpx;
padding: 0 8rpx;
background: #FF4444;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
transform: translate(100%, -50%);
&-text {
font-size: 20rpx;
line-height: 28rpx;
color: #FFFFFF;
white-space: nowrap;
}
}
// 圆点样式
&__dot {
position: absolute;
top: 0;
right: 0;
width: 16rpx;
height: 16rpx;
background: #FF4444;
border-radius: 50%;
transform: translate(100%, -50%);
}
// 底部安全区
&__safearea {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
</style>