引言:HTML5与微信生态的完美融合
HTML5作为现代Web开发的核心技术标准,已经成为构建跨平台应用的基石。在微信生态中,HTML5技术发挥着至关重要的作用,从公众号H5页面到小程序开发,再到企业微信应用,HTML5无处不在。本文将深入探讨HTML5技术如何助力微信生态开发,并通过实际案例展示其应用价值。
HTML5的核心优势
HTML5带来了许多革命性的特性,使其成为微信生态开发的理想选择:
跨平台兼容性
:一次开发,多端运行
丰富的媒体支持
:原生支持音频、视频、Canvas绘图
离线存储能力
:LocalStorage、IndexedDB等
强大的API支持
:地理位置、摄像头、文件系统等
性能优化
:硬件加速、Web Workers等
一、HTML5在微信公众号开发中的应用
1.1 响应式布局设计
在微信公众号H5页面中,响应式布局是基础要求。HTML5配合CSS3提供了强大的响应式设计能力。
欢迎访问公众号页面
这是一个基于HTML5的响应式页面
1.2 微信JS-SDK集成
微信JS-SDK是公众号开发的核心,HTML5页面可以通过JS-SDK调用微信原生功能。
// 微信JS-SDK配置与使用
class WeChatJS {
constructor() {
this.config = {
appId: ”, // 公众号appId
timestamp: ”, // 时间戳
nonceStr: ”, // 随机字符串
signature: ” // 签名
};
this.readyCallbacks = [];
}
// 初始化配置
async initConfig() {
try {
// 从后端获取签名等信息
const response = await fetch(‘/api/wechat/config’);
const data = await response.json();
this.config = {
…this.config,
…data
};
// 配置微信JS-SDK
wx.config({
debug: false, // 开启调试模式
appId: this.config.appId,
timestamp: this.config.timestamp,
nonceStr: this.config.nonceStr,
signature: this.config.signature,
jsApiList: [
‘onMenuShareTimeline’,
‘onMenuShareAppMessage’,
‘chooseImage’,
‘previewImage’,
‘uploadImage’
]
});
// 配置成功回调
wx.ready(() => {
console.log(‘微信JS-SDK配置成功’);
this.executeReadyCallbacks();
});
// 配置失败回调
wx.error((err) => {
console.error(‘微信JS-SDK配置失败:’, err);
});
} catch (error) {
console.error(‘初始化配置失败:’, error);
}
}
// 添加就绪回调
onReady(callback) {
if (this.readyCallbacks.length === 0) {
this.readyCallbacks.push(callback);
} else {
this.readyCallbacks.push(callback);
}
}
// 执行就绪回调
executeReadyCallbacks() {
this.readyCallbacks.forEach(callback => {
try {
callback();
} catch (error) {
console.error(‘执行ready回调失败:’, error);
}
});
this.readyCallbacks = [];
}
// 分享到朋友圈
shareToTimeline(shareData) {
if (!wx) return;
wx.onMenuShareTimeline({
title: shareData.title,
link: shareData.link,
imgUrl: shareData.imgUrl,
success: () => {
console.log(‘分享到朋友圈成功’);
shareData.onSuccess && shareData.onSuccess();
},
cancel: () => {
console.log(‘取消分享到朋友圈’);
shareData.onCancel && shareData.onCancel();
}
});
}
// 分享给朋友
shareToFriend(shareData) {
if (!wx) return;
wx.onMenuShareAppMessage({
title: shareData.title,
desc: shareData.desc,
link: shareData.link,
imgUrl: shareData.imgUrl,
type: shareData.type || ‘link’,
dataUrl: shareData.dataUrl || ”,
success: () => {
console.log(‘分享给朋友成功’);
shareData.onSuccess && shareData.onSuccess();
},
cancel: () => {
console.log(‘取消分享给朋友’);
shareData.onCancel && shareData.onCancel();
}
});
}
// 选择图片
chooseImage(options = {}) {
return new Promise((resolve, reject) => {
if (!wx) {
reject(new Error(‘微信JS-SDK未加载’));
return;
}
wx.chooseImage({
count: options.count || 9,
sizeType: options.sizeType || [‘original’, ‘compressed’],
sourceType: options.sourceType || [‘album’, ‘camera’],
success: (res) => {
resolve(res.localIds);
},
fail: (err) => {
reject(err);
}
});
});
}
// 上传图片
uploadImage(localId) {
return new Promise((resolve, reject) => {
if (!wx) {
reject(new Error(‘微信JS-SDK未加载’));
return;
}
wx.uploadImage({
localId: localId,
isShowProgressTips: 1,
success: (res) => {
resolve(res.serverId);
},
fail: (err) => {
reject(err);
}
});
});
}
// 预览图片
previewImage(current, urls) {
if (!wx) return;
wx.previewImage({
current: current,
urls: urls
});
}
}
// 使用示例
const wechatJS = new WeChatJS();
// 初始化
wechatJS.initConfig();
// 页面加载完成后设置分享
wechatJS.onReady(() => {
wechatJS.shareToTimeline({
title: ‘HTML5技术助力微信生态开发’,
link: window.location.href,
imgUrl: ‘https://example.com/logo.png’,
onSuccess: () => {
alert(‘分享成功!’);
}
});
wechatJS.shareToFriend({
title: ‘HTML5技术助力微信生态开发’,
desc: ‘探索HTML5在微信生态中的应用’,
link: window.location.href,
imgUrl: ‘https://example.com/logo.png’
});
});
// 图片选择与上传
document.getElementById(‘uploadBtn’).addEventListener(‘click’, async () => {
try {
const localIds = await wechatJS.chooseImage({ count: 1 });
const serverId = await wechatJS.uploadImage(localIds[0]);
console.log(‘图片上传成功,serverId:’, serverId);
// 将serverId发送到后端保存
await fetch(‘/api/save-image’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ serverId })
});
alert(‘图片上传成功!’);
} catch (error) {
console.error(‘图片操作失败:’, error);
alert(‘操作失败,请重试’);
}
});
1.3 微信支付集成
HTML5页面集成微信支付是常见的需求,以下是完整的实现方案:
// 微信H5支付类
class WeChatH5Pay {
constructor() {
this.endpoints = {
unifiedOrder: ‘/api/wechat/pay/unified-order’,
queryOrder: ‘/api/wechat/pay/query’
};
}
/**
* 发起微信H5支付
* @param {Object} orderData – 订单数据
* @param {string} orderData.outTradeNo – 商户订单号
* @param {number} orderData.totalFee – 金额(分)
* @param {string} orderData.body – 商品描述
* @param {string} orderData.openid – 用户openid
*/
async pay(orderData) {
try {
// 1. 统一下单
const orderResponse = await fetch(this.endpoints.unifiedOrder, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(orderData)
});
const orderResult = await orderResponse.json();
if (orderResult.return_code !== ‘SUCCESS’) {
throw new Error(orderResult.return_msg || ‘下单失败’);
}
// 2. 跳转到微信支付
const payUrl = orderResult.mweb_url;
if (payUrl) {
// 记录支付日志
this.logPayment(orderData.outTradeNo, ‘pending’);
// 跳转到支付页面
window.location.href = payUrl;
// 3. 监听支付结果(需要后端配合)
this.startPolling(orderData.outTradeNo);
}
} catch (error) {
console.error(‘支付发起失败:’, error);
this.handlePaymentError(error);
}
}
/**
* 轮询支付结果
* @param {string} outTradeNo – 商户订单号
* @param {number} maxAttempts – 最大轮询次数
*/
startPolling(outTradeNo, maxAttempts = 30) {
let attempts = 0;
const poll = async () => {
attempts++;
if (attempts > maxAttempts) {
this.showPayResult(‘timeout’);
return;
}
try {
const response = await fetch(this.endpoints.queryOrder, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ outTradeNo })
});
const result = await response.json();
if (result.trade_state === ‘SUCCESS’) {
this.showPayResult(‘success’);
this.logPayment(outTradeNo, ‘success’);
return;
}
if (result.trade_state === ‘FAILED’) {
this.showPayResult(‘failed’);
this.logPayment(outTradeNo, ‘failed’);
return;
}
// 继续轮询
setTimeout(poll, 2000);
} catch (error) {
console.error(‘查询支付结果失败:’, error);
setTimeout(poll, 2000);
}
};
// 开始轮询
poll();
}
/**
* 记录支付日志
*/
logPayment(outTradeNo, status) {
const logData = {
outTradeNo,
status,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href
};
// 发送到日志服务
fetch(‘/api/payment/log’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(logData)
}).catch(err => console.error(‘日志记录失败:’, err));
}
/**
* 显示支付结果
*/
showPayResult(status) {
const messages = {
success: ‘支付成功!’,
failed: ‘支付失败,请重试’,
timeout: ‘支付超时,请重新下单’
};
// 创建结果提示层
const overlay = document.createElement(‘div’);
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
color: white;
font-size: 1.2rem;
`;
overlay.innerHTML = `
`;
document.body.appendChild(overlay);
// 自动跳转
if (status === ‘success’) {
setTimeout(() => {
window.location.href = ‘/order/success’;
}, 2000);
}
}
/**
* 处理支付错误
*/
handlePaymentError(error) {
let message = ‘支付失败,请重试’;
if (error.message.includes(‘INSUFFICIENT_FUNDS’)) {
message = ‘余额不足’;
} else if (error.message.includes(‘ORDERPAID’)) {
message = ‘订单已支付’;
} else if (error.message.includes(‘USERPAYING’)) {
message = ‘用户支付中’;
}
alert(message);
}
}
// 使用示例
const payService = new WeChatH5Pay();
// 绑定支付按钮
document.getElementById(‘payBtn’).addEventListener(‘click’, async () => {
const orderData = {
outTradeNo: ‘ORDER’ + Date.now(),
totalFee: 100, // 1元
body: ‘HTML5技术课程’,
openid: ‘用户openid’ // 需要通过授权获取
};
await payService.pay(orderData);
});
二、HTML5在微信小程序中的应用
2.1 小程序原生组件与HTML5的对比
虽然小程序使用WXML/WXSS,但其核心概念与HTML5高度相似,许多HTML5技术可以直接迁移。
// 小程序页面结构(类比HTML5)
Page({
data: {
// 类似HTML5中的状态管理
userInfo: null,
articles: [],
loading: false
},
// 类似HTML5中的生命周期
onLoad: function(options) {
// 页面加载,类似HTML5的DOMContentLoaded
this.loadArticles();
},
onShow: function() {
// 页面显示,类似HTML5的visibilitychange
},
// 事件处理,类似HTML5的addEventListener
handleTap: function(e) {
console.log(‘点击事件:’, e.currentTarget.dataset.id);
},
// 数据请求,类似HTML5的fetch
loadArticles: function() {
this.setData({ loading: true });
wx.request({
url: ‘https://api.example.com/articles’,
success: (res) => {
this.setData({
articles: res.data,
loading: false
});
},
fail: (err) => {
console.error(‘请求失败:’, err);
this.setData({ loading: false });
}
});
},
// 路由跳转,类似HTML5的history.pushState
navigateToDetail: function(e) {
const id = e.currentTarget.dataset.id;
wx.navigateTo({
url: `/pages/detail/detail?id=${id}`
});
},
// 分享功能,类似HTML5的Web Share API
onShareAppMessage: function() {
return {
title: ‘HTML5技术文章’,
path: ‘/pages/index/index’,
imageUrl: ‘/images/share.jpg’
};
}
});
2.2 小程序中使用WebView嵌入HTML5页面
小程序可以通过WebView组件嵌入HTML5页面,实现混合开发。
// 小程序JS文件
Page({
data: {
webViewUrl: ‘https://your-html5-page.com’,
canGoBack: false
},
onLoad: function() {
// 从URL参数获取需要加载的HTML5页面地址
const eventChannel = this.getOpenerEventChannel();
eventChannel.on(‘loadPage’, (data) => {
this.setData({ webViewUrl: data.url });
});
},
// 接收WebView消息
handleWebViewMessage: function(e) {
const detail = e.detail;
console.log(‘收到WebView消息:’, detail);
// 处理来自HTML5页面的消息
if (detail.type === ‘payment’) {
this.initiatePayment(detail.data);
} else if (detail.type === ‘share’) {
this.shareToWeChat(detail.data);
}
},
// WebView加载完成
handleWebViewLoad: function() {
console.log(‘WebView加载完成’);
// 向WebView发送初始化数据
this.postMessageToWebView({
type: ‘init’,
data: {
userInfo: getApp().globalData.userInfo,
systemInfo: wx.getSystemInfoSync()
}
});
},
// WebView加载失败
handleWebViewError: function(e) {
console.error(‘WebView加载失败:’, e);
wx.showToast({
title: ‘页面加载失败’,
icon: ‘none’
});
},
// 向WebView发送消息
postMessageToWebView: function(message) {
const webView = this.selectComponent(‘#webView’);
if (webView) {
webView.postMessage(message);
}
},
// 按钮事件
reloadWebView: function() {
this.setData({ webViewUrl: this.data.webViewUrl + ‘?t=’ + Date.now() });
},
goBack: function() {
this.postMessageToWebView({ type: ‘navigateBack’ });
},
postMessage: function() {
this.postMessageToWebView({
type: ‘custom’,
data: { message: ‘来自小程序的消息’ }
});
},
// 发起支付(在小程序中处理)
initiatePayment: function(orderData) {
wx.requestPayment({
timeStamp: orderData.timeStamp,
nonceStr: orderData.nonceStr,
package: orderData.package,
signType: orderData.signType,
paySign: orderData.paySign,
success: (res) => {
// 通知WebView支付成功
this.postMessageToWebView({
type: ‘paymentResult’,
data: { success: true, res }
});
},
fail: (err) => {
this.postMessageToWebView({
type: ‘paymentResult’,
data: { success: false, err }
});
}
});
},
// 分享功能
shareToWeChat: function(shareData) {
// 小程序分享
wx.showShareMenu({
withShareTicket: true,
menus: [‘shareAppMessage’, ‘shareTimeline’]
});
}
});
2.3 小程序WebView与原生通信的完整方案
小程序WebView通信示例
用户信息: 未获取
支付测试
分享测试
三、企业微信中的HTML5应用
3.1 企业微信JSAPI集成
企业微信提供了丰富的JSAPI,HTML5页面可以调用这些API实现企业级功能。
// 企业微信JSAPI封装
class WeComJSAPI {
constructor() {
this.config = {
beta: true, // 必须这么写
debug: false,
appId: ”, // 企业微信的corpID
timestamp: ”,
nonceStr: ”,
signature: ”,
jsApiList: [
‘onMenuShareAppMessage’,
‘onMenuShareWechat’,
‘chooseImage’,
‘previewImage’,
‘uploadImage’,
‘downloadImage’,
‘getLocation’,
‘openLocation’,
‘scanQRCode’,
‘openEnterpriseChat’,
‘selectExternalContact’
]
};
}
// 初始化配置
async initConfig() {
try {
const response = await fetch(‘/api/wecom/config’);
const data = await response.json();
this.config = { …this.config, …data };
wx.config(this.config);
wx.ready(() => {
console.log(‘企业微信JSAPI配置成功’);
this.onReady();
});
wx.error((err) => {
console.error(‘配置失败:’, err);
});
} catch (error) {
console.error(‘初始化失败:’, error);
}
}
// 选择图片并上传
async chooseAndUploadImage() {
return new Promise((resolve, reject) => {
wx.chooseImage({
count: 1,
sizeType: [‘compressed’],
sourceType: [‘album’, ‘camera’],
success: async (chooseRes) => {
try {
const uploadRes = await this.uploadImage(chooseRes.localIds[0]);
resolve(uploadRes.serverId);
} catch (error) {
reject(error);
}
},
fail: (err) => {
reject(err);
}
});
});
}
// 上传图片
uploadImage(localId) {
return new Promise((resolve, reject) => {
wx.uploadImage({
localId: localId,
isShowProgressTips: 1,
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
}
});
});
}
// 获取地理位置
getLocation() {
return new Promise((resolve, reject) => {
wx.getLocation({
type: ‘wgs84′, // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入’gcj02’
success: (res) => {
const latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
const longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
const speed = res.speed; // 速度,以米/每秒计
const accuracy = res.accuracy; // 位置精度
resolve({ latitude, longitude, speed, accuracy });
},
fail: (err) => {
reject(err);
}
});
});
}
// 打开位置
openLocation(data) {
wx.openLocation({
latitude: data.latitude, // 纬度,浮点数,范围为90 ~ -90
longitude: data.longitude, // 经度,浮点数,范围为180 ~ -180
name: data.name, // 位置名
address: data.address, // 地址详情说明
scale: data.scale || 15, // 地图缩放级别,整数范围,1-28,默认为16
infoUrl: data.infoUrl || ” // 在查看位置界面底部显示的超链接,可点击跳转
});
}
// 扫码
scanQRCode() {
return new Promise((resolve, reject) => {
wx.scanQRCode({
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果
scanType: [‘qrCode’, ‘barCode’], // 可以指定扫二维码还是条形码
success: (res) => {
const result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
resolve(result);
},
fail: (err) => {
reject(err);
}
});
});
}
// 打开企业会话
openEnterpriseChat(data) {
wx.openEnterpriseChat({
userIds: data.userIds, // 参与会话的成员列表,格式为userid1;userid2;…
groupName: data.groupName, // 会话名称
chatId: data.chatId, // 会话id,优先级高于userIds
success: () => {
console.log(‘打开企业会话成功’);
},
fail: (err) => {
console.error(‘打开企业会话失败:’, err);
}
});
}
// 选择外部联系人
selectExternalContact() {
return new Promise((resolve, reject) => {
wx.selectExternalContact({
success: (res) => {
// 返回选择的外部联系人userId列表
const userIds = res.userIds;
resolve(userIds);
},
fail: (err) => {
reject(err);
}
});
});
}
// 分享应用消息
shareAppMessage(shareData) {
wx.onMenuShareAppMessage({
title: shareData.title,
desc: shareData.desc,
link: shareData.link,
imgUrl: shareData.imgUrl,
success: () => {
console.log(‘分享成功’);
shareData.onSuccess && shareData.onSuccess();
},
cancel: () => {
console.log(‘取消分享’);
shareData.onCancel && shareData.onCancel();
}
});
}
// 分享到微信
shareToWechat(shareData) {
wx.onMenuShareWechat({
title: shareData.title,
desc: shareData.desc,
link: shareData.link,
imgUrl: shareData.imgUrl,
success: () => {
console.log(‘分享到微信成功’);
shareData.onSuccess && shareData.onSuccess();
},
cancel: () => {
console.log(‘取消分享到微信’);
shareData.onCancel && shareData.onCancel();
}
});
}
// 获取免登录Ticket
getLoginTicket() {
return new Promise((resolve, reject) => {
wx.invoke(‘getLoginTicket’, {}, (res) => {
if (res.err_msg === ‘getLoginTicket:ok’) {
resolve(res.ticket);
} else {
reject(new Error(res.err_msg));
}
});
});
}
// 监听事件
onReady() {
// 页面加载完成后可以执行的操作
console.log(‘企业微信JSAPI就绪’);
}
}
// 使用示例
const wecom = new WeComJSAPI();
// 初始化
wecom.initConfig();
// 绑定扫码按钮
document.getElementById(‘scanBtn’).addEventListener(‘click’, async () => {
try {
const result = await wecom.scanQRCode();
console.log(‘扫码结果:’, result);
alert(‘扫码成功: ‘ + result);
} catch (error) {
console.error(‘扫码失败:’, error);
alert(‘扫码失败: ‘ + error.message);
}
});
// 绑定位置按钮
document.getElementById(‘locationBtn’).addEventListener(‘click’, async () => {
try {
const location = await wecom.getLocation();
console.log(‘当前位置:’, location);
// 显示在地图上
wecom.openLocation({
latitude: location.latitude,
longitude: location.longitude,
name: ‘我的位置’,
address: `纬度:${location.latitude}, 经度:${location.longitude}`
});
} catch (error) {
console.error(‘获取位置失败:’, error);
alert(‘获取位置失败: ‘ + error.message);
}
});
// 绑定图片上传按钮
document.getElementById(‘imageBtn’).addEventListener(‘click’, async () => {
try {
const serverId = await wecom.chooseAndUploadImage();
console.log(‘图片上传成功,serverId:’, serverId);
// 保存到后端
await fetch(‘/api/save-image’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ serverId })
});
alert(‘图片上传成功!’);
} catch (error) {
console.error(‘图片操作失败:’, error);
alert(‘操作失败: ‘ + error.message);
}
});
3.2 企业微信审批流程集成
// 企业微信审批流程
class WeComApproval {
constructor() {
this.endpoints = {
startApproval: ‘/api/wecom/approval/start’,
getApprovalResult: ‘/api/wecom/approval/result’
};
}
/**
* 启动审批流程
* @param {Object} approvalData – 审批数据
*/
async startApproval(approvalData) {
try {
// 1. 构建审批模板数据
const templateData = {
creator_userid: approvalData.userId,
use_template_id: approvalData.templateId,
template_content: {
// 审批表单数据
controls: approvalData.controls || []
},
// 审批人配置
approver: approvalData.approver,
// 抄送人
cc: approvalData.cc || [],
// 审批信息
summary: approvalData.summary,
detail: approvalData.detail
};
// 2. 调用企业微信API启动审批
const response = await fetch(this.endpoints.startApproval, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify(templateData)
});
const result = await response.json();
if (result.errcode === 0) {
console.log(‘审批启动成功:’, result);
return result.sp_no; // 返回审批单号
} else {
throw new Error(result.errmsg);
}
} catch (error) {
console.error(‘启动审批失败:’, error);
throw error;
}
}
/**
* 查询审批结果
* @param {string} spNo – 审批单号
*/
async getApprovalResult(spNo) {
try {
const response = await fetch(this.endpoints.getApprovalResult, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ sp_no: spNo })
});
const result = await response.json();
if (result.errcode === 0) {
return result.info; // 返回审批详情
} else {
throw new Error(result.errmsg);
}
} catch (error) {
console.error(‘查询审批结果失败:’, error);
throw error;
}
}
/**
* 监听审批状态变化
* @param {string} spNo – 审批单号
* @param {Function} callback – 状态变化回调
*/
monitorApprovalStatus(spNo, callback) {
const poll = async () => {
try {
const info = await this.getApprovalResult(spNo);
// 审批状态:0-审批中,1-已通过,2-已驳回,3-已撤销
const status = info.sp_status;
callback(status, info);
if (status === 0) {
// 审批中,继续轮询
setTimeout(poll, 5000);
}
} catch (error) {
console.error(‘监控审批状态失败:’, error);
setTimeout(poll, 5000);
}
};
poll();
}
/**
* 构建审批表单
* @param {Object} formData – 表单数据
*/
buildApprovalForm(formData) {
const controls = [];
// 文本控件
if (formData.text) {
controls.push({
id: ‘text’,
type: ‘text’,
title: formData.text.title,
value: formData.text.value
});
}
// 数字控件
if (formData.number) {
controls.push({
id: ‘number’,
type: ‘number’,
title: formData.number.title,
value: formData.number.value
});
}
// 日期控件
if (formData.date) {
controls.push({
id: ‘date’,
type: ‘date’,
title: formData.date.title,
value: formData.date.value
});
}
// 选择控件
if (formData.selector) {
controls.push({
id: ‘selector’,
type: ‘selector’,
title: formData.selector.title,
value: formData.selector.value,
options: formData.selector.options
});
}
// 附件控件
if (formData.attachment) {
controls.push({
id: ‘attachment’,
type: ‘attachment’,
title: formData.attachment.title,
value: formData.attachment.value // media_id列表
});
}
return controls;
}
}
// 使用示例
const approval = new WeComApproval();
// 提交审批
document.getElementById(‘submitApproval’).addEventListener(‘click’, async () => {
const formData = {
// 表单数据
text: { title: ‘申请事由’, value: ‘购买办公用品’ },
number: { title: ‘申请金额’, value: ‘500’ },
date: { title: ‘申请日期’, value: ‘2024-01-15’ },
selector: {
title: ‘费用类型’,
value: ‘1’,
options: [
{ key: ‘1’, value: ‘办公用品’ },
{ key: ‘2’, value: ‘交通费’ },
{ key: ‘3’, value: ‘餐饮费’ }
]
},
attachment: {
title: ‘相关凭证’,
value: [‘media_id_1’, ‘media_id_2’] // 之前上传的图片media_id
}
};
const approvalData = {
userId: ‘用户ID’,
templateId: ‘审批模板ID’,
controls: approval.buildApprovalForm(formData),
approver: [
{ userid: ‘上级用户ID’, seq: 1 } // seq表示审批顺序
],
cc: [‘抄送人ID’],
summary: ‘办公用品采购申请’,
detail: ‘申请购买打印机墨盒、A4纸等办公用品,预计费用500元’
};
try {
const spNo = await approval.startApproval(approvalData);
alert(‘审批已提交,单号: ‘ + spNo);
// 监控审批结果
approval.monitorApprovalStatus(spNo, (status, info) => {
console.log(‘审批状态更新:’, status, info);
if (status === 1) {
alert(‘审批已通过!’);
} else if (status === 2) {
alert(‘审批被驳回’);
} else if (status === 3) {
alert(‘审批已撤销’);
}
});
} catch (error) {
alert(‘提交审批失败: ‘ + error.message);
}
});
四、HTML5性能优化与最佳实践
4.1 资源加载优化
// 资源加载优化器
class ResourceOptimizer {
constructor() {
this.imageCache = new Map();
this.jsCache = new Map();
}
/**
* 图片懒加载
* @param {NodeList} images – 图片元素列表
* @param {Object} options – 配置选项
*/
lazyLoadImages(images, options = {}) {
const {
rootMargin = ’50px’,
threshold = 0.01,
placeholder = ‘data:image/svg+xml,%3Csvg xmlns=”http://www.w3.org/2000/svg” viewBox=”0 0 1 1″%3E%3C/svg%3E’
} = options;
// 设置占位图
images.forEach(img => {
if (!img.src) {
img.src = placeholder;
}
});
// 使用 IntersectionObserver 实现懒加载
if (‘IntersectionObserver’ in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
observer.unobserve(img);
}
});
}, { rootMargin, threshold });
images.forEach(img => imageObserver.observe(img));
} else {
// 降级处理
this.loadAllImages(images);
}
}
/**
* 加载单张图片
*/
loadImage(img) {
const src = img.dataset.src;
if (!src) return;
// 检查缓存
if (this.imageCache.has(src)) {
img.src = this.imageCache.get(src);
return;
}
// 加载图片
const tempImg = new Image();
tempImg.onload = () => {
img.src = src;
this.imageCache.set(src, src);
img.classList.add(‘loaded’);
};
tempImg.onerror = () => {
console.error(‘图片加载失败:’, src);
img.classList.add(‘error’);
};
tempImg.src = src;
}
/**
* 加载所有图片(降级)
*/
loadAllImages(images) {
images.forEach(img => this.loadImage(img));
}
/**
* 预加载关键资源
*/
preloadCriticalResources(resources) {
resources.forEach(resource => {
const link = document.createElement(‘link’);
link.rel = ‘preload’;
link.as = resource.type;
link.href = resource.url;
if (resource.crossOrigin) {
link.crossOrigin = resource.crossOrigin;
}
document.head.appendChild(link);
});
}
/**
* 代码分割与动态导入
*/
async loadModule(modulePath) {
try {
const module = await import(/* webpackChunkName: “dynamic” */ modulePath);
return module;
} catch (error) {
console.error(‘模块加载失败:’, error);
throw error;
}
}
/**
* Web Workers 优化计算密集型任务
*/
createWorker(workerScript) {
const blob = new Blob([workerScript], { type: ‘application/javascript’ });
const worker = new Worker(URL.createObjectURL(blob));
return {
postMessage: (data) => worker.postMessage(data),
onMessage: (callback) => {
worker.onmessage = (e) => callback(e.data);
},
terminate: () => worker.terminate()
};
}
/**
* 缓存策略
*/
async cacheFirst(request) {
const cache = await caches.open(‘v1’);
const cachedResponse = await cache.match(request);
if (cachedResponse) {
return cachedResponse;
}
const networkResponse = await fetch(request);
if (networkResponse.ok) {
cache.put(request, networkResponse.clone());
}
return networkResponse;
}
/**
* 服务工作者注册
*/
async registerServiceWorker() {
if (‘serviceWorker’ in navigator) {
try {
const registration = await navigator.serviceWorker.register(‘/sw.js’);
console.log(‘ServiceWorker 注册成功:’, registration);
// 监听更新
registration.addEventListener(‘updatefound’, () => {
const newWorker = registration.installing;
newWorker.addEventListener(‘statechange’, () => {
if (newWorker.state === ‘installed’ && navigator.serviceWorker.controller) {
// 有新版本可用
this.showUpdateNotification();
}
});
});
return registration;
} catch (error) {
console.error(‘ServiceWorker 注册失败:’, error);
}
}
}
/**
* 显示更新通知
*/
showUpdateNotification() {
const notification = document.createElement(‘div’);
notification.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
background: #07C160;
color: white;
padding: 1rem;
text-align: center;
z-index: 9999;
cursor: pointer;
`;
notification.textContent = ‘发现新版本,点击刷新’;
notification.addEventListener(‘click’, () => {
window.location.reload();
});
document.body.appendChild(notification);
}
}
// 使用示例
const optimizer = new ResourceOptimizer();
// 图片懒加载
window.addEventListener(‘DOMContentLoaded’, () => {
const images = document.querySelectorAll(‘img[data-src]’);
optimizer.lazyLoadImages(images);
});
// 预加载关键资源
optimizer.preloadCriticalResources([
{ url: ‘/styles/main.css’, type: ‘style’ },
{ url: ‘/scripts/main.js’, type: ‘script’ },
{ url: ‘/images/logo.png’, type: ‘image’ }
]);
// 注册Service Worker
optimizer.registerServiceWorker();
// 动态加载模块
document.getElementById(‘loadFeature’).addEventListener(‘click’, async () => {
const module = await optimizer.loadModule(‘./feature.js’);
module.init();
});
4.2 微信环境下的性能优化
// 微信环境优化器
class WeChatPerformanceOptimizer {
constructor() {
this.weixinJSReady = false;
this.performanceMetrics = {};
}
/**
* 监控页面性能
*/
monitorPerformance() {
if (!window.performance) return;
// 页面加载时间
window.addEventListener(‘load’, () => {
setTimeout(() => {
const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd – perfData.navigationStart;
const domReadyTime = perfData.domContentLoadedEventEnd – perfData.navigationStart;
console.log(`页面加载时间: ${pageLoadTime}ms`);
console.log(`DOM就绪时间: ${domReadyTime}ms`);
// 上报到监控平台
this.reportPerformance({
type: ‘page_load’,
loadTime: pageLoadTime,
domReadyTime: domReadyTime,
url: window.location.href
});
}, 0);
});
// 资源加载时间
window.addEventListener(‘error’, (e) => {
if (e.target.tagName === ‘IMG’ || e.target.tagName === ‘SCRIPT’) {
console.error(‘资源加载失败:’, e.target.src);
this.reportPerformance({
type: ‘resource_error’,
url: e.target.src,
tagName: e.target.tagName
});
}
}, true);
}
/**
* 微信JS-SDK加载优化
*/
async loadWeChatJSSDK() {
return new Promise((resolve, reject) => {
if (window.wx) {
resolve(window.wx);
return;
}
// 使用CDN加速
const script = document.createElement(‘script’);
script.src = ‘https://res.wx.qq.com/open/js/jweixin-1.6.0.js’;
script.async = true;
script.onload = () => {
this.weixinJSReady = true;
resolve(window.wx);
};
script.onerror = () => {
// 降级:使用备用CDN
console.warn(‘主CDN加载失败,尝试备用CDN’);
const backupScript = document.createElement(‘script’);
backupScript.src = ‘https://res2.wx.qq.com/open/js/jweixin-1.6.0.js’;
backupScript.async = true;
backupScript.onload = () => {
this.weixinJSReady = true;
resolve(window.wx);
};
backupScript.onerror = () => {
reject(new Error(‘微信JS-SDK加载失败’));
};
document.head.appendChild(backupScript);
};
document.head.appendChild(script);
});
}
/**
* 图片压缩与优化
*/
async compressImage(file, options = {}) {
const {
maxWidth = 1280,
maxHeight = 1280,
quality = 0.8,
mimeType = ‘image/jpeg’
} = options;
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement(‘canvas’);
const ctx = canvas.getContext(‘2d’);
// 计算尺寸
let width = img.width;
let height = img.height;
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width *= ratio;
height *= ratio;
}
canvas.width = width;
canvas.height = height;
// 绘制
ctx.drawImage(img, 0, 0, width, height);
// 导出
canvas.toBlob((blob) => {
resolve(blob);
}, mimeType, quality);
};
img.onerror = reject;
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
/**
* 内存优化
*/
optimizeMemory() {
// 清理大对象
window.addEventListener(‘beforeunload’, () => {
// 清理缓存
if (window.applicationCache) {
window.applicationCache.cancel();
}
// 清理定时器
const timers = window.setTimeout(() => {}, 0);
for (let i = 0; i < timers; i++) {
window.clearTimeout(i);
window.clearInterval(i);
}
});
// 监听内存使用
if (performance.memory) {
setInterval(() => {
const memory = performance.memory;
const usedMB = memory.usedJSHeapSize / 1048576;
const totalMB = memory.totalJSHeapSize / 1048576;
if (usedMB > 100) { // 超过100MB
console.warn(‘内存使用过高:’, usedMB.toFixed(2) + ‘MB’);
this.reportPerformance({
type: ‘memory_warning’,
usedMB: usedMB,
totalMB: totalMB
});
}
}, 30000);
}
}
/**
* 上报性能数据
*/
async reportPerformance(metrics) {
try {
await fetch(‘/api/performance/report’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({
…metrics,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href
})
});
} catch (error) {
console.error(‘性能数据上报失败:’, error);
}
}
/**
* 微信环境下特殊优化
*/
weChatSpecificOptimizations() {
// 1. 防止微信下拉刷新导致的页面错位
document.body.addEventListener(‘touchmove’, (e) => {
if (e.target === document.body) {
e.preventDefault();
}
}, { passive: false });
// 2. 优化微信内置浏览器的滚动
const container = document.createElement(‘div’);
container.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
`;
// 将body内容移动到容器
while (document.body.firstChild) {
container.appendChild(document.body.firstChild);
}
document.body.appendChild(container);
// 3. 优化点击延迟(300ms问题)
if (‘ontouchstart’ in window) {
document.addEventListener(‘touchstart’, () => {}, { passive: true });
}
// 4. 监听微信网络状态
if (window.WeixinJSBridge) {
document.addEventListener(‘WeixinJSBridgeReady’, () => {
WeixinJSBridge.invoke(‘getNetworkType’, {}, (res) => {
const networkType = res.err_msg.split(‘:’)[1]; // network_type:wifi
console.log(‘当前网络:’, networkType);
if (networkType === ‘network_type:2g’ || networkType === ‘network_type:3g’) {
// 弱网环境下启用降级策略
this.enableWeakNetworkMode();
}
});
});
}
}
/**
* 弱网模式
*/
enableWeakNetworkMode() {
// 1. 禁用非必要动画
document.body.classList.add(‘weak-network’);
// 2. 降低图片质量
const images = document.querySelectorAll(‘img’);
images.forEach(img => {
if (img.dataset.src) {
img.dataset.src = img.dataset.src.replace(‘.jpg’, ‘_low.jpg’);
}
});
// 3. 延迟加载非关键资源
const lazyImages = document.querySelectorAll(‘img[data-src]’);
lazyImages.forEach(img => {
img.dataset.src = ”;
});
// 4. 显示网络提示
const tip = document.createElement(‘div’);
tip.textContent = ‘当前网络较慢,已启用省流模式’;
tip.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
z-index: 9999;
`;
document.body.appendChild(tip);
setTimeout(() => tip.remove(), 3000);
}
}
// 使用示例
const wechatOptimizer = new WeChatPerformanceOptimizer();
// 页面初始化
window.addEventListener(‘DOMContentLoaded’, () => {
// 监控性能
wechatOptimizer.monitorPerformance();
// 微信特定优化
wechatOptimizer.weChatSpecificOptimizations();
// 内存优化
wechatOptimizer.optimizeMemory();
// 加载微信JS-SDK
wechatOptimizer.loadWeChatJSSDK().then(wx => {
console.log(‘微信JS-SDK加载成功’);
}).catch(err => {
console.error(‘微信JS-SDK加载失败:’, err);
});
});
// 图片上传前压缩
document.getElementById(‘fileInput’).addEventListener(‘change’, async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const compressedBlob = await wechatOptimizer.compressImage(file, {
maxWidth: 800,
quality: 0.7
});
console.log(`压缩前: ${(file.size / 1024 / 1024).toFixed(2)}MB`);
console.log(`压缩后: ${(compressedBlob.size / 1024 / 1024).toFixed(2)}MB`);
// 上传压缩后的图片
const formData = new FormData();
formData.append(‘image’, compressedBlob, ‘compressed.jpg’);
await fetch(‘/api/upload’, {
method: ‘POST’,
body: formData
});
alert(‘图片上传成功!’);
} catch (error) {
console.error(‘图片处理失败:’, error);
alert(‘图片处理失败’);
}
});
五、安全与隐私保护
5.1 微信环境安全最佳实践
// 微信安全处理器
class WeChatSecurityHandler {
constructor() {
this.sensitiveFields = [‘openid’, ‘unionid’, ‘mobile’, ‘idcard’];
this.csrfTokens = new Map();
}
/**
* 数据脱敏处理
*/
desensitizeData(data) {
if (typeof data === ‘string’) {
// 手机号脱敏
if (/^1[3-9]\d{9}$/.test(data)) {
return data.replace(/^(\d{3})\d{4}(\d{4})$/, ‘$1****$2’);
}
// 身份证脱敏
if (/^\d{17}[\dXx]$/.test(data)) {
return data.replace(/^(\d{6})\d{8}(\d{4})$/, ‘$1********$2’);
}
// 邮箱脱敏
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data)) {
const [name, domain] = data.split(‘@’);
return `${name[0]}***${name[name.length-1]}@${domain}`;
}
} else if (typeof data === ‘object’ && data !== null) {
const result = Array.isArray(data) ? [] : {};
for (const key in data) {
if (this.sensitiveFields.includes(key.toLowerCase())) {
result[key] = this.desensitizeData(data[key]);
} else {
result[key] = this.desensitizeData(data[key]);
}
}
return result;
}
return data;
}
/**
* 生成CSRF Token
*/
generateCSRFToken() {
const token = Math.random().toString(36).substr(2) + Date.now().toString(36);
const sessionId = this.getSessionId();
this.csrfTokens.set(sessionId, token);
return token;
}
/**
* 验证CSRF Token
*/
validateCSRFToken(token) {
const sessionId = this.getSessionId();
const storedToken = this.csrfTokens.get(sessionId);
return token === storedToken;
}
/**
* 获取会话ID
*/
getSessionId() {
let sessionId = localStorage.getItem(‘wechat_session_id’);
if (!sessionId) {
sessionId = ‘session_’ + Math.random().toString(36).substr(2);
localStorage.setItem(‘wechat_session_id’, sessionId);
}
return sessionId;
}
/**
* 安全请求封装
*/
async secureRequest(url, options = {}) {
const {
method = ‘GET’,
headers = {},
body,
requireAuth = true,
…rest
} = options;
// 添加CSRF Token
const csrfToken = this.generateCSRFToken();
const secureHeaders = {
‘X-CSRF-Token’: csrfToken,
‘X-Request-ID’: Date.now() + ‘_’ + Math.random().toString(36).substr(2),
…headers
};
// 添加认证信息
if (requireAuth) {
const token = localStorage.getItem(‘wechat_access_token’);
if (token) {
secureHeaders[‘Authorization’] = `Bearer ${token}`;
}
}
// 处理敏感数据
let processedBody = body;
if (body && typeof body === ‘object’) {
processedBody = JSON.stringify(this.desensitizeData(body));
secureHeaders[‘Content-Type’] = ‘application/json’;
}
try {
const response = await fetch(url, {
method,
headers: secureHeaders,
body: processedBody,
…rest
});
// 验证响应
if (response.status === 401) {
// 认证过期
this.handleAuthExpiry();
throw new Error(‘认证过期’);
}
if (response.status === 403) {
// CSRF验证失败
console.error(‘CSRF验证失败’);
throw new Error(‘安全验证失败’);
}
const data = await response.json();
// 验证响应签名(如果后端提供)
if (data.signature && !this.validateResponseSignature(data)) {
console.error(‘响应签名验证失败’);
throw new Error(‘响应数据被篡改’);
}
return data;
} catch (error) {
console.error(‘安全请求失败:’, error);
throw error;
}
}
/**
* 验证响应签名
*/
validateResponseSignature(data) {
// 简化的签名验证示例
// 实际应使用更复杂的算法
const { signature, …payload } = data;
const expectedSignature = this.calculateSignature(payload);
return signature === expectedSignature;
}
/**
* 计算签名
*/
calculateSignature(data) {
const sortedKeys = Object.keys(data).sort();
const signString = sortedKeys.map(key => `${key}=${data[key]}`).join(‘&’);
// 实际应使用更安全的哈希算法
return btoa(signString).substr(0, 16);
}
/**
* 处理认证过期
*/
handleAuthExpiry() {
localStorage.removeItem(‘wechat_access_token’);
localStorage.removeItem(‘wechat_refresh_token’);
// 显示登录提示
const overlay = document.createElement(‘div’);
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
`;
overlay.innerHTML = `
登录已过期
请重新登录
`;
document.body.appendChild(overlay);
}
/**
* 隐私数据清理
*/
cleanupPrivacyData() {
// 清理敏感数据
const sensitiveKeys = [
‘access_token’, ‘refresh_token’, ‘openid’, ‘unionid’,
‘user_info’, ‘mobile’, ‘idcard’
];
sensitiveKeys.forEach(key => {
localStorage.removeItem(key);
sessionStorage.removeItem(key);
});
// 清理Cookie
const cookies = document.cookie.split(‘;’);
cookies.forEach(cookie => {
const eqPos = cookie.indexOf(‘=’);
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
if (sensitiveKeys.includes(name)) {
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
}
});
// 清理内存中的敏感数据
if (window.wechatUserData) {
delete window.wechatUserData;
}
}
/**
* 安全退出
*/
async secureLogout() {
try {
// 调用后端退出接口
await this.secureRequest(‘/api/auth/logout’, {
method: ‘POST’,
requireAuth: true
});
} catch (error) {
console.error(‘退出请求失败:’, error);
} finally {
// 清理本地数据
this.cleanupPrivacyData();
// 跳转到登录页
window.location.href = ‘/login’;
}
}
/**
* 防止XSS攻击
*/
sanitizeHTML(str) {
const div = document.createElement(‘div’);
div.textContent = str;
return div.innerHTML;
}
/**
* 安全的DOM操作
*/
safeSetHTML(element, html) {
// 使用textContent而不是innerHTML防止XSS
if (this.isSafeHTML(html)) {
element.innerHTML = html;
} else {
element.textContent = html;
}
}
/**
* 检查HTML是否安全
*/
isSafeHTML(html) {
const dangerousPatterns = [
/