uni-app开发极简入门(11):表格与表单
在移动客户端,通常来说表格页用的比较少,多是元素很丰富的页面,不过考虑到表格页还是很基础的且我个人没有美术资源,搞不来那么漂亮的页面了,就写个简简单单的表格页。
创建pages/user/user.vue:
<template><viewclass="container"><!-- 数据列表 --><viewclass="list-item"v-for="item in userList":key="item.id" @click="handleItemClick(item)">{{ item.id }},{{item.name}},{{item.idCard}}</view><!-- 加载状态 --><viewclass="loading-wrapper"><textv-if="isRefreshing"class="loading-text">加载中...</text><textv-if="!hasMore"class="no-more">没有更多数据了</text></view></view></template><scriptlang='ts'setupname='UniUser'>import {onMounted,ref} from 'vue'import {BaseResponse,Page} from '../../common/params'import {UserDetail} from '../user/userVo'import {request} from '../../utils/request'import {onShow} from "@dcloudio/uni-app"import {onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app';// 类型定义const isRefreshing = ref(false) // 下拉刷新状态const isLoading = ref(false) // 加载状态const userList = ref < UserDetail[] > ([])const page = ref(1)const pageSize = 20const hasMore = ref(true)const scrollViewHeight = ref('100vh')// POST请求const getUserList = async (initData: boolean = true) => {try {isLoading.value = trueisRefreshing.value = trueif (initData) {page.value = 1}const res = await request < BaseResponse < Page < UserDetail[] >>> ({url: '/user/page',method: 'POST',data: {page: page.value,pageSize: pageSize},header: {'Content-Type': 'application/json'}})if (res.code === 0) {const response = res.data as unknown as Page < UserDetail[] >if (initData) {userList.value = response.list} else {userList.value = [...userList.value, ...response.list]}hasMore.value = userList.value.length < response.totalpage.value++uni.showToast({title: '列表获取成功',icon: 'success'})} else {throw new Error(res.msg)}} catch (error) {uni.showToast({title: '获取失败',icon: 'none'})console.error('POST请求错误:', error)} finally {isLoading.value = falseisRefreshing.value = falseuni.stopPullDownRefresh();//很重要,数据请求无论成功或失败,都要通过此方法管边下拉刷新的加载动画,否则动画会一直持续}}const handleItemClick = (item: UserDetail) => {uni.navigateTo({url: `/pages/user/userDetail?id=${item.id}` // 跳转到详情页并传递id参数});}// 下拉刷新处理// 监听下拉刷新事件onPullDownRefresh(() => {console.log("下拉刷新");if (isLoading.value) {return}getUserList()});// 监听上拉触底事件onReachBottom(() => {console.log("上拉加载");if (isLoading.value || !hasMore.value) {return}getUserList(false)});/*** onShow是页面切出去再切回来,就执行。与之对应的是onHide* onMounted是页面第一次加载执行,页面切出去再切回来,不执行。与之对应的是onUnmounted*/onMounted(() => {console.log("列表页面加载")// 计算滚动区域高度(根据实际布局调整)const systemInfo = uni.getSystemInfoSync()scrollViewHeight.value = `calc(${systemInfo.windowHeight}px - 100px)` // 示例中减去顶部高度// 初始化加载数据getUserList()})</script><stylescoped>.container {height: 100vh;display: flex;flex-direction: column;}.scroll-view {flex: 1;height: 100%;}.list-item {padding: 24rpx;border-bottom: 1rpx solid #eee;font-size: 28rpx;}.loading-wrapper {padding: 30rpx 0;text-align: center;}.loading-text {color: #999;font-size: 26rpx;display: flex;align-items: center;justify-content: center;}.no-more {color: #ccc;font-size: 26rpx;text-align: center;}/* 加载动画 */.loading-text::after {content: '';display: inline-block;width: 20rpx;height: 20rpx;border: 3rpx solid #ddd;border-radius: 50%;border-top-color: #007AFF;margin-left: 10rpx;animation: spin 0.6s linear infinite;}</style>
-
服务端的接口代码就不放了。
-
BaseResponse、UserDetail这些代码之前的文章都有,也不放了。
-
跳转之详情页,我只用url传了一个id,如果传复杂对象,可用前文讲到的EventChannel。
-
网络请求相关代码都简单,不讲了。
-
主要是下拉刷新、下拉加载,实现的方案有很多,我使用了uni-app原生的onPullDownRefresh,onReachBottom,与之对应的也要修改pages.json。
修改pages.json中的”pages”:
{"path": "pages/user/user","style": {"navigationBarTitleText": "原生用户","enablePullDownRefresh": true}}
设置配置项enablePullDownRefresh为true,开启下拉刷新。
详情页pages/user/userDetail:
<template><viewclass="container"><scroll-viewscroll-yclass="scroll-view"><!-- 自定义卡片容器 --><viewv-if="userDetail.id"class="custom-card"><viewclass="card-header"><textclass="card-title">用户详情</text></view><viewclass="card-body"><viewclass="detail-item"><textclass="label">用户ID:</text><textclass="value">{{ userDetail.id }}</text></view><viewclass="detail-item"><textclass="label">姓名:</text><textclass="value">{{ userDetail.name }}</text></view><viewclass="detail-item"><textclass="label">身份证:</text><textclass="value">{{ userDetail.idCard }}</text></view><viewclass="detail-item"><textclass="label">手机号:</text><textclass="value">{{ userDetail.phone }}</text></view><viewclass="detail-item"><textclass="label">性别:</text><textclass="value">{{ displayGender }}</text></view><viewclass="detail-item"><textclass="label">地址:</text><textclass="value">{{ userDetail.address }}</text></view></view></view><!-- 自定义加载状态 --><viewv-if="loading"class="loading-wrapper"><viewclass="loading-content"><textclass="loading-text">加载中...</text></view></view></scroll-view></view></template><scriptlang='ts'setupname='UserDetail'>import {ref,onMounted,computed} from 'vue'import {onLoad} from "@dcloudio/uni-app"import {UserDetail} from './userVo';import {BaseResponse} from '../../common/params';import {request} from '../../utils/request';// 用户详情数据const userDetail = ref < UserDetail > ({id: '-',name: '-',idCard: '-',phone: '-',gender: 'male',birthDate: '2021-01-01',address: '-'})const loading = ref(false)// 计算属性显示性别const displayGender = computed(() => {return userDetail.value.gender === 'male' ? '男' : '女'})const fetchUserDetail = async (id: string) => {try {loading.value = trueconst res = await request < BaseResponse < UserDetail >> ({url: '/user/getUserById',data: {userId: id}})if (res.code === 0) {const response = res.data as unknown as UserDetailuserDetail.value = responseuni.showToast({title: '用户详情获取成功',icon: 'success'})} else {throw new Error(res.msg)}} catch (error) {uni.showToast({title: '获取失败:' + error.message,icon: "error"})console.error('POST请求错误:', error)} finally {loading.value = false}}// 从路由参数获取IDonLoad((options: any) => {const userId = options.id as stringif (userId) {fetchUserDetail(userId)} else {uni.showToast({title: '用户ID无效',icon: 'none'})uni.navigateBack()}})</script><stylescoped>.container {padding: 20rpx;height: 100vh;background-color: #f8f8f8;}.scroll-view {height: 100%;}.custom-card {margin: 20rpx;background: #fff;border-radius: 16rpx;box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);}.card-header {padding: 24rpx 32rpx;border-bottom: 2rpx solid #f0f0f0;}.card-title {font-size: 34rpx;font-weight: 600;color: #333;}.card-body {padding: 32rpx;}.detail-item {display: flex;justify-content: space-between;align-items: center;padding: 24rpx 0;}.detail-item:not(:last-child) {border-bottom: 1rpx solid #eee;}.label {color: #666;font-size: 28rpx;flex-shrink: 0;}.value {color: #333;font-size: 30rpx;max-width: 70%;text-align: right;word-break: break-all;}.loading-wrapper {display: flex;justify-content: center;padding: 60rpx 0;}.loading-content {display: flex;flex-direction: column;align-items: center;}.loading-icon {width: 80rpx;height: 80rpx;margin-bottom: 20rpx;animation: rotate 1s linear infinite;}.loading-text {font-size: 26rpx;color: #999;}@keyframes rotate {from {transform: rotate(0deg);}to {transform: rotate(360deg);}}</style>
通过onLoad从路由参数获取ID。
夜雨聆风
