Common Pitfalls and Solutions When Developing WeChat Mini Programs with UniApp (Vue 3 + TypeScript)
This guide summarizes the typical problems encountered while building a WeChat mini‑program with uniapp + Vue 3 + TypeScript—such as nickname input validation, custom navigation bars, custom tabbars, iOS safe‑area handling, list scrolling quirks, and privacy‑policy configuration—and provides concrete code‑level solutions and best‑practice recommendations.
The author recently built a WeChat mini‑program using the uniapp stack (uniapp + Vue 3 + TypeScript) and collected a series of common pitfalls, linking to official documentation and reliable articles for each issue.
1. Nickname Input Issue
Since 2022‑10‑25 the wx.getUserProfile and wx.getUserInfo APIs were removed, requiring developers to use the new nickname capability. The input field performs asynchronous compliance checks, so the value is not immediately available on blur. The solution is to await the check or listen to the bindnicknamereview event.
<uv-input v-model="form.name" type="nickname" placeholder="请输入内容" @blur="handleSubmit"></uv-input> async function handleSubmit() {
// wait for async nickname validation
await new Promise(resolve => setTimeout(resolve, 0));
console.log('form.value.name', form.value.name);
if (form.value.name === rawName) return;
// ...
}If the nickname is rejected, the input is cleared automatically; therefore the bindnicknamereview callback must be used to retry or restore the original value.
<uv-input v-model="form.name" type="nickname" placeholder="请输入内容" @nicknamereview="handleSubmit"></uv-input> function onNickNameReview(e) {
console.log('onNickNameReview', e);
if (e.detail.pass) {
handleSubmit();
} else {
form.value.name = rawName;
}
}Because the uv‑ui library does not expose this event, the author patched the component source in node_modules/uv-input and submitted a PR.
2. Custom Navigation Bar
The native navigation bar cannot be customized (e.g., font size). The workaround is to set navigationStyle: "custom" in pages.json , calculate the status‑bar and capsule heights, and build a custom view.
// pages.json
{ navigationStyle: "custom" } const statusBarHeight = ref(0);
const navBarHeight = ref(0);
statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight;
let menuButtonInfo = uni.getMenuButtonBoundingClientRect();
navBarHeight.value = menuButtonInfo.height + (menuButtonInfo.top - statusBarHeight.value) * 2; <view class="nav-bar">
<!-- status‑bar placeholder -->
<view :style="{ height: statusBarHeight + 'px' }"></view>
<!-- actual navigation content -->
<view class="nav-bar-content" style="font-size: 34rpx;" :style="{ height: navBarHeight + 'px' }">导航栏标题</view>
</view>Note: the native bar still follows system settings (dark mode, font size) which cannot be overridden.
3. Custom TabBar
To overcome the limitations of the native tab bar, enable custom: true in pages.json and create a custom-tab-bar component under src .
// pages.json
tabBar: { custom: true, /* ... */ } <!-- src/custom-tab-bar/index.wxml -->
<view class="tab-bar">
<view class="tab-bar-border"></view>
<view wx:for="{{list}}" wx:key="index" class="tab-bar-item" data-path="{{item.pagePath}}" data-index="{{index}}" bindtap="switchTab">
<image class="tab-bar-item-img" src="{{selected === index ? item.selectedIconPath : item.iconPath}}"></image>
<view class="tab-bar-item-text" :style="{ color: selected === index ? selectedColor : color }">{{item.text}}</view>
</view>
</view> // src/custom-tab-bar/index.js
Component({
data: {
selected: 0,
color: "#8d939f",
selectedColor: "#e3eaf9",
list: [
{ pagePath: "/pages/index/index", iconPath: "../static/tabbar/home01.png", selectedIconPath: "../static/tabbar/home02.png", text: "首页" },
{ pagePath: "/pages/my/my", iconPath: "../static/tabbar/user01.png", selectedIconPath: "../static/tabbar/user02.png", text: "我的" }
]
},
methods: {
switchTab(e) {
const { path, index } = e.currentTarget.dataset;
wx.switchTab({ url: path });
this.setData({ selected: index });
}
}
});Each page must set the active index of the custom tab bar, e.g. in onShow :
onShow(() => {
const currentPage = getCurrentPages()[0];
const currentTabBar = currentPage?.getTabBar?.();
currentTabBar?.setData({ selected: 0 });
});4. iOS Safe‑Area Adaptation
Three approaches are described:
Configure safearea in manifest.json (background color only, not flexible for images).
Read system info via uni.getSystemInfoSync() to obtain statusBarHeight and bottom values.
Use CSS env() / constant() functions with viewport-fit=cover meta tag for dynamic padding.
// manifest.json
"app-plus": {
"safearea": {
"background": "#FFFFFF",
"bottom": { "offset": "auto" }
}
} let app = uni.getSystemInfoSync();
console.log('statusBarHeight', app.statusBarHeight);
console.log('bottom safe area', app.bottom); padding-bottom: constant(safe-area-inset-bottom); /* iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* iOS ≥ 11.2 */
/* with extra offset */
padding-bottom: calc(constant(safe-area-inset-bottom) + 20rpx);5. List Scrolling Issues
Using overflow:auto on a list causes the whole page to pull down on the first swipe. Wrapping the list in a scroll-view solves the problem. For pull‑to‑refresh, enable the enhanced attribute and programmatically scroll to top.
<scroll-view scroll-y class="max-h-[800rpx] overflow-auto"></scroll-view> <scroll-view id="scrollview" :enhanced="true" scroll-y class="max-h-[800rpx] overflow-auto"></scroll-view> function scrollToTop(id) {
wx.createSelectorQuery().select(id).node().exec(res => {
const scrollView = res[0].node;
scrollView.scrollTo({ top: 0, animated: true });
});
}
onPullDownRefresh(async () => {
try { await fetchList(); } finally { uni.stopPullDownRefresh(); scrollToTop('#scrollview'); }
});6. Mini‑Program Privacy‑Protection Guide
When a mini‑program accesses any user data, a privacy agreement must be configured in the WeChat public‑platform settings. The author also sets __usePrivacyCheck__: true in the manifest, adds a custom privacy‑popup component, and handles location permission flow.
// manifest.config.ts
'mp-weixin': { __usePrivacyCheck__: true } <WsWxPrivacy id="privacy-popup" @agree="onAgree" @disagree="onDisAgree" /> function handleCheckLocation() {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'gcj02',
success: async res => {
try {
let r = await checkLocation({ lon: res.longitude.toString(), lat: res.latitude.toString() });
resolve('success');
} catch (e) { reject(e); }
},
fail: err => { console.log('获取位置失败:', err); reject(err); }
});
});
}The author also provides a reusable getSetting helper that checks the current authorization state, prompts the user with wx.showModal , and opens the settings page if needed.
function getSetting(scopeName, cb) {
uni.getSetting({
success: res => {
if (res.authSetting[scopeName]) {
cb();
} else if (res.authSetting[scopeName] === false) {
wx.showModal({
title: '您未开启相关授权',
content: '请在设置中开启授权',
success: r => { if (r.confirm) wx.openSetting({ success: s => { if (s.authSetting[scopeName]) cb(); } }); }
});
} else {
uni.authorize({ scope: scopeName, success: cb });
}
}
});
}Following these steps ensures the mini‑program complies with privacy regulations and provides a smooth user experience.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.