feat: 商品详情页面交互
This commit is contained in:
239
components/ImageSwiper/README.md
Normal file
239
components/ImageSwiper/README.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# ImageSwiper 轮播图组件
|
||||
|
||||
一个功能丰富的轮播图组件,支持自定义圆角、缩略图导航和图片描述。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🎨 **可配置圆角**:支持数字(px)或字符串形式的圆角设置
|
||||
- 🖼️ **缩略图导航**:底部缩略图快速切换,支持左右滑动
|
||||
- 📱 **响应式设计**:适配不同屏幕尺寸
|
||||
- 🎯 **自定义数据**:支持传入自定义图片数据
|
||||
- 📊 **进度指示器**:显示当前图片位置
|
||||
- 🎭 **选中状态**:缩略图选中时高亮显示,带缩放动画
|
||||
- 🔄 **自动滚动**:缩略图自动滚动到可视区域
|
||||
- ⚡ **性能优化**:使用计算属性优化渲染
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 默认使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageSwiper />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ImageSwiper from '@/components/ImageSwiper/index.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
### 自定义圆角
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 数字形式 (px) -->
|
||||
<ImageSwiper :border-radius="12" />
|
||||
|
||||
<!-- 字符串形式 -->
|
||||
<ImageSwiper border-radius="1rem" />
|
||||
|
||||
<!-- 无圆角 -->
|
||||
<ImageSwiper :border-radius="0" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 自定义图片数据
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageSwiper
|
||||
:border-radius="15"
|
||||
:images="customImages"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ImageSwiper from '@/components/ImageSwiper/index.vue'
|
||||
|
||||
const customImages = ref([
|
||||
{
|
||||
photoUrl: "https://example.com/image1.jpg",
|
||||
photoName: "图片描述1"
|
||||
},
|
||||
{
|
||||
photoUrl: "https://example.com/image2.jpg",
|
||||
photoName: "图片描述2"
|
||||
}
|
||||
])
|
||||
</script>
|
||||
```
|
||||
|
||||
### 缩略图滑动功能
|
||||
|
||||
组件支持缩略图左右滑动,当图片数量较多时,缩略图会自动滚动到可视区域:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 多图片展示,缩略图支持滑动 -->
|
||||
<ImageSwiper :images="manyImages" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const manyImages = ref([
|
||||
{ photoUrl: "https://example.com/1.jpg", photoName: "图片1" },
|
||||
{ photoUrl: "https://example.com/2.jpg", photoName: "图片2" },
|
||||
{ photoUrl: "https://example.com/3.jpg", photoName: "图片3" },
|
||||
// ... 更多图片
|
||||
{ photoUrl: "https://example.com/10.jpg", photoName: "图片10" }
|
||||
])
|
||||
</script>
|
||||
```
|
||||
|
||||
## API 文档
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| borderRadius | Number \| String | 8 | 轮播图圆角大小,数字时单位为px |
|
||||
| images | Array | [] | 图片数据数组,为空时使用默认数据 |
|
||||
|
||||
### images 数组结构
|
||||
|
||||
```typescript
|
||||
interface ImageItem {
|
||||
photoUrl: string; // 图片URL
|
||||
photoName: string; // 图片名称/描述
|
||||
}
|
||||
```
|
||||
|
||||
## 样式定制
|
||||
|
||||
### 圆角配置示例
|
||||
|
||||
```vue
|
||||
<!-- 小圆角 -->
|
||||
<ImageSwiper :border-radius="4" />
|
||||
|
||||
<!-- 中等圆角 -->
|
||||
<ImageSwiper :border-radius="12" />
|
||||
|
||||
<!-- 大圆角 -->
|
||||
<ImageSwiper :border-radius="24" />
|
||||
|
||||
<!-- 使用rem单位 -->
|
||||
<ImageSwiper border-radius="0.5rem" />
|
||||
|
||||
<!-- 使用百分比 -->
|
||||
<ImageSwiper border-radius="10%" />
|
||||
```
|
||||
|
||||
### 动态圆角控制
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<slider
|
||||
:value="radius"
|
||||
:min="0"
|
||||
:max="50"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<ImageSwiper :border-radius="radius" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const radius = ref(8)
|
||||
|
||||
const handleChange = (e) => {
|
||||
radius.value = e.detail.value
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 响应式圆角
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageSwiper :border-radius="responsiveRadius" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
// 根据屏幕宽度动态调整圆角
|
||||
const responsiveRadius = computed(() => {
|
||||
const screenWidth = uni.getSystemInfoSync().screenWidth
|
||||
return screenWidth > 750 ? 16 : 8
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 主题适配
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ImageSwiper :border-radius="themeRadius" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
// 根据主题调整圆角
|
||||
const isDarkMode = ref(false)
|
||||
const themeRadius = computed(() => {
|
||||
return isDarkMode.value ? 12 : 8
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **圆角单位**:数字类型自动添加px单位,字符串类型直接使用
|
||||
2. **图片比例**:建议使用相同比例的图片以获得最佳显示效果
|
||||
3. **性能优化**:大量图片时建议使用懒加载
|
||||
4. **兼容性**:支持微信小程序、H5、App等平台
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.2.0
|
||||
- ✨ 新增缩略图左右滑动功能
|
||||
- ✨ 新增缩略图选中状态高亮显示
|
||||
- ✨ 新增缩略图自动滚动到可视区域
|
||||
- 🎨 优化缩略图动画效果和交互体验
|
||||
- 🔧 改进主轮播图与缩略图的联动机制
|
||||
- 📝 更新文档和演示示例
|
||||
|
||||
### v1.1.0
|
||||
- ✨ 新增 `borderRadius` 属性,支持自定义圆角
|
||||
- ✨ 新增 `images` 属性,支持自定义图片数据
|
||||
- 🔧 优化组件结构,使用计算属性提升性能
|
||||
- 📝 完善文档和示例
|
||||
|
||||
### v1.0.0
|
||||
- 🎉 初始版本发布
|
||||
- ✨ 基础轮播图功能
|
||||
- ✨ 缩略图导航
|
||||
- ✨ 进度指示器
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Vue 3 Composition API
|
||||
- SCSS
|
||||
- uni-app
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
- 微信小程序
|
||||
- H5 (Chrome, Firefox, Safari, Edge)
|
||||
- App (iOS, Android)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
151
components/ImageSwiper/demo.vue
Normal file
151
components/ImageSwiper/demo.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="demo-container">
|
||||
<view class="demo-title">ImageSwiper 轮播图组件演示</view>
|
||||
|
||||
<!-- 示例1: 默认圆角 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例1: 默认圆角 (8px)</view>
|
||||
<ImageSwiper />
|
||||
</view>
|
||||
|
||||
<!-- 示例2: 无圆角 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例2: 无圆角 (0px)</view>
|
||||
<ImageSwiper :border-radius="0" />
|
||||
</view>
|
||||
|
||||
<!-- 示例3: 大圆角 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例3: 大圆角 (20px)</view>
|
||||
<ImageSwiper :border-radius="20" />
|
||||
</view>
|
||||
|
||||
<!-- 示例4: 字符串圆角 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例4: 字符串圆角 (1rem)</view>
|
||||
<ImageSwiper border-radius="1rem" />
|
||||
</view>
|
||||
|
||||
<!-- 示例5: 自定义图片数据 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例5: 自定义图片数据 + 圆角15px</view>
|
||||
<ImageSwiper :border-radius="15" :images="customImages" />
|
||||
</view>
|
||||
|
||||
<!-- 示例7: 多图片测试滑动 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例7: 多图片测试缩略图滑动</view>
|
||||
<ImageSwiper :border-radius="10" :images="manyImages" />
|
||||
</view>
|
||||
|
||||
<!-- 示例6: 动态圆角控制 -->
|
||||
<view class="demo-section">
|
||||
<view class="section-title">示例6: 动态圆角控制</view>
|
||||
<view class="control-panel">
|
||||
<text>圆角大小: {{ dynamicRadius }}px</text>
|
||||
<slider
|
||||
:value="dynamicRadius"
|
||||
:min="0"
|
||||
:max="50"
|
||||
@change="handleRadiusChange"
|
||||
activeColor="#007AFF"
|
||||
/>
|
||||
</view>
|
||||
<ImageSwiper :border-radius="dynamicRadius" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ImageSwiper from './index.vue'
|
||||
|
||||
// 动态圆角控制
|
||||
const dynamicRadius = ref(8)
|
||||
|
||||
// 自定义图片数据
|
||||
const customImages = ref([
|
||||
{
|
||||
photoUrl: "https://fastly.picsum.photos/id/100/654/400.jpg?hmac=lYhMw5jKKjJJKjJKjJKjJKjJKjJKjJKjJKjJKjJKjJK",
|
||||
photoName: "自定义图片1",
|
||||
},
|
||||
{
|
||||
photoUrl: "https://fastly.picsum.photos/id/200/654/400.jpg?hmac=lYhMw5jKKjJJKjJKjJKjJKjJKjJKjJKjJKjJKjJKjJK",
|
||||
photoName: "自定义图片2",
|
||||
},
|
||||
{
|
||||
photoUrl: "https://fastly.picsum.photos/id/300/654/400.jpg?hmac=lYhMw5jKKjJJKjJKjJKjJKjJKjJKjJKjJKjJKjJKjJK",
|
||||
photoName: "自定义图片3",
|
||||
}
|
||||
])
|
||||
|
||||
// 多图片数据,用于测试缩略图滑动
|
||||
const manyImages = ref([
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/10/654/400.jpg", photoName: "风景1" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/20/654/400.jpg", photoName: "风景2" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/30/654/400.jpg", photoName: "风景3" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/40/654/400.jpg", photoName: "风景4" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/50/654/400.jpg", photoName: "风景5" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/60/654/400.jpg", photoName: "风景6" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/70/654/400.jpg", photoName: "风景7" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/80/654/400.jpg", photoName: "风景8" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/90/654/400.jpg", photoName: "风景9" },
|
||||
{ photoUrl: "https://fastly.picsum.photos/id/110/654/400.jpg", photoName: "风景10" }
|
||||
])
|
||||
|
||||
// 处理圆角变化
|
||||
const handleRadiusChange = (e) => {
|
||||
dynamicRadius.value = e.detail.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.demo-container {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 60rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20rpx;
|
||||
color: #333;
|
||||
border-left: 6rpx solid #007AFF;
|
||||
padding-left: 16rpx;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
margin-bottom: 30rpx;
|
||||
padding: 20rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.control-panel text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
slider {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -2,17 +2,19 @@
|
||||
<view class="image-swiper">
|
||||
<swiper
|
||||
class="swiper-box"
|
||||
:style="borderRadiusStyle"
|
||||
:autoplay="false"
|
||||
:interval="3000"
|
||||
:duration="1000"
|
||||
:current="active"
|
||||
@change="handleSwiperChange"
|
||||
>
|
||||
<swiper-item
|
||||
class="swiper-item"
|
||||
v-for="(item, index) in thumbnails"
|
||||
:key="index"
|
||||
>
|
||||
<image :src="item.url" mode="aspectFill"></image>
|
||||
<image :src="item.photoUrl" mode="aspectFill"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
@@ -20,50 +22,122 @@
|
||||
图片{{ active + 1 }}/{{ thumbnails.length }}
|
||||
</view>
|
||||
|
||||
<!-- 缩略图部分 -->
|
||||
<view class="thumbnail-box">
|
||||
<view
|
||||
v-for="(thumb, index) in thumbnails"
|
||||
:key="index"
|
||||
class="thumbnail-item"
|
||||
@click="handleThumbnailClick(index)"
|
||||
<scroll-view
|
||||
class="thumbnail-scroll"
|
||||
scroll-x="true"
|
||||
:scroll-left="scrollLeft"
|
||||
:scroll-with-animation="true"
|
||||
show-scrollbar="false"
|
||||
>
|
||||
<image :src="thumb.url" mode="aspectFill"></image>
|
||||
<text>{{ thumb.description }}</text>
|
||||
</view>
|
||||
<view class="thumbnail-list">
|
||||
<view
|
||||
v-for="(thumb, index) in thumbnails"
|
||||
:key="index"
|
||||
:class="['thumbnail-item', { active: index === active }]"
|
||||
:id="`thumbnail-${index}`"
|
||||
@click="handleThumbnailClick(index)"
|
||||
>
|
||||
<image :src="thumb.photoUrl" mode="aspectFill"></image>
|
||||
<text>{{ thumb.photoName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { ref, computed, nextTick } from "vue";
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
// 轮播图圆角大小,支持数字(px)或字符串
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 8,
|
||||
},
|
||||
// 轮播图数据
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const active = ref(0);
|
||||
const scrollLeft = ref(0);
|
||||
|
||||
const thumbnails = ref([
|
||||
// 计算圆角样式
|
||||
const borderRadiusStyle = computed(() => {
|
||||
const radius =
|
||||
typeof props.borderRadius === "number"
|
||||
? `${props.borderRadius}px`
|
||||
: props.borderRadius;
|
||||
return {
|
||||
borderRadius: radius,
|
||||
};
|
||||
});
|
||||
|
||||
// 默认图片数据
|
||||
const defaultImages = [
|
||||
{
|
||||
url: "https://fastly.picsum.photos/id/866/654/400.jpg?hmac=z3vI4CYrpnXEgimSlJCDwXRxEa-UDHiRwzGEyB8V-po",
|
||||
description: "瑶山古寨",
|
||||
photoUrl:
|
||||
"https://fastly.picsum.photos/id/866/654/400.jpg?hmac=z3vI4CYrpnXEgimSlJCDwXRxEa-UDHiRwzGEyB8V-po",
|
||||
photoName: "瑶山古寨",
|
||||
},
|
||||
{
|
||||
url: "https://fastly.picsum.photos/id/284/654/400.jpg?hmac=89XRCJxYTblKIFGLOp6hJ9U0GC8BQrcnJwE5pG21NAk",
|
||||
description: "民俗表演",
|
||||
photoUrl:
|
||||
"https://fastly.picsum.photos/id/284/654/400.jpg?hmac=89XRCJxYTblKIFGLOp6hJ9U0GC8BQrcnJwE5pG21NAk",
|
||||
photoName: "民俗表演",
|
||||
},
|
||||
{
|
||||
url: "https://fastly.picsum.photos/id/281/654/400.jpg?hmac=hcAJB7y2Xz3DVuz6S4XeQZgzaTJ_QWnxtbnaagZL6Fs",
|
||||
description: "特色美食",
|
||||
photoUrl:
|
||||
"https://fastly.picsum.photos/id/281/654/400.jpg?hmac=hcAJB7y2Xz3DVuz6S4XeQZgzaTJ_QWnxtbnaagZL6Fs",
|
||||
photoName: "特色美食",
|
||||
},
|
||||
{
|
||||
url: "https://fastly.picsum.photos/id/435/654/400.jpg?hmac=TSVDxfo-zXbunxNQK0erSG_nmKcS20xfhbQsCAXLlHo",
|
||||
description: "传统服饰",
|
||||
photoUrl:
|
||||
"https://fastly.picsum.photos/id/435/654/400.jpg?hmac=TSVDxfo-zXbunxNQK0erSG_nmKcS20xfhbQsCAXLlHo",
|
||||
photoName: "传统服饰",
|
||||
},
|
||||
{
|
||||
url: "https://fastly.picsum.photos/id/737/654/400.jpg?hmac=VED05oEK3XB0Aa_DUVoZjTAf0bHjAmNYyJky4lq5vVo",
|
||||
description: "其他",
|
||||
photoUrl:
|
||||
"https://fastly.picsum.photos/id/737/654/400.jpg?hmac=VED05oEK3XB0Aa_DUVoZjTAf0bHjAmNYyJky4lq5vVo",
|
||||
photoName: "其他",
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
// 使用传入的图片数据或默认数据
|
||||
const thumbnails = computed(() => {
|
||||
return props.images.length ? props.images : defaultImages;
|
||||
});
|
||||
|
||||
const handleThumbnailClick = (index) => {
|
||||
active.value = index;
|
||||
scrollToActiveItem(index);
|
||||
};
|
||||
|
||||
// 滚动到选中项
|
||||
const scrollToActiveItem = async (index) => {
|
||||
await nextTick();
|
||||
|
||||
// 计算每个缩略图项的宽度(包括间距)
|
||||
const itemWidth = 58; // 48px宽度 + 10px间距
|
||||
const containerWidth = 300; // 大概的容器宽度
|
||||
const targetScrollLeft = Math.max(
|
||||
0,
|
||||
index * itemWidth - containerWidth / 2 + itemWidth / 2
|
||||
);
|
||||
|
||||
scrollLeft.value = targetScrollLeft;
|
||||
};
|
||||
|
||||
// 监听主轮播图变化,同步缩略图滚动
|
||||
const handleSwiperChange = (e) => {
|
||||
const currentIndex = e.detail.current;
|
||||
active.value = currentIndex;
|
||||
scrollToActiveItem(currentIndex);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
border-radius: 8px;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
// 圆角通过内联样式动态设置
|
||||
}
|
||||
|
||||
.swiper-item image {
|
||||
@@ -31,23 +31,53 @@
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 36px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.thumbnail-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.thumbnail-list {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.thumbnail-item {
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
image {
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-item image {
|
||||
width: 48px;
|
||||
height: 38px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #fff;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.thumbnail-item text {
|
||||
color: #fff;
|
||||
font-size: 8px;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 48px;
|
||||
}
|
||||
|
||||
46
components/LocationInfo/index.vue
Normal file
46
components/LocationInfo/index.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view class="store-address" @click="openMap">
|
||||
<uni-icons type="location" size="18" color="#999" />
|
||||
<text>{{ orderData.commodityAddress }}</text>
|
||||
<uni-icons type="right" size="14" color="#999" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
|
||||
defineProps({
|
||||
orderData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
// 打开地图
|
||||
const openMap = () => {
|
||||
const address = props.orderData.commodityAddress;
|
||||
const latitude = Number(props.orderData.commodityLatitude);
|
||||
const longitude = Number(props.orderData.commodityLongitude);
|
||||
|
||||
uni.getLocation({
|
||||
type: "gcj02", //返回可以用于uni.openLocation的经纬度
|
||||
success: (res) => {
|
||||
console.log("当前经纬度", latitude, longitude);
|
||||
|
||||
uni.openLocation({
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
address: address,
|
||||
success: () => {
|
||||
console.log("success");
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
14
components/LocationInfo/styles/index.scss
Normal file
14
components/LocationInfo/styles/index.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
.store-address {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
text {
|
||||
flex: 1;
|
||||
padding: 0 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
176
components/TopNavBar/README.md
Normal file
176
components/TopNavBar/README.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# TopNavBar 顶部导航栏组件
|
||||
|
||||
一个功能完整的顶部导航栏组件,支持固定定位、自定义样式和插槽内容。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持固定在页面顶部(可配置)
|
||||
- ✅ 自动适配状态栏高度
|
||||
- ✅ 支持自定义标题和颜色
|
||||
- ✅ 支持插槽自定义内容
|
||||
- ✅ 内置返回按钮功能
|
||||
- ✅ 响应式设计
|
||||
- ✅ 深色模式支持
|
||||
- ✅ 安全区域适配
|
||||
|
||||
## 基础用法
|
||||
|
||||
### 简单使用
|
||||
```vue
|
||||
<template>
|
||||
<TopNavBar title="页面标题" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TopNavBar from '@/components/TopNavBar/index.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
### 固定在顶部
|
||||
```vue
|
||||
<template>
|
||||
<TopNavBar title="页面标题" :fixed="true" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 自定义样式
|
||||
```vue
|
||||
<template>
|
||||
<TopNavBar
|
||||
title="页面标题"
|
||||
backgroundColor="#007AFF"
|
||||
titleColor="#ffffff"
|
||||
backIconColor="#ffffff"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 标题对齐方式
|
||||
```vue
|
||||
<template>
|
||||
<!-- 标题居中显示(默认) -->
|
||||
<TopNavBar title="居中标题" titleAlign="center" />
|
||||
|
||||
<!-- 标题左对齐显示 -->
|
||||
<TopNavBar title="左对齐标题" titleAlign="left" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 使用插槽
|
||||
```vue
|
||||
<template>
|
||||
<TopNavBar>
|
||||
<template #title>
|
||||
<view class="custom-title">
|
||||
<text>自定义标题内容</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<uni-icons type="more" size="20" color="#333" />
|
||||
</template>
|
||||
</TopNavBar>
|
||||
</template>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| title | String | '' | 导航栏标题 |
|
||||
| fixed | Boolean | false | 是否固定在页面顶部 |
|
||||
| showBack | Boolean | true | 是否显示返回按钮 |
|
||||
| backgroundColor | String | '#ffffff' | 背景颜色 |
|
||||
| titleColor | String | '#333333' | 标题文字颜色 |
|
||||
| backIconColor | String | '#333333' | 返回按钮图标颜色 |
|
||||
| hideStatusBar | Boolean | false | 是否隐藏状态栏占位 |
|
||||
| zIndex | Number | 999 | 层级索引 |
|
||||
| titleAlign | String | 'center' | 标题对齐方式,可选值:'center'、'left' |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 参数 |
|
||||
|--------|------|------|
|
||||
| back | 点击返回按钮时触发 | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| 插槽名 | 说明 |
|
||||
|--------|------|
|
||||
| title | 自定义标题内容 |
|
||||
| right | 自定义右侧内容 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 订单列表页面
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<TopNavBar>
|
||||
<template #title>
|
||||
<Tabs
|
||||
:tabs="tabList"
|
||||
:defaultActive="currentTabIndex"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
</template>
|
||||
</TopNavBar>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<view class="page-content">
|
||||
<!-- 内容区域 -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 商品详情页面
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<TopNavBar
|
||||
title="商品详情"
|
||||
:fixed="true"
|
||||
@back="handleBack"
|
||||
>
|
||||
<template #right>
|
||||
<uni-icons type="share" size="20" color="#333" />
|
||||
</template>
|
||||
</TopNavBar>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<view class="page-content">
|
||||
<!-- 内容区域 -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const handleBack = () => {
|
||||
// 自定义返回逻辑
|
||||
console.log('自定义返回处理')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **固定定位使用**:当设置 `fixed="true"` 时,组件会固定在页面顶部,此时需要为页面内容添加适当的顶部间距。
|
||||
|
||||
2. **状态栏适配**:组件会自动获取系统状态栏高度并进行适配,无需手动处理。
|
||||
|
||||
3. **返回按钮**:默认点击返回按钮会执行 `uni.navigateBack()`,如果需要自定义返回逻辑,请监听 `@back` 事件。
|
||||
|
||||
4. **样式覆盖**:如需自定义样式,建议通过 props 传入颜色值,或在父组件中使用深度选择器覆盖样式。
|
||||
|
||||
5. **插槽使用**:title 插槽会完全替换默认的标题显示,right 插槽用于添加右侧操作按钮。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0
|
||||
- 初始版本发布
|
||||
- 支持基础导航栏功能
|
||||
- 支持固定定位配置
|
||||
- 支持自定义样式和插槽
|
||||
151
components/TopNavBar/demo.vue
Normal file
151
components/TopNavBar/demo.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="demo-container">
|
||||
<!-- 示例1: 基础用法 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">基础用法</view>
|
||||
<TopNavBar title="基础导航栏" />
|
||||
</view>
|
||||
|
||||
<!-- 示例2: 固定在顶部 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">固定在顶部</view>
|
||||
<TopNavBar title="固定导航栏" :fixed="true" />
|
||||
</view>
|
||||
|
||||
<!-- 示例3: 自定义颜色 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">自定义颜色</view>
|
||||
<TopNavBar
|
||||
title="蓝色导航栏"
|
||||
backgroundColor="#007AFF"
|
||||
titleColor="#ffffff"
|
||||
backIconColor="#ffffff"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 示例4: 隐藏返回按钮 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">隐藏返回按钮</view>
|
||||
<TopNavBar title="无返回按钮" :showBack="false" />
|
||||
</view>
|
||||
|
||||
<!-- 示例5: 标题左对齐 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">标题左对齐</view>
|
||||
<TopNavBar title="左对齐标题" titleAlign="left" />
|
||||
</view>
|
||||
|
||||
<!-- 示例6: 标题居中对齐(默认) -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">标题居中对齐(默认)</view>
|
||||
<TopNavBar title="居中对齐标题" titleAlign="center" />
|
||||
</view>
|
||||
|
||||
<!-- 示例7: 使用插槽 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">使用插槽</view>
|
||||
<TopNavBar>
|
||||
<template #title>
|
||||
<view class="custom-title">
|
||||
<uni-icons type="star" size="16" color="#FFD700" />
|
||||
<text class="title-text">自定义标题</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<view class="right-actions">
|
||||
<uni-icons type="search" size="20" color="#333" style="margin-right: 10px;" />
|
||||
<uni-icons type="more" size="20" color="#333" />
|
||||
</view>
|
||||
</template>
|
||||
</TopNavBar>
|
||||
</view>
|
||||
|
||||
<!-- 示例8: 渐变背景 -->
|
||||
<view class="demo-section">
|
||||
<view class="demo-title">渐变背景</view>
|
||||
<TopNavBar
|
||||
title="渐变导航栏"
|
||||
backgroundColor="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
titleColor="#ffffff"
|
||||
backIconColor="#ffffff"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="content-area">
|
||||
<view class="content-item" v-for="i in 20" :key="i">
|
||||
<text>内容项 {{ i }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import TopNavBar from './index.vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.demo-container {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin-bottom: 30px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
padding: 15px 20px;
|
||||
background-color: #f8f9fa;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.custom-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.title-text {
|
||||
margin-left: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.content-item {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
components/TopNavBar/index.vue
Normal file
133
components/TopNavBar/index.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<view :class="navBarClass" :style="navBarStyle">
|
||||
<!-- 状态栏占位 -->
|
||||
<view :style="{ height: statusBarHeight + 'px' }" v-if="!hideStatusBar"></view>
|
||||
|
||||
<!-- 导航栏内容 -->
|
||||
<view class="nav-bar-content" :style="{ height: navBarHeight + 'px' }">
|
||||
<!-- 左侧返回按钮 -->
|
||||
<view class="nav-bar-left" @click="handleBack" v-if="showBack">
|
||||
<uni-icons type="left" size="20" :color="backIconColor" />
|
||||
</view>
|
||||
|
||||
<!-- 中间标题区域 -->
|
||||
<view :class="['nav-bar-center', `nav-bar-center--${titleAlign}`]">
|
||||
<slot name="title">
|
||||
<text class="nav-bar-title" :style="{ color: titleColor }">{{ title }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 右侧操作区域 -->
|
||||
<view class="nav-bar-right">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
// 标题文本
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否固定在顶部
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示返回按钮
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 标题颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#333333'
|
||||
},
|
||||
// 返回按钮图标颜色
|
||||
backIconColor: {
|
||||
type: String,
|
||||
default: '#333333'
|
||||
},
|
||||
// 是否隐藏状态栏占位
|
||||
hideStatusBar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 自定义z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 999
|
||||
},
|
||||
// 标题对齐方式
|
||||
titleAlign: {
|
||||
type: String,
|
||||
default: 'center', // 'center' | 'left'
|
||||
validator: (value) => ['center', 'left'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits(['back'])
|
||||
|
||||
// 系统信息
|
||||
const statusBarHeight = ref(0)
|
||||
const navBarHeight = ref(44) // 默认导航栏高度
|
||||
|
||||
// 获取系统信息
|
||||
onMounted(() => {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||
|
||||
// 根据平台设置导航栏高度
|
||||
if (systemInfo.platform === 'ios') {
|
||||
navBarHeight.value = 44
|
||||
} else {
|
||||
navBarHeight.value = 48
|
||||
}
|
||||
})
|
||||
|
||||
// 计算导航栏样式类
|
||||
const navBarClass = computed(() => {
|
||||
return [
|
||||
'top-nav-bar',
|
||||
{
|
||||
'top-nav-bar--fixed': props.fixed
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 计算导航栏样式
|
||||
const navBarStyle = computed(() => {
|
||||
return {
|
||||
backgroundColor: props.backgroundColor,
|
||||
zIndex: props.zIndex
|
||||
}
|
||||
})
|
||||
|
||||
// 处理返回事件
|
||||
const handleBack = () => {
|
||||
emit('back')
|
||||
// 如果没有监听back事件,默认执行返回上一页
|
||||
if (!emit('back')) {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "./styles/index.scss";
|
||||
</style>
|
||||
108
components/TopNavBar/styles/index.scss
Normal file
108
components/TopNavBar/styles/index.scss
Normal file
@@ -0,0 +1,108 @@
|
||||
// TopNavBar 组件样式
|
||||
.top-nav-bar {
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&--fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.nav-bar-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
|
||||
.nav-bar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 20px; // 为左右按钮留出空间
|
||||
|
||||
// 居中对齐(默认)
|
||||
&--center {
|
||||
justify-content: center;
|
||||
|
||||
.nav-bar-title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 左对齐
|
||||
&--left {
|
||||
justify-content: flex-start;
|
||||
padding-left: 20px; // 为返回按钮留出更多空间
|
||||
|
||||
.nav-bar-title {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 30px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 固定导航栏时的页面内容适配
|
||||
.page-with-fixed-navbar {
|
||||
padding-top: calc(var(--status-bar-height, 44px) + 44px);
|
||||
}
|
||||
|
||||
// 安全区域适配
|
||||
.top-nav-bar {
|
||||
padding-left: constant(safe-area-inset-left);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: constant(safe-area-inset-right);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
312
components/TopNavBar/使用指南.md
Normal file
312
components/TopNavBar/使用指南.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# TopNavBar 组件使用指南
|
||||
|
||||
## 组件概述
|
||||
|
||||
TopNavBar 是一个功能完整的顶部导航栏组件,专为 uni-app 项目设计。该组件支持固定定位、自定义样式、插槽内容等功能,可以满足大部分页面的导航需求。
|
||||
|
||||
## 核心特性
|
||||
|
||||
### 1. 可配置固定定位
|
||||
- **默认行为**: 组件默认不固定,跟随页面滚动
|
||||
- **固定模式**: 设置 `fixed="true"` 可将导航栏固定在页面顶部
|
||||
- **自动适配**: 固定模式下自动处理状态栏高度和安全区域
|
||||
|
||||
### 2. 智能状态栏适配
|
||||
- 自动获取系统状态栏高度
|
||||
- 支持不同平台的导航栏高度适配(iOS: 44px, Android: 48px)
|
||||
- 可选择隐藏状态栏占位区域
|
||||
|
||||
### 3. 灵活的自定义选项
|
||||
- 支持自定义背景色、标题色、图标色
|
||||
- 可控制返回按钮显示/隐藏
|
||||
- 支持自定义 z-index 层级
|
||||
- 支持标题对齐方式配置(居中/左对齐)
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基础使用
|
||||
|
||||
```vue
|
||||
<!-- 最简单的使用方式 -->
|
||||
<TopNavBar title="页面标题" />
|
||||
```
|
||||
|
||||
### 固定在顶部
|
||||
|
||||
```vue
|
||||
<!-- 固定导航栏,适合长页面滚动 -->
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<TopNavBar title="商品详情" :fixed="true" />
|
||||
<view class="page-content">
|
||||
<!-- 页面内容 -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.page-content {
|
||||
/* 为固定导航栏预留空间 */
|
||||
padding-top: calc(var(--status-bar-height, 44px) + 44px);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 自定义样式
|
||||
|
||||
```vue
|
||||
<!-- 深色主题导航栏 -->
|
||||
<TopNavBar
|
||||
title="深色导航栏"
|
||||
backgroundColor="#1a1a1a"
|
||||
titleColor="#ffffff"
|
||||
backIconColor="#ffffff"
|
||||
/>
|
||||
|
||||
<!-- 品牌色导航栏 -->
|
||||
<TopNavBar
|
||||
title="品牌导航栏"
|
||||
backgroundColor="#007AFF"
|
||||
titleColor="#ffffff"
|
||||
backIconColor="#ffffff"
|
||||
/>
|
||||
|
||||
<!-- 左对齐标题 -->
|
||||
<TopNavBar
|
||||
title="左对齐标题"
|
||||
titleAlign="left"
|
||||
/>
|
||||
|
||||
<!-- 居中标题(默认) -->
|
||||
<TopNavBar
|
||||
title="居中标题"
|
||||
titleAlign="center"
|
||||
/>
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 使用插槽自定义内容
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<TopNavBar>
|
||||
<!-- 自定义标题区域 -->
|
||||
<template #title>
|
||||
<view class="custom-title">
|
||||
<image src="/static/logo.png" class="logo" />
|
||||
<text class="brand-name">品牌名称</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 自定义右侧操作 -->
|
||||
<template #right>
|
||||
<view class="nav-actions">
|
||||
<uni-icons type="search" size="20" @click="handleSearch" />
|
||||
<uni-icons type="more" size="20" @click="showMore" />
|
||||
</view>
|
||||
</template>
|
||||
</TopNavBar>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.custom-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.logo {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 监听返回事件
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<TopNavBar
|
||||
title="自定义返回"
|
||||
@back="handleCustomBack"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const handleCustomBack = () => {
|
||||
// 自定义返回逻辑
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要离开当前页面吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 实际应用场景
|
||||
|
||||
### 1. 商品详情页
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="goods-detail">
|
||||
<TopNavBar
|
||||
title="商品详情"
|
||||
:fixed="true"
|
||||
backgroundColor="rgba(255, 255, 255, 0.95)"
|
||||
>
|
||||
<template #right>
|
||||
<uni-icons type="share" size="20" @click="shareGoods" />
|
||||
</template>
|
||||
</TopNavBar>
|
||||
|
||||
<view class="goods-content">
|
||||
<!-- 商品内容 -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 2. 订单列表页
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="order-list">
|
||||
<TopNavBar>
|
||||
<template #title>
|
||||
<Tabs
|
||||
:tabs="orderTabs"
|
||||
:active="activeTab"
|
||||
@change="switchTab"
|
||||
/>
|
||||
</template>
|
||||
</TopNavBar>
|
||||
|
||||
<view class="order-content">
|
||||
<!-- 订单列表 -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 3. 聊天页面
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="chat-page">
|
||||
<TopNavBar
|
||||
title="客服小沐"
|
||||
:fixed="true"
|
||||
backgroundColor="#f8f9fa"
|
||||
>
|
||||
<template #right>
|
||||
<uni-icons type="phone" size="20" @click="makeCall" />
|
||||
</template>
|
||||
</TopNavBar>
|
||||
|
||||
<view class="chat-content">
|
||||
<!-- 聊天内容 -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 固定导航栏的页面布局
|
||||
|
||||
```scss
|
||||
// 推荐的页面结构
|
||||
.page-container {
|
||||
.page-content {
|
||||
// 方法1: 使用 padding-top
|
||||
padding-top: calc(var(--status-bar-height, 44px) + 44px);
|
||||
|
||||
// 方法2: 使用 margin-top
|
||||
// margin-top: calc(var(--status-bar-height, 44px) + 44px);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 响应式设计
|
||||
|
||||
```scss
|
||||
// 适配不同屏幕尺寸
|
||||
@media screen and (max-width: 375px) {
|
||||
.page-content {
|
||||
padding-top: calc(var(--status-bar-height, 44px) + 40px);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 主题适配
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
// 根据系统主题动态调整颜色
|
||||
const navBarStyle = computed(() => {
|
||||
const isDark = uni.getSystemInfoSync().theme === 'dark'
|
||||
return {
|
||||
backgroundColor: isDark ? '#1a1a1a' : '#ffffff',
|
||||
titleColor: isDark ? '#ffffff' : '#333333',
|
||||
backIconColor: isDark ? '#ffffff' : '#333333'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TopNavBar
|
||||
title="自适应主题"
|
||||
:backgroundColor="navBarStyle.backgroundColor"
|
||||
:titleColor="navBarStyle.titleColor"
|
||||
:backIconColor="navBarStyle.backIconColor"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **固定定位的性能考虑**: 固定导航栏会创建新的层叠上下文,在复杂页面中可能影响性能
|
||||
|
||||
2. **状态栏适配**: 在不同设备上状态栏高度可能不同,组件会自动处理,但建议在测试时验证各种设备
|
||||
|
||||
3. **插槽内容**: 使用插槽时注意内容的响应式设计,确保在不同屏幕尺寸下都能正常显示
|
||||
|
||||
4. **z-index 管理**: 如果页面中有其他固定定位元素,注意调整 z-index 避免层级冲突
|
||||
|
||||
5. **返回按钮**: 默认返回行为是 `uni.navigateBack()`,如需自定义请监听 `@back` 事件
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 固定导航栏下的内容被遮挡了?**
|
||||
A: 需要为页面内容添加顶部间距,参考上面的最佳实践。
|
||||
|
||||
**Q: 在某些设备上状态栏高度不正确?**
|
||||
A: 组件会自动获取状态栏高度,如果仍有问题,可以手动设置 `hideStatusBar="true"` 并自行处理。
|
||||
|
||||
**Q: 自定义颜色不生效?**
|
||||
A: 确保传入的颜色值格式正确,支持 hex、rgb、rgba 等标准 CSS 颜色格式。
|
||||
|
||||
**Q: 插槽内容显示异常?**
|
||||
A: 检查插槽内容的样式,确保没有影响导航栏布局的 CSS 属性。
|
||||
Reference in New Issue
Block a user