{{wikiTitle}}
移动端添加页面说明
目录:
移动端添加页面说明
概述
CRMEB多店系统的移动端采用 uni-app 框架开发,支持编译到微信小程序、H5、APP等多个平台。本文档详细介绍如何在移动端项目中添加新页面,包括页面创建、路由配置、API对接、组件使用等完整流程。
目录结构
view/uniapp/
├── api/ # API接口定义目录
│ ├── api.js # 公共接口
│ ├── user.js # 用户相关接口
│ ├── order.js # 订单相关接口
│ ├── store.js # 门店相关接口
│ └── activity.js # 活动相关接口
├── components/ # 公共组件目录
│ ├── skeleton/ # 骨架屏组件
│ ├── countDown/ # 倒计时组件
│ ├── productWindow/ # 商品弹窗组件
│ └── ... # 更多组件
├── config/ # 配置文件目录
│ └── app.js # 应用配置(接口地址等)
├── libs/ # 工具库目录
│ ├── login.js # 登录相关
│ └── new_chat.js # 聊天WebSocket
├── pages/ # 主包页面目录
│ ├── index/ # 首页
│ ├── user/ # 个人中心
│ ├── goods_details/ # 商品详情
│ └── ... # 更多页面
├── store/ # Vuex状态管理
│ ├── index.js # Store入口
│ ├── modules/ # 模块化Store
│ └── getters.js # 全局Getters
├── utils/ # 工具函数目录
│ ├── request.js # 请求封装
│ ├── util.js # 通用工具函数
│ └── cache.js # 缓存管理
├── static/ # 静态资源目录
├── App.vue # 应用入口组件
├── main.js # 应用入口文件
├── pages.json # 页面路由配置
├── manifest.json # 应用配置文件
└── uni.scss # 全局样式变量
完整开发流程
第一步:创建页面文件
1.1 确定页面位置
根据页面功能确定放置位置:
- 主包页面:放在
pages/目录下,启动时加载 - 分包页面:放在
pages/users/、pages/activity/等分包目录下
# 主包页面示例(首页相关)
view/uniapp/pages/my_feature/
├── index.vue # 页面主文件
├── components/ # 页面私有组件
│ └── MyComponent.vue
└── static/ # 页面私有静态资源
└── icon.png
# 分包页面示例(用户相关)
view/uniapp/pages/users/my_page/
└── index.vue
1.2 创建基础页面模板
<!-- pages/my_feature/index.vue -->
<template>
<view class="my-feature" :style="colorStyle">
<!-- 骨架屏(可选) -->
<skeleton
:show="showSkeleton"
ref="skeleton"
loading="chiaroscuro"
bgcolor="#FFF">
</skeleton>
<!-- 页面内容 -->
<view v-if="!showSkeleton">
<!-- 导航栏 -->
<view class="nav-bar">
<text class="title">页面标题</text>
</view>
<!-- 主体内容 -->
<view class="content">
<view class="card" v-for="item in dataList" :key="item.id">
<text>{{ item.name }}</text>
</view>
</view>
<!-- 空状态 -->
<emptyPage
v-if="!dataList.length && !loading"
title="暂无数据">
</emptyPage>
<!-- 加载更多 -->
<view class="load-more" v-if="dataList.length">
<text v-if="loading">加载中...</text>
<text v-else-if="!hasMore">没有更多了</text>
</view>
</view>
</view>
</template>
<script>
// 引入API
import { getMyDataList } from '@/api/my_api.js';
// 引入工具函数
import { toLogin } from '@/libs/login.js';
// 引入颜色混入
import colors from "@/mixins/color.js";
export default {
name: 'MyFeature',
// 混入主题色
mixins: [colors],
// 页面数据
data() {
return {
showSkeleton: true, // 骨架屏显示
loading: false, // 加载状态
dataList: [], // 数据列表
page: 1, // 当前页码
limit: 10, // 每页数量
hasMore: true, // 是否有更多
// 页面参数
pageParams: {}
};
},
// 计算属性
computed: {
// 示例:计算总数
totalCount() {
return this.dataList.length;
}
},
// 页面加载
onLoad(options) {
// 接收页面参数
this.pageParams = options;
// 获取初始数据
this.getInitData();
},
// 页面显示
onShow() {
// 刷新数据等操作
},
// 下拉刷新
onPullDownRefresh() {
this.refreshData();
},
// 上拉加载更多
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore();
}
},
// 分享给朋友
onShareAppMessage() {
return {
title: '分享标题',
path: '/pages/my_feature/index',
imageUrl: ''
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '分享标题',
query: ''
};
},
methods: {
// 获取初始数据
async getInitData() {
this.showSkeleton = true;
try {
await this.getDataList();
} finally {
this.showSkeleton = false;
}
},
// 获取数据列表
async getDataList() {
this.loading = true;
try {
const params = {
page: this.page,
limit: this.limit
};
const res = await getMyDataList(params);
if (this.page === 1) {
this.dataList = res.data.list || [];
} else {
this.dataList = [...this.dataList, ...(res.data.list || [])];
}
// 判断是否有更多
this.hasMore = this.dataList.length < res.data.count;
} catch (error) {
uni.showToast({
title: error.msg || '获取数据失败',
icon: 'none'
});
} finally {
this.loading = false;
}
},
// 刷新数据
async refreshData() {
this.page = 1;
this.hasMore = true;
await this.getDataList();
uni.stopPullDownRefresh();
},
// 加载更多
async loadMore() {
this.page++;
await this.getDataList();
},
// 跳转页面
navigateTo(url) {
uni.navigateTo({ url });
},
// 返回上一页
navigateBack() {
uni.navigateBack();
}
}
};
</script>
<style lang="scss" scoped>
.my-feature {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: env(safe-area-inset-bottom);
.nav-bar {
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
.title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
}
.content {
padding: 20rpx;
.card {
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
}
}
.load-more {
text-align: center;
padding: 30rpx;
color: #999;
font-size: 24rpx;
}
}
</style>
第二步:配置页面路由
在 pages.json 中配置页面路由:
2.1 主包页面配置
{
"pages": [
// 在此添加主包页面
{
"path": "pages/my_feature/index",
"style": {
"navigationBarTitleText": "我的功能",
"enablePullDownRefresh": true,
// 自定义导航栏(可选)
"navigationStyle": "custom",
"navigationBarTextStyle": "black",
// APP特殊配置
"app-plus": {
"titleNView": {
"type": "default"
}
}
}
}
]
}
2.2 分包页面配置
{
"subPackages": [
{
"root": "pages/users",
"name": "users",
"pages": [
// 在分包中添加页面
{
"path": "my_page/index",
"style": {
"navigationBarTitleText": "我的页面",
"navigationBarBackgroundColor": "#f5f5f5",
"navigationBarTextStyle": "black",
"app-plus": {
"titleNView": {
"type": "default"
}
}
}
}
]
}
]
}
2.3 页面样式配置说明
| 属性 | 说明 | 可选值 |
|---|---|---|
navigationBarTitleText |
导航栏标题 | 字符串 |
navigationBarBackgroundColor |
导航栏背景色 | 十六进制颜色 |
navigationBarTextStyle |
导航栏标题颜色 | black/white |
navigationStyle |
导航栏样式 | default/custom |
enablePullDownRefresh |
开启下拉刷新 | true/false |
disableScroll |
禁止页面滚动 | true/false |
第三步:创建API接口
在 api/ 目录下创建或编辑API文件:
3.1 创建新的API文件
// api/my_api.js
import request from "@/utils/request.js";
/**
* 获取我的数据列表
* @param {Object} data - 请求参数
* @param {number} data.page - 页码
* @param {number} data.limit - 每页数量
*/
export function getMyDataList(data) {
return request.get('my/data/list', data);
}
/**
* 获取我的数据详情
* @param {number} id - 数据ID
*/
export function getMyDataDetail(id) {
return request.get(`my/data/detail/${id}`);
}
/**
* 提交我的数据
* @param {Object} data - 提交的数据
*/
export function submitMyData(data) {
return request.post('my/data/submit', data);
}
/**
* 更新我的数据
* @param {number} id - 数据ID
* @param {Object} data - 更新的数据
*/
export function updateMyData(id, data) {
return request.put(`my/data/update/${id}`, data);
}
/**
* 删除我的数据
* @param {number} id - 数据ID
*/
export function deleteMyData(id) {
return request.delete(`my/data/delete/${id}`);
}
/**
* 无需登录的接口示例
* 设置 noAuth: true 即可无需登录访问
*/
export function getPublicData() {
return request.get('public/data', {}, {
noAuth: true
});
}
3.2 请求封装说明
utils/request.js 封装了统一的请求方法:
// 请求方法
request.get(url, data, options) // GET请求
request.post(url, data, options) // POST请求
request.put(url, data, options) // PUT请求
request.delete(url, data, options) // DELETE请求
// options 配置
{
noAuth: false, // 是否无需登录,默认false
noVerify: false // 是否不验证返回结果,默认false
}
// 状态码说明
// 200 - 成功
// 410000 - 请登录
// 410001 - 登录已过期
// 410002 - 登录状态有误
// 410010 - 站点升级中
// 410020 - 账号被禁止
第四步:使用组件
4.1 全局组件
在 main.js 中已注册的全局组件可直接使用:
<template>
<!-- 骨架屏组件 -->
<skeleton :show="loading" ref="skeleton"></skeleton>
<!-- 金额显示组件 -->
<BaseMoney :money="price" symbolSize="24" integerSize="36"></BaseMoney>
<!-- 标签组件 -->
<BaseTag text="热销" color="#ff5500" background="#fff5f0"></BaseTag>
<!-- 抽屉组件 -->
<baseDrawer :visible="showDrawer" @close="showDrawer = false">
<view>抽屉内容</view>
</baseDrawer>
<!-- 图片懒加载组件 -->
<easyLoadimage :image-src="imageUrl" mode="widthFix"></easyLoadimage>
</template>
4.2 引入局部组件
<script>
// 引入组件
import emptyPage from '@/components/emptyPage.vue';
import productWindow from '@/components/productWindow/productWindow.vue';
import countDown from '@/components/countDown/countDown.vue';
export default {
components: {
emptyPage,
productWindow,
countDown
}
}
</script>
4.3 常用组件说明
| 组件 | 路径 | 用途 |
|---|---|---|
emptyPage |
components/emptyPage.vue |
空状态提示 |
skeleton |
components/skeleton/index.vue |
骨架屏加载 |
countDown |
components/countDown/countDown.vue |
倒计时 |
productWindow |
components/productWindow/productWindow.vue |
商品SKU选择弹窗 |
couponWindow |
components/couponWindow/couponWindow.vue |
优惠券选择弹窗 |
addressWindow |
components/addressWindow/addressWindow.vue |
地址选择弹窗 |
NavBar |
components/NavBar.vue |
自定义导航栏 |
第五步:状态管理(Vuex)
5.1 Store结构
store/
├── index.js # Store入口
├── getters.js # 全局Getters
└── modules/ # 模块化Store
└── app.js # 应用状态模块
5.2 使用Store
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
computed: {
// 映射state
...mapState(['app']),
// 获取用户信息
userInfo() {
return this.$store.state.app.userInfo;
},
// 获取token
token() {
return this.$store.state.app.token;
},
// 映射getters
...mapGetters(['isLogin'])
},
methods: {
// 直接调用
setUserInfo(info) {
this.$store.commit('SET_USERINFO', info);
},
// 映射mutations
...mapMutations(['SET_TOKEN', 'SET_USERINFO']),
// 映射actions
...mapActions(['logout'])
}
}
</script>
5.3 创建新的Store模块
// store/modules/myModule.js
const state = {
myData: null
};
const mutations = {
SET_MY_DATA(state, data) {
state.myData = data;
}
};
const actions = {
async fetchMyData({ commit }) {
// 异步操作
const data = await someApi();
commit('SET_MY_DATA', data);
}
};
export default {
state,
mutations,
actions
};
第六步:混入(Mixins)
项目提供了常用的混入功能:
6.1 颜色主题混入
<script>
import colors from "@/mixins/color.js";
export default {
mixins: [colors],
// 可直接使用 colorStyle 计算属性
// 在模板中::style="colorStyle"
}
</script>
<template>
<view :style="colorStyle">
<!-- 内容会应用主题色 -->
</view>
</template>
第七步:工具函数
7.1 常用工具函数
// utils/util.js 中的常用函数
// 使用方式
import { formatTime, formatPrice, deepClone } from '@/utils/util.js';
// 或通过全局挂载使用
this.$util.formatTime(new Date());
7.2 缓存管理
// utils/cache.js
import Cache from '@/utils/cache.js';
// 设置缓存
Cache.set('key', 'value');
Cache.set('key', 'value', 3600); // 带过期时间(秒)
// 获取缓存
const value = Cache.get('key');
// 删除缓存
Cache.remove('key');
// 清空缓存
Cache.clear();
// 检查是否存在
const has = Cache.has('key');
7.3 登录相关
// libs/login.js
import { toLogin, checkLogin } from '@/libs/login.js';
// 检查是否登录
if (!checkLogin()) {
// 跳转登录
toLogin();
}
平台条件编译
uni-app支持条件编译,针对不同平台编写不同代码:
模板条件编译
<template>
<!-- 仅在微信小程序显示 -->
<!-- #ifdef MP-WEIXIN -->
<button open-type="share">分享给好友</button>
<!-- #endif -->
<!-- 仅在H5显示 -->
<!-- #ifdef H5 -->
<button @click="shareH5">分享</button>
<!-- #endif -->
<!-- 仅在APP显示 -->
<!-- #ifdef APP-PLUS -->
<button @click="shareApp">APP分享</button>
<!-- #endif -->
<!-- 在小程序和APP中显示 -->
<!-- #ifdef MP || APP-PLUS -->
<view class="native-only">原生平台专属内容</view>
<!-- #endif -->
</template>
JavaScript条件编译
export default {
methods: {
share() {
// #ifdef MP-WEIXIN
// 微信小程序分享逻辑
wx.showShareMenu({
withShareTicket: true
});
// #endif
// #ifdef H5
// H5分享逻辑
this.h5Share();
// #endif
// #ifdef APP-PLUS
// APP分享逻辑
plus.share.getServices();
// #endif
}
}
}
CSS条件编译
<style lang="scss">
.container {
/* #ifdef H5 */
padding-top: 44px; /* H5导航栏高度 */
/* #endif */
/* #ifdef MP */
padding-top: 0; /* 小程序有原生导航栏 */
/* #endif */
/* #ifdef APP-PLUS */
padding-top: var(--status-bar-height);
/* #endif */
}
</style>
常用编译条件
| 条件 | 说明 |
|---|---|
MP |
所有小程序 |
MP-WEIXIN |
微信小程序 |
MP-ALIPAY |
支付宝小程序 |
H5 |
H5网页 |
APP-PLUS |
APP(iOS和Android) |
APP-PLUS-NVUE |
APP nvue页面 |
页面生命周期
export default {
// 页面加载时触发,只触发一次
onLoad(options) {
console.log('页面参数:', options);
},
// 页面显示时触发,每次显示都触发
onShow() {
console.log('页面显示');
},
// 页面初次渲染完成
onReady() {
console.log('页面渲染完成');
},
// 页面隐藏时触发
onHide() {
console.log('页面隐藏');
},
// 页面卸载时触发
onUnload() {
console.log('页面卸载');
},
// 下拉刷新(需在pages.json中开启)
onPullDownRefresh() {
// 处理刷新
uni.stopPullDownRefresh();
},
// 上拉触底
onReachBottom() {
// 加载更多
},
// 页面滚动
onPageScroll(e) {
console.log('滚动距离:', e.scrollTop);
},
// 用户点击分享
onShareAppMessage() {
return {
title: '分享标题',
path: '/pages/index/index'
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '分享标题'
};
}
}
页面跳转与传参
跳转方式
// 保留当前页面,跳转到新页面
uni.navigateTo({
url: '/pages/detail/index?id=1&name=test'
});
// 关闭当前页面,跳转到新页面
uni.redirectTo({
url: '/pages/other/index'
});
// 关闭所有页面,跳转到新页面
uni.reLaunch({
url: '/pages/index/index'
});
// 切换Tab页面
uni.switchTab({
url: '/pages/user/index'
});
// 返回上一页
uni.navigateBack({
delta: 1 // 返回的页面数
});
接收参数
export default {
onLoad(options) {
// 接收URL参数
const id = options.id;
const name = options.name;
// 复杂参数需要编码
const data = JSON.parse(decodeURIComponent(options.data));
}
}
页面间通信
// 方式1:使用EventChannel(推荐)
// 发送页面
uni.navigateTo({
url: '/pages/other/index',
success: function(res) {
res.eventChannel.emit('acceptData', { data: 'test' });
}
});
// 接收页面
export default {
onLoad() {
const eventChannel = this.getOpenerEventChannel();
eventChannel.on('acceptData', (data) => {
console.log(data);
});
}
}
// 方式2:使用全局事件总线
// 发送
this.$eventHub.$emit('eventName', data);
// 接收
this.$eventHub.$on('eventName', (data) => {
// 处理数据
});
// 移除监听(页面销毁时)
this.$eventHub.$off('eventName');
实战示例:创建商品列表页
完整代码示例
<!-- pages/goods/list/index.vue -->
<template>
<view class="goods-list" :style="colorStyle">
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input" @tap="goSearch">
<text class="iconfont icon-search"></text>
<text class="placeholder">搜索商品</text>
</view>
</view>
<!-- 筛选栏 -->
<view class="filter-bar">
<view
class="filter-item"
:class="{ active: sortType === 'default' }"
@tap="changeSort('default')">
<text>综合</text>
</view>
<view
class="filter-item"
:class="{ active: sortType === 'sales' }"
@tap="changeSort('sales')">
<text>销量</text>
</view>
<view
class="filter-item"
:class="{ active: sortType === 'price' }"
@tap="changeSort('price')">
<text>价格</text>
<text class="iconfont" :class="priceOrder === 'asc' ? 'icon-up' : 'icon-down'"></text>
</view>
</view>
<!-- 商品列表 -->
<scroll-view
scroll-y
class="goods-scroll"
@scrolltolower="loadMore"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh">
<view class="goods-grid">
<view
class="goods-item"
v-for="item in goodsList"
:key="item.id"
@tap="goDetail(item.id)">
<easyLoadimage
:image-src="item.image"
mode="aspectFill"
class="goods-img">
</easyLoadimage>
<view class="goods-info">
<text class="goods-name">{{ item.store_name }}</text>
<view class="goods-price">
<BaseMoney :money="item.price" symbolSize="24" integerSize="32"></BaseMoney>
<text class="original-price">¥{{ item.ot_price }}</text>
</view>
<text class="goods-sales">已售 {{ item.sales }}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<emptyPage v-if="!goodsList.length && !loading" title="暂无商品"></emptyPage>
<!-- 加载状态 -->
<view class="load-status">
<text v-if="loading">加载中...</text>
<text v-else-if="!hasMore && goodsList.length">没有更多了</text>
</view>
</scroll-view>
</view>
</template>
<script>
import { getProductList } from '@/api/store.js';
import emptyPage from '@/components/emptyPage.vue';
import colors from "@/mixins/color.js";
export default {
name: 'GoodsList',
mixins: [colors],
components: { emptyPage },
data() {
return {
loading: false,
isRefreshing: false,
goodsList: [],
page: 1,
limit: 10,
hasMore: true,
sortType: 'default',
priceOrder: 'asc',
categoryId: ''
};
},
onLoad(options) {
if (options.cid) {
this.categoryId = options.cid;
}
this.getList();
},
methods: {
async getList() {
if (this.loading) return;
this.loading = true;
try {
const params = {
page: this.page,
limit: this.limit,
cid: this.categoryId,
salesOrder: this.sortType === 'sales' ? 'desc' : '',
priceOrder: this.sortType === 'price' ? this.priceOrder : ''
};
const res = await getProductList(params);
const list = res.data.list || [];
if (this.page === 1) {
this.goodsList = list;
} else {
this.goodsList = [...this.goodsList, ...list];
}
this.hasMore = list.length === this.limit;
} catch (e) {
uni.showToast({ title: e.msg || '加载失败', icon: 'none' });
} finally {
this.loading = false;
this.isRefreshing = false;
}
},
onRefresh() {
this.isRefreshing = true;
this.page = 1;
this.hasMore = true;
this.getList();
},
loadMore() {
if (this.hasMore && !this.loading) {
this.page++;
this.getList();
}
},
changeSort(type) {
if (type === 'price' && this.sortType === 'price') {
this.priceOrder = this.priceOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortType = type;
this.priceOrder = 'asc';
}
this.page = 1;
this.hasMore = true;
this.goodsList = [];
this.getList();
},
goSearch() {
uni.navigateTo({ url: '/pages/goods/search/index' });
},
goDetail(id) {
uni.navigateTo({ url: `/pages/goods_details/index?id=${id}` });
}
}
};
</script>
<style lang="scss" scoped>
.goods-list {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.search-bar {
padding: 20rpx;
background: #fff;
.search-input {
height: 72rpx;
background: #f5f5f5;
border-radius: 36rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
.iconfont {
font-size: 32rpx;
color: #999;
margin-right: 12rpx;
}
.placeholder {
color: #999;
font-size: 28rpx;
}
}
}
.filter-bar {
display: flex;
background: #fff;
border-bottom: 1rpx solid #eee;
.filter-item {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #333;
&.active {
color: var(--view-theme);
}
.iconfont {
margin-left: 8rpx;
font-size: 24rpx;
}
}
}
.goods-scroll {
flex: 1;
height: 0;
}
.goods-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
}
.goods-item {
width: calc(50% - 10rpx);
margin-bottom: 20rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
&:nth-child(odd) {
margin-right: 20rpx;
}
.goods-img {
width: 100%;
height: 340rpx;
}
.goods-info {
padding: 16rpx;
.goods-name {
font-size: 26rpx;
color: #333;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.goods-price {
margin-top: 12rpx;
display: flex;
align-items: baseline;
.original-price {
margin-left: 12rpx;
font-size: 22rpx;
color: #999;
text-decoration: line-through;
}
}
.goods-sales {
margin-top: 8rpx;
font-size: 22rpx;
color: #999;
}
}
}
.load-status {
text-align: center;
padding: 30rpx;
color: #999;
font-size: 24rpx;
}
</style>
注意事项
1. 分包限制
- 主包大小限制:2MB
- 分包大小限制:2MB
- 总包大小限制:20MB(微信小程序)
2. 跨平台兼容
- 使用条件编译处理平台差异
- 避免使用平台特有API
- 测试时需要在各平台验证
3. 性能优化
- 合理使用分包加载
- 图片使用懒加载组件
- 长列表使用虚拟滚动
- 避免频繁setData
4. 安全注意
- 敏感信息不要存储在本地
- API请求需要Token验证
- 支付等敏感操作需二次验证
常见问题
Q1: 页面跳转参数过长怎么办?
// 使用编码
const params = encodeURIComponent(JSON.stringify(data));
uni.navigateTo({
url: `/pages/detail/index?data=${params}`
});
// 或使用全局存储
uni.setStorageSync('tempData', data);
uni.navigateTo({
url: '/pages/detail/index'
});
Q2: 如何获取页面高度?
onReady() {
uni.getSystemInfo({
success: (res) => {
this.windowHeight = res.windowHeight;
this.statusBarHeight = res.statusBarHeight;
}
});
}
Q3: 自定义导航栏如何适配?
<template>
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content" :style="{ height: navBarHeight + 'px' }">
<!-- 导航内容 -->
</view>
</view>
</template>
<script>
export default {
data() {
return {
statusBarHeight: 0,
navBarHeight: 44
};
},
onLoad() {
const sysInfo = uni.getSystemInfoSync();
this.statusBarHeight = sysInfo.statusBarHeight;
// #ifdef MP-WEIXIN
const menuButton = uni.getMenuButtonBoundingClientRect();
this.navBarHeight = (menuButton.top - sysInfo.statusBarHeight) * 2 + menuButton.height;
// #endif
}
}
</script>
Q4: 如何处理登录状态?
import { checkLogin, toLogin } from '@/libs/login.js';
// 检查登录状态
if (!checkLogin()) {
toLogin();
return;
}
// 在API中设置noAuth跳过登录验证
export function getPublicData() {
return request.get('public/data', {}, { noAuth: true });
}
Q5: 页面返回如何刷新上一页数据?
// 方式1:使用getCurrentPages
uni.navigateBack({
success: () => {
const pages = getCurrentPages();
const prevPage = pages[pages.length - 1];
if (prevPage && prevPage.refreshData) {
prevPage.refreshData();
}
}
});
// 方式2:使用事件总线
// 返回前触发
this.$eventHub.$emit('refreshList');
// 上一页监听
onShow() {
this.$eventHub.$on('refreshList', this.getList);
}
评论({{cateWiki.comment_num}})
{{commentWhere.order ? '评论从旧到新':'评论从新到旧'}}
{{cateWiki.page_view_num}}人看过该文档
评论(0)
{{commentWhere.order ? '评论从旧到新':'评论从新到旧'}}
13人看过该文档
{{item.user ? item.user.nickname : ''}}
(自评)
{{item.content}}
{{item.create_time}}
删除
搜索结果
为您找到{{wikiCount}}条结果
{{item.page_view_num}}
{{item.like ? item.like.like_num : 0}}
{{item.comment ? item.comment.comment_num : 0}}
位置:
{{path.name}}
{{(i+1) == item.catalogue.path_data.length ? '':'/'}}