vue3+ts+uniapp+unibest 微信小程序(第二篇)—— 图文详解自定义背景图页面布局、普通页面布局、分页表单页面布局
文章目录
- 简介
- 一、自定义背景图布局
- 1.1 效果预览
- 1.2 实现思路
- 1.3 custom-page 组件全量代码
- 1.4 页面使用
- 二、普通页面布局
- 2.1 效果预览
- 2.2 实现思路
- 2.3 公共样式部分
- 2.4 页面使用
- 三、分页表单页面布局
- 3.1 效果预览
- 3.2 实现思路
- 3.3 页面代码
简介
开发工具:VsCode
技术栈:vue3 + Ts + uni-app + unibest + Wot Design Uni
简介:图文结合,十分钟带你搞定微信小程序常见页面布局
一、自定义背景图布局
1.1 效果预览
1.2 实现思路
- 因为小程序原生顶部导航栏只有白色、黑色两种背景色,所以使用自定义导航栏
- 提高代码复用率,采用组件封装得形式。代码见 ——》步骤 1.3 custom-page 组件全量代码
- wot design uni 组件库的 wd-navbar 组件刚好符合需求
- 关键:wd-navbar 组件有一个属性(placeholder:固定在顶部时,在标签位置生成一个等高的占位元素),解决了顶部高度计算的问题
- 具体文档所在位置如下图
1.3 custom-page 组件全量代码
<template>
<view class="h-full w-full bg-[#F5F5F5] custom-page-container">
<!-- 自定义顶部背景图(可选) -->
<image src="@/static/images/home/bg.png" class="custom-page-bg" />
<!-- 页面内容 -->
<view class="custom-page-main">
<!-- 自定义顶部--导航栏 -->
<wd-navbar
:title="title"
safeAreaInsetTop
placeholder
:left-arrow="showBack"
@click-left="handleClickLeft"
custom-class="wd-navbar-custom"
></wd-navbar>
<!-- 页面主体--功能版块 -->
<view class="custom-page-content">
<!-- 页面主体内容--插槽 -->
<view :class="showPagePadding ? 'page-content-p' : ''" class="custom-slot-content">
<slot name="content"></slot>
</view>
<!-- 页面底部按钮--插槽 -->
<slot name="footer"></slot>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
defineProps({
// navbar标题
title: {
type: String,
default: '',
required: true,
},
// 是否显示返回按钮
showBack: {
type: Boolean,
default: true,
},
// 页面主体是否需要展示左右边距
showPagePadding: {
type: Boolean,
default: true,
},
})
/**
* 返回上一级
*/
function handleClickLeft() {
uni.navigateBack()
}
</script>
<style lang="scss" scoped>
.custom-page-container {
position: relative;
.custom-page-bg {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 668rpx;
}
.custom-page-main {
position: absolute;
top: 0;
left: 0;
z-index: 2;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
.custom-page-content {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
max-height: 100%;
overflow: hidden;
.custom-slot-content {
flex: 1;
overflow: hidden;
}
}
}
}
</style>
1.4 页面使用
二、普通页面布局
2.1 效果预览
2.2 实现思路
- 整个页面 flex 布局,中间部分 flex:1。
- 抽离公共布局样式,不采用组件封装方式,减小心智负担
2.3 公共样式部分
// /src/style/public.scss
// 公共页面布局样式--简易版
.custom-page-simple {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
overflow: hidden;
background-color: $open-bg-grey;
.page-main {
flex: 1;
padding: 28rpx;
margin: 28rpx;
overflow: auto;
background: $open-bg-primary;
border-radius: $open-border-radius;
}
.page-footer {
width: 100%;
height: 130rpx;
padding: 30rpx 28rpx;
background-color: $open-text-color-inverse;
box-shadow: 0rpx 0rpx 27.78rpx 0rpx rgba(9, 197, 133, 0.25);
}
}
2.4 页面使用
三、分页表单页面布局
3.1 效果预览
3.2 实现思路
- 使用 z-paging 组件
- z-paging 官方文档:z-paging官方文档
- 组件安装方式参考官方,没有比这个更清晰的了
3.3 页面代码
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '公告列表',
},
custom: true,
}
</route>
<template>
<z-paging
ref="pagingRef"
v-model="noticeList"
:paging-style="{ backgroundColor: '#fff' }"
:default-page-size="pageQuery.size"
@query="queryList"
>
<!-- 顶部搜索栏 -->
<template #top>
<wd-search
ref="searchRef"
v-model="pageQuery.someText"
placeholder="请输入搜索内容"
placeholder-left
hide-cancel
:maxlength="50"
@change="handleSearchChangeDebounce"
/>
</template>
<!-- 通告列表 -->
<div class="notice-list">
<div class="notice-item" v-for="(item, index) in noticeList" :key="index">
<!-- 标题 -->
<div class="notice-title">{{ item.title }}</div>
<!-- 内容 -->
<span v-if="!item.details || item.details.length < 58" class="notice-content">
{{ item.details || '--' }}
</span>
<wd-collapse
v-else
v-model="item.showMoreContent"
viewmore
custom-class="notice-content"
:line-num="2"
>
{{ item.details || '--' }}
</wd-collapse>
<!-- 发布时间 -->
<div class="notice-publish-time">
{{ '发布时间:' + parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}') }}
</div>
<!-- 图片、视频列表 -->
<div class="notice-file flex items-center">
<div
class="notice-file-item"
v-for="(file, fileIndex) in item.pictureUrlList"
:key="fileIndex"
>
<wd-img
:width="100"
:height="75"
:src="file"
:enable-preview="true"
mode="aspectFill"
/>
</div>
<div
class="notice-file-item"
v-for="(file, fileIndex) in item.videoUrlList"
:key="fileIndex"
>
<video :src="file" :id="`video${fileIndex}`" controls></video>
</div>
</div>
</div>
</div>
</z-paging>
</template>
<script lang="ts" setup>
import { debounce } from 'lodash-es'
import { useUserStore } from '@/store'
import { fetchNoticePageList } from '@/api/fitness-test/notice'
import { INoticePageQuery, INoticePageResponseListItem } from '@/api/fitness-test/notice/types'
import { parseTime } from '@/utils/business'
const userStore = useUserStore()
// 通知公列表--数据
const noticeList = ref<INoticePageResponseListItem[]>([])
/** ================================ 分页请求 start ================================== */
// z-paging 组件实例
const pagingRef = ref<ZPagingRef>()
// 分页查询参数
const pageQuery = reactive<Omit<INoticePageQuery, 'schoolId'>>({
current: 1,
size: 6,
someText: '',
})
// 搜索框实例
const searchRef = ref()
// 数据总数
const total = ref(0)
// 学校id
const schoolId = computed(() => userStore.getUserInfo.schoolId)
/**
* 分页查询
* @param pageNo 当前页
* @param pageSize 每页条数
*/
function queryList(pageNo: number, pageSize: number) {
// console.log('queryList', pageNo, pageSize)
fetchNoticePageList({
current: pageNo,
size: pageSize,
schoolId: schoolId.value,
someText: pageQuery.someText,
})
.then((res) => {
const { count, data, size, current } = res.data
// 赋值给total
total.value = count
// 赋值给pageQuery.size
pageQuery.size = size
// 赋值给pageQuery.current
pageQuery.current = current
const resultList = (data || []).map((item) => {
return {
...item,
showMoreContent: false,
pictureUrlList: item.pictureUrlList || [],
videoUrlList: item.videoUrlList || [],
}
})
// 将请求的结果数组传递给z-paging
// 参考文档:https://z-paging.zxlee.cn/api/methods/main.html#%E6%95%B0%E6%8D%AE%E5%88%B7%E6%96%B0-%E5%A4%84%E7%90%86%E6%96%B9%E6%B3%95
pagingRef.value?.completeByTotal([...resultList], total.value)
})
.catch((err) => {
console.log(err)
pagingRef.value?.complete(false)
})
}
// 为提升性能,避免高频触发接口,搜索框改变时触发用 防抖 函数
const handleSearchChangeDebounce = debounce(handleSearchChange, 500)
/**
* 搜索框改变时触发
* @param value 搜索框的值
*/
function handleSearchChange(ipt: { value: string }) {
// console.log('handleSearchChange', ipt)
// 重新请求
pagingRef.value?.reload()
}
/** ================================ 分页请求 end ================================== */
</script>