Files
xsl 9eec4369da feat(taobao): add native mini program with shared backend
Preserve H5 flows while adding Taobao identity, native capture, reports and isolated local development. Fix canceled province selection and expired-session logout. Record passing local regressions, two pre-existing H5 assertion failures and pending platform validation.
2026-10-19 20:14:31 +08:00

187 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
var api = require('../../utils/api'), image = require('../../utils/image'), voice = require('../../utils/voice');
var permissions = require('../../utils/permissions');
var development = require('../../utils/development');
var STEPS = [
{ target: 'front', tip: '请单人正脸出镜', turn: '正脸居中', mask: 0 },
{ target: 'right', tip: '请右脸出镜', turn: '请向左转头', mask: 2 },
{ target: 'left', tip: '请左脸出镜', turn: '请向右转头', mask: 1 }
];
Page({
data: { stepIndex: 0, stepText: '1/3', stepTip: STEPS[0].tip, guides: ['正脸居中', '取下眼镜', '光线充足'],
maskStep: 0, devicePosition: 'front', camError: false, previewUrl: '', doneTip: '', analyzing: false,
compressWidth: 1, compressHeight: 1, cameraEnabled: false, permissionMessage: '拍摄面部照片用于肌肤检测,请开启相机权限。',
debugMode: false, debugComplete: false, debugPhotos: [] },
onLoad: function (query) {
this.alive = true; this.visible = true; this.photos = []; this.busy = false; this.seq = 0;
this.debugMode = !!(query && query.debug === 'camera' && development.cameraDebugEnabled());
this.setData({ debugMode: this.debugMode });
if (this.debugMode) return;
if (!api.user() || !getApp().globalData.profile) my.redirectTo({ url: '/pages/splash/splash' });
},
onReady: function () {
this.pageReady = true;
if (this.cameraAuthorized) this.mountCamera();
},
onShow: function () {
this.visible = true;
if (!this.data.previewUrl && !this.data.analyzing && !this.data.debugComplete) this.renderStep();
var that = this;
permissions.settings().then(function (current) {
if (!that.alive || !that.visible) return;
if (permissions.state(current, 'scope.camera') === true) { that.cameraAuthorized = true; that.mountCamera(); }
else if (permissions.state(current, 'scope.camera') === false) that.setData({ cameraEnabled: false, permissionMessage: '相机权限未开启,可前往授权设置或使用相册。' });
});
},
mountCamera: function () {
if (!this.alive || !this.visible || !this.pageReady || this.data.cameraEnabled || this.data.debugComplete) return;
var that = this;
this.setData({ cameraEnabled: true, camError: false }, function () {
if (!that.alive || !that.visible) return;
try {
that.cameraCtx = my.createCameraContext('skin-camera');
if (!that.cameraCtx || !that.cameraCtx.takePhoto) that.onCameraError();
} catch (_) { that.onCameraError(); }
});
},
onEnableCamera: function () {
if (this.authorizing) return;
var that = this; this.authorizing = true;
permissions.ensure('scope.camera').then(function () { that.cameraAuthorized = true; that.mountCamera(); }).catch(function (err) {
if (that.alive && that.visible) that.setData({ permissionMessage: err.message });
}).then(function () { that.authorizing = false; });
},
onOpenSettings: function () {
var that = this;
permissions.open().then(function (current) {
if (permissions.state(current, 'scope.camera') === true) { that.cameraAuthorized = true; that.mountCamera(); }
}).catch(function (err) { if (that.alive) my.showToast({ content: err.message, type: 'none' }); });
},
onHide: function () { this.pause(); },
onUnload: function () { this.pause(); this.alive = false; this.photos = []; this.debugResults = []; },
pause: function () {
this.visible = false; this.seq++; this.busy = false;
clearTimeout(this.nextTimer);
if (this.signal) this.signal.canceled = true;
voice.stop(true);
this.cameraCtx = null;
if (this.alive) this.setData({ cameraEnabled: false });
if (this.alive && this.data.analyzing) this.setData({ analyzing: false, doneTip: '' });
},
renderStep: function () {
var cfg = STEPS[this.data.stepIndex];
this.setData({ stepText: (this.data.stepIndex + 1) + '/3', stepTip: cfg.tip,
guides: [cfg.turn, '取下眼镜', '光线充足'], maskStep: cfg.mask, doneTip: '' });
if (!this.debugMode) voice.play(voice.VOICE.step[this.data.stepIndex]);
},
onCameraError: function () {
this.setData({ camError: true, cameraEnabled: false, permissionMessage: '相机暂不可用,请检查淘宝的系统相机权限,或使用系统相机/相册。' });
my.showToast({ content: '相机不可用,请使用系统相机或相册', type: 'none' });
},
onCameraStop: function () { if (this.alive && this.visible) this.onCameraError(); },
onShutter: function () {
if (this.busy || this.data.previewUrl || this.data.analyzing) return;
if (this.data.camError || !this.cameraCtx) return this.choose(['camera']);
var that = this, seq = this.seq;
this.busy = true;
this.cameraCtx.takePhoto({ quality: 'high', success: function (res) {
if (!that.alive || !that.visible || seq !== that.seq) return;
var file = res.tempImagePath || res.tempFilePath || res.apFilePath;
if (file) that.setData({ previewUrl: file });
else my.showToast({ content: '拍摄失败,请重试', type: 'none' });
}, fail: function () {
if (that.alive && that.visible) my.showToast({ content: '拍摄失败,请重试', type: 'none' });
}, complete: function () { if (seq === that.seq) that.busy = false; } });
},
choose: function (sourceType) {
if (this.busy || this.data.previewUrl || this.data.analyzing) return;
var that = this;
this.busy = true;
// chooseImage has its own album scope; native capture also needs camera.
permissions.ensure('scope.album').then(function () {
if (sourceType[0] === 'camera') return permissions.ensure('scope.camera');
}).then(function () {
if (!that.alive || !that.visible) { that.busy = false; return; }
my.chooseImage({ count: 1, sourceType: sourceType,
success: function (res) {
var paths = res.apFilePaths || res.tempFilePaths || [];
if (that.alive && paths[0]) that.setData({ previewUrl: paths[0] });
}, fail: function () { if (that.alive && that.visible) my.showToast({ content: '未选取照片,可重试或检查系统权限', type: 'none' }); },
complete: function () { that.busy = false; } });
}).catch(function (err) {
that.busy = false;
if (that.alive && that.visible) { that.setData({ permissionMessage: err.message }); my.showToast({ content: err.message, type: 'none' }); }
});
},
onSystemCamera: function () { this.choose(['camera']); },
onAlbum: function () { this.choose(['album']); },
onFlip: function () {
if (this.busy || this.data.previewUrl || this.data.analyzing) return;
this.setData({ devicePosition: this.data.devicePosition === 'front' ? 'back' : 'front' });
},
onRetake: function () { if (!this.busy) this.setData({ previewUrl: '' }); },
onConfirm: function () {
if (this.busy || !this.data.previewUrl || this.data.analyzing) return;
var that = this, step = this.data.stepIndex;
this.busy = true; this.photos[step] = this.data.previewUrl;
if (!this.debugMode) voice.play(voice.VOICE.shot, { cut: true });
if (step === 2) { this.setData({ previewUrl: '' }); this.submitPhotos(); return; }
this.setData({ stepIndex: step + 1, previewUrl: '', doneTip: '拍摄完成。下一张:' + STEPS[step + 1].tip + ',' + STEPS[step + 1].turn });
this.nextTimer = setTimeout(function () {
if (!that.alive || !that.visible) return;
that.busy = false; that.renderStep();
}, 2000);
},
submitPhotos: function () {
if (!this.photos[0] || !this.photos[1] || !this.photos[2]) return;
var that = this, seq = ++this.seq, dataUrls = [];
var debugResults = [];
this.signal = { canceled: false }; var signal = this.signal;
this.busy = true; this.setData({ analyzing: true, doneTip: '' });
if (!this.debugMode) voice.play(voice.VOICE.analyzing);
// One shared native canvas: draw the photos sequentially.
this.photos.reduce(function (chain, photo) {
return chain.then(function () {
if (signal.canceled) throw { canceled: true };
return image.compressToBase64(photo, that);
}).then(function (value) {
dataUrls.push(value);
if (that.debugMode) debugResults.push({ key: STEPS[debugResults.length].target,
title: STEPS[debugResults.length].tip.replace(/^请/, ''), photo: photo,
width: that.data.compressWidth, height: that.data.compressHeight,
kilobytes: Math.round(value.replace(/^data:[^,]+,/, '').length * 3 / 4 / 1024) });
});
}, Promise.resolve()).then(function () {
if (signal.canceled) throw { canceled: true };
if (that.debugMode) {
if (!that.alive || !that.visible || seq !== that.seq) return;
that.busy = false; that.cameraCtx = null;
dataUrls = []; // Do not keep base64, upload, create an account or touch report state.
that.setData({ analyzing: false, cameraEnabled: false, debugComplete: true, debugPhotos: debugResults });
return;
}
var ordered = [dataUrls[0], dataUrls[2], dataUrls[1]];
return api.analyze(ordered, getApp().globalData.profile, signal).then(function (payload) {
if (!that.alive || !that.visible || seq !== that.seq) return;
getApp().globalData.photos = ordered; getApp().globalData.payload = payload;
voice.stop(true); my.redirectTo({ url: '/pages/report/report' });
});
}).catch(function (err) {
if (err.canceled || !that.alive || !that.visible || seq !== that.seq) return;
that.busy = false; that.setData({ analyzing: false });
if (!that.debugMode) voice.play(voice.VOICE.fail, { cut: true });
my.showToast({ content: err.message || '照片处理失败,请重新拍摄', type: 'none', duration: 3000 });
});
},
onDebugRestart: function () {
if (!this.debugMode || !development.cameraDebugEnabled()) return;
this.photos = []; this.busy = false; this.seq++;
this.setData({ debugComplete: false, debugPhotos: [], stepIndex: 0, previewUrl: '', analyzing: false });
this.renderStep(); this.onEnableCamera();
},
onCancel: function () {
if (this.debugMode) { this.photos = []; this.setData({ debugPhotos: [], previewUrl: '' }); }
this.pause(); my.navigateBack();
},
noop: function () {}
});