原创作者:移动端架构师 | 发布日期:2024年1月18日
一、UniApp架构设计与多端适配原理
1.1 跨端运行机制深度解析
// UniApp跨端架构核心原理
class UniAppArchitecture {
constructor() {
this.platforms = {
'mp-weixin': '微信小程序',
'mp-alipay': '支付宝小程序',
'h5': 'Web端',
'app': '移动App'
};
this.compileStrategies = new Map();
}
initCompileStrategies() {
// 不同平台的编译策略
this.compileStrategies.set('mp-weixin', {
template: 'wxml',
style: 'wxss',
script: 'js',
adapter: '微信小程序适配器'
});
this.compileStrategies.set('h5', {
template: 'html',
style: 'css',
script: 'js',
adapter: 'Web端适配器'
});
}
getPlatformConfig(platform) {
return {
condition: process.env.UNI_PLATFORM === platform,
polyfills: this.getPlatformPolyfills(platform),
apiAdapter: this.getApiAdapter(platform)
};
}
}
1.2 条件编译实战技巧
// 平台特定代码条件编译
export default {
data() {
return {
// 多端适配数据
platformConfig: this.getPlatformConfig()
}
},
methods: {
getPlatformConfig() {
// #ifdef MP-WEIXIN
return {
loginType: 'wxLogin',
payMethod: 'wxPay',
shareAPI: 'wxShare'
};
// #endif
// #ifdef H5
return {
loginType: 'h5Login',
payMethod: 'aliPay',
shareAPI: 'webShare'
};
// #endif
// #ifdef APP
return {
loginType: 'uniLogin',
payMethod: 'iapPay',
shareAPI: 'nativeShare'
};
// #endif
},
// 统一API调用封装
async unifiedLogin() {
// #ifdef MP-WEIXIN
return await this.wxLogin();
// #endif
// #ifdef H5
return await this.h5Login();
// #endif
// #ifdef APP
return await this.appLogin();
// #endif
},
wxLogin() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: resolve,
fail: reject
});
});
}
}
}
二、企业级项目工程化配置
2.1 Vuex状态管理架构
// store/index.js - 核心状态管理
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user';
import cart from './modules/cart';
import product from './modules/product';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
user,
cart,
product
},
// 全局状态
state: {
platform: '',
networkType: 'wifi',
theme: 'light'
},
// 全局getters
getters: {
isWeixinPlatform: state => state.platform === 'mp-weixin',
isOnline: state => state.networkType !== 'none'
},
// 全局mutations
mutations: {
SET_PLATFORM(state, platform) {
state.platform = platform;
},
SET_NETWORK(state, networkType) {
state.networkType = networkType;
}
},
// 全局actions
actions: {
async initApp({ commit, dispatch }) {
// 获取平台信息
const platform = process.env.UNI_PLATFORM;
commit('SET_PLATFORM', platform);
// 初始化网络监听
dispatch('startNetworkListener');
// 初始化用户信息
await dispatch('user/checkLoginStatus');
// 初始化购物车
await dispatch('cart/loadCartData');
},
startNetworkListener({ commit }) {
uni.onNetworkStatusChange((res) => {
commit('SET_NETWORK', res.networkType);
});
}
}
});
export default store;
2.2 路由拦截与权限控制
// router/interceptor.js - 路由拦截器
class RouterInterceptor {
constructor() {
this.whiteList = ['/pages/login/login', '/pages/index/index'];
this.authPages = ['/pages/order/order', '/pages/user/user'];
}
// 路由前置守卫
async beforeRouteEnter(to, from, next) {
// 检查页面是否需要登录
if (this.isAuthPage(to.path) && !this.checkLoginStatus()) {
// 跳转到登录页
uni.redirectTo({
url: '/pages/login/login?redirect=' + encodeURIComponent(to.path)
});
return;
}
// 检查页面权限
if (!this.checkPagePermission(to.path)) {
uni.showToast({
title: '无权限访问',
icon: 'none'
});
return;
}
next();
}
// 路由后置钩子
afterRouteLeave(to, from) {
// 页面离开统计
this.trackPageLeave(from.path);
}
isAuthPage(path) {
return this.authPages.some(authPath => path.includes(authPath));
}
checkLoginStatus() {
const token = uni.getStorageSync('token');
const userInfo = uni.getStorageSync('userInfo');
return !!(token && userInfo);
}
checkPagePermission(path) {
const userRole = this.$store.getters['user/role'];
const pageRoles = this.getPageRoles(path);
return pageRoles.includes(userRole);
}
}
// 注册全局路由拦截
const interceptor = new RouterInterceptor();
uni.addInterceptor('navigateTo', interceptor.beforeRouteEnter);
uni.addInterceptor('redirectTo', interceptor.beforeRouteEnter);
三、电商核心模块开发实战
3.1 商品列表虚拟滚动优化
// components/virtual-list.vue - 虚拟滚动组件
export default {
name: 'VirtualList',
props: {
listData: Array,
itemHeight: {
type: Number,
default: 100
},
bufferSize: {
type: Number,
default: 5
}
},
data() {
return {
scrollTop: 0,
visibleCount: 0,
startIndex: 0,
endIndex: 0
};
},
computed: {
// 计算可见区域数据
visibleData() {
const start = Math.max(0, this.startIndex - this.bufferSize);
const end = Math.min(this.listData.length, this.endIndex + this.bufferSize);
return this.listData.slice(start, end);
},
// 列表总高度
listHeight() {
return this.listData.length * this.itemHeight;
},
// 偏移量
offsetY() {
return this.startIndex * this.itemHeight;
}
},
mounted() {
this.calcVisibleCount();
this.bindScrollEvent();
},
methods: {
calcVisibleCount() {
// 计算可见区域能显示多少项
const query = uni.createSelectorQuery().in(this);
query.select('.virtual-list-container').boundingClientRect(data => {
this.visibleCount = Math.ceil(data.height / this.itemHeight) + this.bufferSize * 2;
this.updateVisibleRange();
}).exec();
},
bindScrollEvent() {
// 监听滚动事件
this.$el.addEventListener('scroll', this.handleScroll, { passive: true });
},
handleScroll(event) {
this.scrollTop = event.target.scrollTop;
this.updateVisibleRange();
},
updateVisibleRange() {
this.startIndex = Math.floor(this.scrollTop / this.itemHeight);
this.endIndex = this.startIndex + this.visibleCount;
// 触发可视区域变化事件
this.$emit('visible-change', {
startIndex: this.startIndex,
endIndex: this.endIndex
});
},
// 渲染单项数据
renderItem(item, index) {
const actualIndex = this.startIndex + index;
return `
<div class="virtual-item" :style="{ transform: 'translateY(' + (actualIndex * this.itemHeight) + 'px)' }">
<product-item :product="item" :index="actualIndex"></product-item>
</div>
`;
}
}
};
3.2 购物车数据同步策略
// store/modules/cart.js - 购物车状态管理
const cart = {
namespaced: true,
state: {
items: [],
selectedIds: [],
lastSyncTime: 0
},
getters: {
totalCount: state => state.items.reduce((total, item) => total + item.quantity, 0),
totalPrice: state => state.items.reduce((total, item) => {
if (state.selectedIds.includes(item.id)) {
return total + (item.price * item.quantity);
}
return total;
}, 0),
selectedItems: state => state.items.filter(item => state.selectedIds.includes(item.id))
},
mutations: {
ADD_TO_CART(state, product) {
const existingItem = state.items.find(item => item.id === product.id);
if (existingItem) {
existingItem.quantity += product.quantity || 1;
} else {
state.items.push({
...product,
quantity: product.quantity || 1,
addedTime: Date.now()
});
}
// 本地存储
this.dispatch('saveToStorage');
},
UPDATE_QUANTITY(state, { id, quantity }) {
const item = state.items.find(item => item.id === id);
if (item) {
item.quantity = quantity;
this.dispatch('saveToStorage');
}
},
SYNC_FROM_SERVER(state, serverItems) {
// 与服务端数据合并策略
state.items = this.mergeCartItems(state.items, serverItems);
state.lastSyncTime = Date.now();
}
},
actions: {
// 添加商品到购物车
async addToCart({ commit, dispatch }, product) {
try {
commit('ADD_TO_CART', product);
// 同步到服务端
await dispatch('syncToServer');
uni.showToast({
title: '添加成功',
icon: 'success'
});
} catch (error) {
uni.showToast({
title: '添加失败',
icon: 'none'
});
}
},
// 与服务端同步
async syncToServer({ state, commit }) {
// #ifdef H5 || MP-WEIXIN
const result = await uni.request({
url: '/api/cart/sync',
method: 'POST',
data: {
items: state.items,
lastSyncTime: state.lastSyncTime
}
});
if (result.data.success) {
commit('SYNC_FROM_SERVER', result.data.items);
}
// #endif
},
// 本地存储
saveToStorage({ state }) {
try {
uni.setStorageSync('cart_data', {
items: state.items,
updateTime: Date.now()
});
} catch (error) {
console.error('购物车存储失败:', error);
}
}
}
};
export default cart;
四、多端性能优化策略
4.1 图片懒加载与压缩方案
// utils/image-optimizer.js - 图片优化工具
class ImageOptimizer {
constructor() {
this.qualityMap = {
'avatar': 0.6, // 头像质量
'product': 0.8, // 商品图片质量
'banner': 0.9 // 横幅图片质量
};
}
// 获取优化后的图片URL
getOptimizedImageUrl(originalUrl, type = 'product', width = 375) {
if (!originalUrl) return '';
// #ifdef H5
return this.optimizeForH5(originalUrl, type, width);
// #endif
// #ifdef MP-WEIXIN
return this.optimizeForWeapp(originalUrl, type, width);
// #endif
// #ifdef APP
return this.optimizeForApp(originalUrl, type, width);
// #endif
return originalUrl;
}
optimizeForH5(url, type, width) {
// Web端使用WebP格式和响应式图片
if (url.includes('?')) {
return `${url}&width=${width}&format=webp&quality=${this.qualityMap[type]}`;
}
return `${url}?width=${width}&format=webp&quality=${this.qualityMap[type]}`;
}
optimizeForWeapp(url, type, width) {
// 微信小程序使用云开发图片处理
return `https://cdn.domain.com/${width}x0/${url}`;
}
// 图片懒加载指令
installLazyLoadDirective(Vue) {
Vue.directive('lazy', {
inserted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = new Image();
img.src = binding.value;
img.onload = () => {
el.src = binding.value;
observer.unobserve(el);
};
}
});
});
observer.observe(el);
}
});
}
}
// 在main.js中注册
import ImageOptimizer from '@/utils/image-optimizer';
Vue.prototype.$image = new ImageOptimizer();
4.2 请求缓存与防抖优化
// utils/request-optimizer.js - 请求优化器
class RequestOptimizer {
constructor() {
this.cache = new Map();
this.pendingRequests = new Map();
this.cacheTime = 5 * 60 * 1000; // 5分钟缓存
}
// 带缓存的请求
async cachedRequest(options) {
const cacheKey = this.generateCacheKey(options);
const cached = this.cache.get(cacheKey);
// 检查缓存是否有效
if (cached && Date.now() - cached.timestamp {
// 缓存成功响应
this.cache.set(cacheKey, {
data: response,
timestamp: Date.now()
});
// 清除pending状态
this.pendingRequests.delete(cacheKey);
return response;
}).catch(error => {
this.pendingRequests.delete(cacheKey);
throw error;
});
this.pendingRequests.set(cacheKey, requestPromise);
return requestPromise;
}
// 防抖请求
debouncedRequest(func, wait = 300) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
return new Promise((resolve) => {
timeoutId = setTimeout(async () => {
const result = await func.apply(this, args);
resolve(result);
}, wait);
});
};
}
// 批量请求合并
createBatchRequest() {
let batch = [];
let timer;
return function(request, key) {
return new Promise((resolve, reject) => {
batch.push({ request, key, resolve, reject });
clearTimeout(timer);
timer = setTimeout(() => {
this.executeBatch(batch);
batch = [];
}, 50);
});
};
}
generateCacheKey(options) {
return `${options.method}_${options.url}_${JSON.stringify(options.data)}`;
}
async makeRequest(options) {
return new Promise((resolve, reject) => {
uni.request({
...options,
success: resolve,
fail: reject
});
});
}
}
// 使用示例
const optimizer = new RequestOptimizer();
export const searchProducts = optimizer.debouncedRequest(async (keyword) => {
return optimizer.cachedRequest({
url: '/api/products/search',
data: { keyword }
});
});
五、部署上线与监控体系
5.1 多端发布自动化流程
// package.json - 自动化构建脚本
{
"scripts": {
"build:weapp": "uni-build --platform mp-weixin --mode production",
"build:h5": "uni-build --platform h5 --mode production",
"build:app": "uni-build --platform app --mode production",
"deploy:weapp": "npm run build:weapp && node scripts/upload-weapp.js",
"deploy:h5": "npm run build:h5 && node scripts/deploy-h5.js",
"deploy:all": "npm run deploy:weapp && npm run deploy:h5",
"preview:weapp": "npm run build:weapp && uni-open-dev-tool"
}
}
// scripts/upload-weapp.js - 微信小程序自动上传
const ci = require('miniprogram-ci');
const packageInfo = require('../package.json');
(async () => {
const project = new ci.Project({
appid: 'wx1234567890abcdef',
type: 'miniProgram',
projectPath: './dist/build/mp-weixin',
privateKeyPath: './private.key',
ignores: ['node_modules/**/*']
});
const uploadResult = await ci.upload({
project,
version: packageInfo.version,
desc: `v${packageInfo.version} - ${new Date().toLocaleDateString()}`,
setting: {
es6: true,
es7: true,
minify: true,
autoPrefixWXSS: true
}
});
console.log('上传成功:', uploadResult);
})().catch(error => {
console.error('上传失败:', error);
process.exit(1);
});
5.2 性能监控与错误追踪
// utils/monitor.js - 应用监控系统
class AppMonitor {
constructor() {
this.performanceData = [];
this.errorLogs = [];
this.isMonitoring = false;
}
startMonitoring() {
this.isMonitoring = true;
this.setupPerformanceMonitor();
this.setupErrorHandler();
this.setupPageTracker();
}
setupPerformanceMonitor() {
// 页面加载性能监控
uni.onAppShow((res) => {
this.trackAppShow(res);
});
uni.onAppHide((res) => {
this.trackAppHide(res);
});
// 自定义性能指标
this.measurePageLoadTime();
}
setupErrorHandler() {
// 全局错误捕获
uni.onError((error) => {
this.trackError('UncaughtError', error);
});
// Promise错误捕获
uni.onUnhandledRejection((error) => {
this.trackError('UnhandledRejection', error);
});
// Vue错误捕获
Vue.config.errorHandler = (error, vm, info) => {
this.trackError('VueError', { error, component: vm?.$options.name, info });
};
}
setupPageTracker() {
// 页面访问统计
const originalPage = Page;
Page = function(options) {
const originalOnLoad = options.onLoad;
const originalOnUnload = options.onUnload;
options.onLoad = function(...args) {
this.$pageStartTime = Date.now();
this.trackPageView(this.route);
if (originalOnLoad) {
originalOnLoad.apply(this, args);
}
};
options.onUnload = function(...args) {
const duration = Date.now() - this.$pageStartTime;
this.trackPageLeave(this.route, duration);
if (originalOnUnload) {
originalOnUnload.apply(this, args);
}
};
return originalPage(options);
};
}
trackError(type, error) {
const errorInfo = {
type,
error: error.toString(),
stack: error.stack,
timestamp: Date.now(),
platform: process.env.UNI_PLATFORM,
version: plus.runtime.version
};
this.errorLogs.push(errorInfo);
this.reportError(errorInfo);
}
reportError(errorInfo) {
// 上报错误到监控平台
uni.request({
url: 'https://monitor.domain.com/api/error',
method: 'POST',
data: errorInfo,
fail: () => {
// 失败时存储到本地
this.storeLocally('errors', errorInfo);
}
});
}
// 性能数据上报
reportPerformance() {
const performanceReport = {
platform: process.env.UNI_PLATFORM,
data: this.performanceData,
timestamp: Date.now()
};
uni.request({
url: 'https://monitor.domain.com/api/performance',
method: 'POST',
data: performanceReport
});
}
}
// 在App.vue中初始化
const monitor = new AppMonitor();
monitor.startMonitoring();