插件 Demo · 修改定位
这个 Demo 对应市场里的 定位调试(location-editor)。它演示四件事:
- 逻辑层包装真实
wx.getLocation(同样原理可套到getFuzzyLocation) - 用
storage.getSync / setSync记住接管开关与坐标 - 把“真实坐标”和“小程序实际拿到的坐标”分开测试
- 面板通过
logicLayer.call调用逻辑层函数,并用messaging接收结果
目录结构
text
location-editor/
├── plugin.html # meta + 面板 HTML + 逻辑段
└── (可选) assets/ # 本 Demo 不需要1. 逻辑层:保存原始函数,避免重复包装
js
// <script type="text/wad-logic">
(function () {
// 插件自己的 storage key。逻辑层会把它存进 __wad_<插件id>_override,
// 不会污染小程序业务自己的 storage。
var KEY = 'override';
// 当前接管状态:
// enabled=false 时透传真实定位;true 时返回 latitude/longitude。
var state = { enabled: false, latitude: null, longitude: null };
/**
* 从插件 storage 恢复状态。
*
* 为什么每次调用都 load:
* 面板保存后只改了逻辑层内存;如果页面重载/重注入,需要能从持久化恢复。
*/
function load() {
var v = wadLogicLayer.storage.getSync(KEY);
if (v && typeof v === 'object') {
state.enabled = !!v.enabled;
// Number(null) 会变成 0,这里必须先把 null/undefined 归一成 null。
state.latitude = v.latitude == null ? null : Number(v.latitude);
state.longitude = v.longitude == null ? null : Number(v.longitude);
}
}
/**
* 包装 wx.getLocation。
*
* 关键点:
* 1. 原始函数只保存一次(wx.__wadOrigGetLocation),否则二次注入会把
* 上一次的 wrapper 当成“原始函数”,造成 wrapper 套 wrapper。
* 2. 未接管时原样调用 real.apply(wx, arguments),保留小程序传入的
* type / isHighAccuracy / success / fail / complete 等所有参数。
* 3. 接管时自己构造返回对象,并按 wx API 约定回调 success + complete。
*/
function install() {
if (!wx.__wadOrigGetLocation && typeof wx.getLocation === 'function') {
wx.__wadOrigGetLocation = wx.getLocation;
}
var real = wx.__wadOrigGetLocation;
if (typeof real !== 'function') return;
var wrapped = function (opt) {
opt = opt || {};
// 面板可能刚改过状态;wrapper 执行前重新读一次,避免闭包读到旧值。
load();
// 没开启接管:完全走真实定位,连回调时序都保持 wx 原样。
if (!state.enabled || state.latitude == null) {
return real.apply(wx, arguments);
}
// 开启接管:按 wx.getLocation 的返回字段构造结果。
var r = {
latitude: state.latitude,
longitude: state.longitude,
accuracy: 30,
errMsg: 'getLocation:ok',
type: opt.type || 'wgs84'
};
// 通过 messaging 广播给面板,面板据此显示“小程序拿到的坐标”。
wadLogicLayer.messaging.emit('locationServed', {
latitude: r.latitude,
longitude: r.longitude,
api: 'getLocation'
});
// wx API 的回调必须用 try/catch 包住:业务回调抛错不能反过来影响插件。
try { opt.success && opt.success(r); } catch (e) {}
try { opt.complete && opt.complete(r); } catch (e) {}
};
// 打标记,供 diag / 防止重复包装判断使用。
wrapped.__wadLocOverride = true;
// 优先 defineProperty;个别基础库对 wx 属性冻结,失败时退回直接赋值。
try {
Object.defineProperty(wx, 'getLocation', {
value: wrapped, configurable: true, writable: true
});
} catch (e) {
try { wx.getLocation = wrapped; } catch (e2) {}
}
}
// 脚本执行时先恢复状态并安装一次。
load();
install();
// attach = 每次注入(换页/重启/重新启用)都会触发,必须重新安装。
wadLogicLayer.onAttach(install);
})();2. 逻辑层:注册面板可调用的函数
js
/**
* 读取当前状态。
*
* 返回值必须是 JSON 可序列化对象:面板 logicLayer.call('state') 会把它
* 作为 Promise 的结果拿回去。
*/
wadLogicLayer.register('state', function () {
load(); // 保证返回的是持久化后的最新值
return {
enabled: state.enabled,
latitude: state.latitude,
longitude: state.longitude,
hasValue: state.latitude !== null && state.longitude !== null
};
});
/**
* 保存坐标并开启/关闭接管。
*
* @param {number|string} lat 纬度,范围 -90 ~ 90
* @param {number|string} lng 经度,范围 -180 ~ 180
* @param {boolean} enabled false 表示只保存不接管
* @returns {{ok:boolean, err?:string, state?:object}}
*/
wadLogicLayer.register('save', function (lat, lng, enabled) {
var a = Number(lat);
var b = Number(lng);
// 面板传来的 input.value 是字符串,这里统一转数字并做范围校验。
// isFinite 同时挡住 NaN / Infinity。
if (!isFinite(a) || a < -90 || a > 90) {
return { ok: false, err: '纬度需在 -90 ~ 90 之间' };
}
if (!isFinite(b) || b < -180 || b > 180) {
return { ok: false, err: '经度需在 -180 ~ 180 之间' };
}
state.latitude = a;
state.longitude = b;
state.enabled = enabled !== false;
// 持久化:页面重载、插件重注入后仍然能恢复。
wadLogicLayer.storage.setSync('override', {
enabled: state.enabled,
latitude: a,
longitude: b
});
// 状态变了要重新安装,确保 wrapper 读取的是新闭包状态。
install();
return { ok: true, state: state };
});
/**
* 只切换接管开关,保留上一次保存的坐标。
*
* @param {boolean} on true=接管,false=恢复真实定位
*/
wadLogicLayer.register('setEnabled', function (on) {
state.enabled = !!on;
wadLogicLayer.storage.setSync('override', {
enabled: state.enabled,
latitude: state.latitude,
longitude: state.longitude
});
install();
return { ok: true, state: state };
});3. 逻辑层:同时返回真实坐标与接管坐标
current 用原始函数拿真实坐标;test 再调用小程序实际使用的 wx.getLocation,这样能确认接管是否生效。
js
/**
* 调用一次定位函数,并保证 onResult 只执行一次。
*
* 为什么要 done 标记:
* wx API 可能 success/fail 回调和超时同时发生;只保留第一个结果。
*
* @param {Function} fn 要调用的定位函数(原始函数或 wrapper)
* @param {Function} onResult 接收 {ok, latitude, longitude} 或 {ok:false, err}
*/
function ask(fn, onResult) {
if (typeof fn !== 'function') {
onResult({ ok: false, err: '定位函数不可用' });
return;
}
var done = false;
function finish(r) {
if (done) return;
done = true;
onResult(r);
}
fn.call(wx, {
type: 'wgs84',
success: function (r) {
finish({ ok: true, latitude: r.latitude, longitude: r.longitude });
},
fail: function (e) {
finish({ ok: false, err: String(e && e.errMsg || e) });
}
});
// 真机上未授权/系统定位服务关闭时,wx 可能一直不回调;
// 4.5s 后给出可读错误,避免面板永远“读取中”。
setTimeout(function () {
finish({ ok: false, err: '超时(可能未授权定位)' });
}, 4500);
}
/**
* 读取系统真实坐标。
*
* 注意:这里故意用 __wadOrigGetLocation,绕过我们自己的 wrapper,
* 所以即使接管开启,也能拿到真实定位。
*/
wadLogicLayer.register('current', function () {
ask(wx.__wadOrigGetLocation, function (r) {
wadLogicLayer.messaging.emit('currentLocation', r);
});
return 'requested'; // 立即受理,结果走 messaging
});
/**
* 对比测试:
* - real = 原始 wx.getLocation → 系统真实坐标
* - served = 当前 wx.getLocation → 小程序实际拿到的坐标(可能是接管值)
*
* 两个结果都到齐后再 emit,面板一次显示两行,便于判断接管是否生效。
*/
wadLogicLayer.register('test', function () {
var out = { pending: true, at: Date.now() };
var realDone = false;
var servedDone = false;
function finish() {
if (!realDone || !servedDone) return;
out.pending = false;
wadLogicLayer.messaging.emit('testResult', out);
}
// 真实坐标
ask(wx.__wadOrigGetLocation, function (r) {
if (r.ok) {
out.realLatitude = r.latitude;
out.realLongitude = r.longitude;
} else {
out.realErr = r.err;
}
realDone = true;
finish();
});
// 小程序实际拿到的坐标
ask(wx.getLocation, function (r) {
if (r.ok) {
out.latitude = r.latitude;
out.longitude = r.longitude;
out.ok = true;
} else {
out.servedErr = r.err;
}
servedDone = true;
finish();
});
return 'requested';
});4. 面板:调用并显示结果
html
<button id="current">获取真实坐标</button>
<button id="test">测试一次小程序拿到的定位</button>
<pre id="log"></pre>
<script>
// 简单日志:追加一行并保留换行。
const log = (s) => {
document.getElementById('log').textContent += s + '\n';
};
// 点击“获取真实坐标”:
// logicLayer.call 会跨到逻辑层执行 register('current'),
// 返回的 Promise 只代表“已受理”,真正的坐标通过 messaging 回来。
document.getElementById('current').onclick = async () => {
await wadPanelLayer.logicLayer.call('current');
log('已请求真实坐标…');
};
// 点击“测试”:逻辑层会同时调用原始函数和当前 wx.getLocation,
// 结果通过 testResult 事件返回。
document.getElementById('test').onclick = async () => {
await wadPanelLayer.logicLayer.call('test');
log('已请求 wx.getLocation…');
};
// 真实坐标事件(由 current 触发)
wadPanelLayer.messaging.on('currentLocation', (d) => {
if (d.ok) log(`真实坐标:${d.latitude}, ${d.longitude}`);
else log(`真实坐标失败:${d.err}`);
});
// 对比测试事件(由 test 触发)
wadPanelLayer.messaging.on('testResult', (d) => {
if (d.ok) log(`小程序拿到:${d.latitude}, ${d.longitude}`);
else log(`小程序拿到失败:${d.servedErr}`);
// realLatitude 存在说明原始函数成功返回;否则显示 realErr。
if (d.realLatitude != null) {
log(`真实坐标:${d.realLatitude}, ${d.realLongitude}`);
} else if (d.realErr) {
log(`真实坐标失败:${d.realErr}`);
}
});
</script>验证清单
- [ ] 未开启接管时,
wx.getLocation返回系统真实坐标 - [ ] 保存坐标后,再次调用返回自定义坐标
- [ ] 面板“测试”同时显示真实坐标与小程序实际坐标
- [ ] 关闭接管后恢复真实定位
- [ ] 切换页面/重注入后,不会出现 wrapper 套 wrapper
- [ ] 未授权/定位服务关闭时,4.5s 内能显示可读错误
完整实现见市场插件 定位调试(location-editor,版本 1.0.0)。