var api = require('../../utils/api'), image = require('../../utils/image'), voice = require('../../utils/voice'); var permissions = require('../../utils/permissions'); var development = require('../../utils/development'); var config = require('../../config'); 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: [], showDiagnose: config.environment !== 'production', diagText: '', cameraSupported: false }, onLoad: function (query) { this.alive = true; this.visible = true; this.photos = []; this.busy = false; this.seq = 0; var supported = config.nativeCamera === true && (typeof permissions.cameraSupported === 'function' ? permissions.cameraSupported() : true); this.cameraSupported = supported; this.debugMode = !!(query && query.debug === 'camera' && development.cameraDebugEnabled()); this.setData({ debugMode: this.debugMode, cameraSupported: supported }); if (this.debugMode) return; var app = typeof getApp === 'function' ? getApp() : null; if (!api.user() || !app || !app.globalData.profile) { my.redirectTo({ url: '/pages/splash/splash' }); return; } if (!supported) { // 淘宝 组件不可用:直接用系统相机/相册,页面不再挂相机组件(否则整页崩溃) this.setData({ cameraEnabled: false, camError: true, permissionMessage: '请用下方按钮拍摄,三张依次为正脸、右脸、左脸。' }); } }, 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(); if (!this.cameraSupported) return; // 平台没有内置相机:直接用系统相机/相册 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: '相机权限未开启,可前往授权设置或使用相册。' }); }).catch(function () {}); }, mountCamera: function () { if (!this.alive || !this.visible || !this.pageReady || this.data.cameraEnabled || this.data.debugComplete) return; if (!this.cameraSupported) 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' }); }); }, onCloseDiagnose: function () { this.setData({ diagText: '' }); }, // 开发版:把真机上的相机能力/授权返回值直接打在页面上,供截图定位 onDiagnose: function () { var that = this; this.setData({ diagText: '检测中…' }); permissions.diagnose().then(function (info) { if (that.alive) that.setData({ diagText: info.join('\n') }); }).catch(function (err) { if (that.alive) that.setData({ diagText: 'diagnose error: ' + (err && err.message ? err.message : String(err)) }); }); }, 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 || seq !== that.seq) return; // 真机上内置相机 takePhoto 失败(机型/组件差异、权限被拒等): // 不打断流程,直接退回系统相机走 my.chooseImage 那条已验证过的路。 that.busy = false; my.showToast({ content: '内置相机不可用,改用系统相机', type: 'none' }); that.choose(['camera']); }, 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 () {} });