From b5bfe2cac7c8abaa78684dd98668f4f6f847be31 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Fri, 26 Sep 2025 18:11:30 +0800 Subject: [PATCH 01/23] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx new file mode 100644 index 0000000000..f58afd9802 --- /dev/null +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -0,0 +1,42 @@ +import React, { forwardRef, useRef } from 'react' +import { Camera, useCameraDevice } from 'react-native-vision-camera' +import useNodesRef, { HandlerRef } from './useNodesRef' + +interface CameraProps { + mode?: 'normal' | 'scanCode' + resolution?: 'low' | 'medium' | 'high' + devicePosition?: 'front' | 'back' + flash?: 'auto' | 'on' | 'off' + frameSize?: 'small' | 'medium' | 'large' + style?: Record + bindstop?: () => void + binderror?: (error: { message: string }) => void + bindinitdone?: (result: { type: string; data: string }) => void + bindscancode?: (result: { type: string; data: string }) => void +} +const _camera = forwardRef, CameraProps>((props: CameraProps): JSX.Element | null => { + const cameraRef = useRef(null) + const { + mode = 'normal', + resolution = 'medium', + devicePosition = 'back', + flash = 'auto', + frameSize = 'medium' + } = props + + const device = useCameraDevice(devicePosition || 'back') + return ( + + ) +}) + +_camera.displayName = 'MpxCamera' + +export default _camera \ No newline at end of file From 9732d33ed6298292b8428a767eb64881d8b0d43e Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 9 Oct 2025 16:49:25 +0800 Subject: [PATCH 02/23] =?UTF-8?q?=E5=BC=80=E5=8F=91camera,=20api-proxy?= =?UTF-8?q?=E9=83=A8=E5=88=86=E8=BF=98=E5=B7=AE=E4=B8=80=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api-proxy/src/platform/index.js | 6 + .../platform/patch/getDefaultOptions.ios.js | 8 +- .../template/wx/component-config/camera.js | 12 ++ .../wx/component-config/unsupported.js | 2 +- .../runtime/components/react/mpx-camera.tsx | 141 +++++++++++++++++- .../runtime/components/react/mpx-web-view.tsx | 2 +- .../lib/utils/dom-tag-config.js | 2 +- packages/webpack-plugin/package.json | 1 + 8 files changed, 161 insertions(+), 13 deletions(-) diff --git a/packages/api-proxy/src/platform/index.js b/packages/api-proxy/src/platform/index.js index aba7057503..ba1ebd2c14 100644 --- a/packages/api-proxy/src/platform/index.js +++ b/packages/api-proxy/src/platform/index.js @@ -37,6 +37,9 @@ export * from './api/create-selector-query' // getNetworkType, onNetworkStatusChange, offNetworkStatusChange export * from './api/device/network' +// startWifi, stopWifi, getWifiList, onGetWifiList, offGetWifiList, getConnectedWifi +export * from './api/device/wifi' + // downloadFile, uploadFile export * from './api/file' @@ -119,3 +122,6 @@ export * from './api/keyboard' // getSetting, openSetting, enableAlertBeforeUnload, disableAlertBeforeUnload, getMenuButtonBoundingClientRect export * from './api/setting' + +// createCameraContext +export * from './api/camera' diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 7b2e2d7946..a7d6dc52fb 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -3,7 +3,7 @@ import * as ReactNative from 'react-native' import { ReactiveEffect } from '../../observer/effect' import { watch } from '../../observer/watch' import { del, reactive, set } from '../../observer/reactive' -import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, error, setFocusedNavigation } from '@mpxjs/utils' +import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, error, warn, setFocusedNavigation } from '@mpxjs/utils' import MpxProxy from '../../core/proxy' import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../core/innerLifecycle' import mergeOptions from '../../core/mergeOptions' @@ -593,7 +593,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const instanceRef = useRef(null) const propsRef = useRef(null) const intersectionCtx = useContext(IntersectionObserverContext) - const { pageId } = useContext(RouteContext) || {} + const { pageId, navigation } = useContext(RouteContext) || {} const parentProvides = useContext(ProviderContext) let relation = null if (hasDescendantRelation || hasAncestorRelation) { @@ -652,6 +652,10 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { usePageEffect(proxy, pageId) useEffect(() => { proxy.mounted() + if (navigation.camera?.multi) { + navigation.camera.multi = false + warn(': 一个页面只能插入一个') + } return () => { proxy.unmounted() proxy.target.__resetInstance() diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/camera.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/camera.js index a55e469023..159b2311a1 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/camera.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/camera.js @@ -15,6 +15,18 @@ module.exports = function ({ print }) { const qaEventLog = print({ platform: 'qa', tag: TAG_NAME, isError: false, type: 'event' }) return { test: TAG_NAME, + ios (tag, { el }) { + el.isBuiltIn = true + return 'mpx-camera' + }, + android (tag, { el }) { + el.isBuiltIn = true + return 'mpx-camera' + }, + harmony (tag, { el }) { + el.isBuiltIn = true + return 'mpx-camera' + }, props: [ { test: 'mode', diff --git a/packages/webpack-plugin/lib/platform/template/wx/component-config/unsupported.js b/packages/webpack-plugin/lib/platform/template/wx/component-config/unsupported.js index 5d16584b73..7c643badfa 100644 --- a/packages/webpack-plugin/lib/platform/template/wx/component-config/unsupported.js +++ b/packages/webpack-plugin/lib/platform/template/wx/component-config/unsupported.js @@ -11,7 +11,7 @@ const JD_UNSUPPORTED_TAG_NAME_ARR = ['functional-page-navigator', 'live-pusher', // 快应用不支持的标签集合 const QA_UNSUPPORTED_TAG_NAME_ARR = ['movable-view', 'movable-area', 'open-data', 'official-account', 'editor', 'functional-page-navigator', 'live-player', 'live-pusher', 'ad', 'cover-image'] // RN不支持的标签集合 -const RN_UNSUPPORTED_TAG_NAME_ARR = ['open-data', 'official-account', 'editor', 'functional-page-navigator', 'live-player', 'live-pusher', 'ad', 'audio', 'camera', 'match-media', 'page-container', 'editor', 'keyboard-accessory', 'map'] +const RN_UNSUPPORTED_TAG_NAME_ARR = ['open-data', 'official-account', 'editor', 'functional-page-navigator', 'live-player', 'live-pusher', 'ad', 'audio', 'match-media', 'page-container', 'editor', 'keyboard-accessory', 'map'] /** * @param {function(object): function} print diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index f58afd9802..ba5fdfacaa 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,6 +1,7 @@ -import React, { forwardRef, useRef } from 'react' -import { Camera, useCameraDevice } from 'react-native-vision-camera' -import useNodesRef, { HandlerRef } from './useNodesRef' +import React, { forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' +import { Camera, useCameraDevice, useCodeScanner, useCameraFormat, useFrameProcessor } from 'react-native-vision-camera' +import { getCustomEvent } from './getInnerListeners' +import { RouteContext } from './context' interface CameraProps { mode?: 'normal' | 'scanCode' @@ -11,27 +12,151 @@ interface CameraProps { style?: Record bindstop?: () => void binderror?: (error: { message: string }) => void - bindinitdone?: (result: { type: string; data: string }) => void - bindscancode?: (result: { type: string; data: string }) => void + bindinitdone?: (result: { type: string, data: string }) => void + bindscancode?: (result: { type: string, data: string }) => void } -const _camera = forwardRef, CameraProps>((props: CameraProps): JSX.Element | null => { + +interface CameraRef { + setZoom: (zoom: number) => void + getTakePhoto: () => (() => Promise) | undefined + getStartRecord: () => ((options: any) => void) | undefined + getStopRecord: () => (() => void) | undefined +} + +type HandlerRef = { + // 根据实际的 HandlerRef 类型定义调整 + current: T | null +} + +const _camera = forwardRef, CameraProps>((props: CameraProps, ref): JSX.Element | null => { const cameraRef = useRef(null) const { mode = 'normal', resolution = 'medium', devicePosition = 'back', flash = 'auto', - frameSize = 'medium' + frameSize = 'medium', + bindinitdone, + bindstop, + bindscancode } = props + const isPhoto = mode === 'normal' const device = useCameraDevice(devicePosition || 'back') + const { navigation } = useContext(RouteContext) || {} + const [zoomValue, setZoomValue] = useState(1) + const [hasPermission, setHasPermission] = useState(null) + + // 先定义常量,避免在条件判断后使用 + const maxZoom = device?.maxZoom || 1 + const RESOLUTION_MAPPING: Record = { + low: { width: 640, height: 480 }, + medium: { width: 1280, height: 720 }, + high: { width: 1920, height: 1080 } + } + const FRAME_SIZE_MAPPING: Record = { + small: { width: 480, height: 360 }, + medium: { width: 720, height: 540 }, + large: { width: 1080, height: 810 } + } + + // 所有 Hooks 必须在条件判断之前调用 + const format = useCameraFormat(device, [ + { + photoResolution: RESOLUTION_MAPPING[resolution], + videoResolution: FRAME_SIZE_MAPPING[frameSize] || RESOLUTION_MAPPING[resolution] + } + ]) + + const codeScanner = useCodeScanner({ + codeTypes: ['qr', 'ean-13'], + onCodeScanned: (codes) => { + const result = codes.map(code => code.value).join(',') + bindscancode && bindscancode(getCustomEvent('scancode', {}, { + detail: { + result: codes.map(code => code.value).join(',') + } + })) + } + }) + + const onInitialized = useCallback(() => { + bindinitdone && bindinitdone(getCustomEvent('initdone', {}, { + detail: { + maxZoom + } + })) + }, [bindinitdone, maxZoom]) + + const onStopped = useCallback(() => { + bindstop && bindstop() + }, [bindstop]) + + // 检查相机权限 + useEffect(() => { + const checkCameraPermission = async () => { + try { + const cameraPermission = global?.__mpx?.config?.rnConfig?.cameraPermission + if (typeof cameraPermission === 'function') { + const permissionResult = await cameraPermission() + setHasPermission(permissionResult === true) + } else { + setHasPermission(true) + } + } catch (error) { + setHasPermission(false) + } + } + + checkCameraPermission() + }, []) + + const camera: CameraRef = { + setZoom: (zoom: number) => { + setZoomValue(zoom) + }, + getTakePhoto: () => { + return cameraRef.current?.takePhoto + }, + getStartRecord: () => { + return cameraRef.current?.startRecording + }, + getStopRecord: () => { + return cameraRef.current?.stopRecording + } + } + + if (navigation) { + navigation.camera = camera + } + + // 所有 Hooks 调用完成后再进行条件判断 + if (hasPermission === null) { + return null + } + + if (!hasPermission) { + return null + } + + if (!device) { + return null + } + return ( ) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-web-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-web-view.tsx index 5c530b513a..f40b2a802b 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-web-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-web-view.tsx @@ -226,7 +226,7 @@ const _WebView = forwardRef, WebViewProps>((pr } break case 'postMessage': - bindmessage && bindmessage(getCustomEvent('messsage', {}, { // RN组件销毁顺序与小程序不一致,所以改成和支付宝消息一致 + bindmessage && bindmessage(getCustomEvent('message', {}, { // RN组件销毁顺序与小程序不一致,所以改成和支付宝消息一致 detail: { data: params[0]?.data } diff --git a/packages/webpack-plugin/lib/utils/dom-tag-config.js b/packages/webpack-plugin/lib/utils/dom-tag-config.js index 63a71fccc6..a8fadcd4fa 100644 --- a/packages/webpack-plugin/lib/utils/dom-tag-config.js +++ b/packages/webpack-plugin/lib/utils/dom-tag-config.js @@ -91,7 +91,7 @@ const isBuildInReactTag = makeMap( 'mpx-movable-area,mpx-label,mpx-keyboard-avoiding-view,mpx-input,mpx-inline-text,' + 'mpx-image,mpx-form,mpx-checkbox,mpx-checkbox-group,mpx-button,' + 'mpx-rich-text,mpx-portal,mpx-popup,mpx-picker-view-column,mpx-picker-view,mpx-picker,' + - 'mpx-icon,mpx-canvas' + 'mpx-icon,mpx-canvas,mpx-camera' ) const isSpace = makeMap('ensp,emsp,nbsp') diff --git a/packages/webpack-plugin/package.json b/packages/webpack-plugin/package.json index 2de09d6b9c..9f7bc34246 100644 --- a/packages/webpack-plugin/package.json +++ b/packages/webpack-plugin/package.json @@ -95,6 +95,7 @@ "react-native-safe-area-context": "^4.12.0", "react-native-svg": "^15.8.0", "react-native-video": "^6.9.0", + "react-native-vision-camera": "^4.7.2", "react-native-webview": "^13.12.2", "rimraf": "^6.0.1" }, From c95bff95b496fbde6efb9c9b72fb29218e6eccb9 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 9 Oct 2025 17:16:59 +0800 Subject: [PATCH 03/23] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=9C=AA=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/platform/api/camera/index.ios.js | 9 ++ .../src/platform/api/camera/index.js | 7 ++ .../src/platform/api/camera/rnCamera.js | 111 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 packages/api-proxy/src/platform/api/camera/index.ios.js create mode 100644 packages/api-proxy/src/platform/api/camera/index.js create mode 100644 packages/api-proxy/src/platform/api/camera/rnCamera.js diff --git a/packages/api-proxy/src/platform/api/camera/index.ios.js b/packages/api-proxy/src/platform/api/camera/index.ios.js new file mode 100644 index 0000000000..4ea712dfaf --- /dev/null +++ b/packages/api-proxy/src/platform/api/camera/index.ios.js @@ -0,0 +1,9 @@ +import CreateCamera from './rnCamera' + +function createCameraContext () { + return new CreateCamera() +} + +export { + createCameraContext +} diff --git a/packages/api-proxy/src/platform/api/camera/index.js b/packages/api-proxy/src/platform/api/camera/index.js new file mode 100644 index 0000000000..1df0bbc389 --- /dev/null +++ b/packages/api-proxy/src/platform/api/camera/index.js @@ -0,0 +1,7 @@ +import { ENV_OBJ, envError } from '../../../common/js' + +const createCameraContext = ENV_OBJ.createCameraContext || envError('createCameraContext') + +export { + createCameraContext +} diff --git a/packages/api-proxy/src/platform/api/camera/rnCamera.js b/packages/api-proxy/src/platform/api/camera/rnCamera.js new file mode 100644 index 0000000000..37c185bf6f --- /dev/null +++ b/packages/api-proxy/src/platform/api/camera/rnCamera.js @@ -0,0 +1,111 @@ +import { noop } from '@mpxjs/utils' + +const qualityValue = { + high: 90, + normal: 75, + low: 50, + original: 100 +} +export default class CreateCamera { + constructor () { + const navigation = Object.values(global.__mpxPagesMap || {})[0]?.[1] + this.camera = navigation?.camera || {} + } + + setZoom (options = {}) { + const { zoom, success = noop, fail = noop, complete = noop } = options + if (this.camera.setZoom) { + this.camera.setZoom(zoom) + } + } + takePhoto (options = {}) { + const { success = noop, fail = noop, complete = noop } = options + const takePhoto = this.camera.getTakePhoto?.() + if (takePhoto) { + takePhoto({ + quality: qualityValue[options.quality || 'normal'] + }).then((res) => { + const result = { + errMsg: 'takePhoto:ok', + tempImagePath: res.path + } + success(result) + complete(result) + }).catch(() => { + const result = { + errMsg: 'takePhoto:fail' + } + fail(result) + complete(result) + }) + } + } + startRecord (options = {}) { + let { timeout = 30, success = noop, fail = noop, complete = noop, timeoutCallback = noop } = options + timeout = timeout > 300 ? 300 : timeout + let recordTimer = null + let isTimeout = false + const startRecord = this.camera.getStartRecord?.() + if (startRecord) { + const result = { + errMsg: 'startRecord:ok' + } + success(result) + complete(result) + startRecord({ + onRecordingError: (res) => { + clearTimeout(recordTimer) + timeoutCallback() + }, + onRecordingFinished: (res) => { + if (isTimeout) { + console.log('record timeout, ignore', res) + } + clearTimeout(recordTimer) + console.log('record finished', res) + } + }) + recordTimer = setTimeout(() => { // 超时自动停止 + if (this.camera.stopRecord) { + this.camera.stopRecord().catch(() => {}) + } + }, timeout * 1000) + } else { + const result = { + errMsg: 'startRecord:fail to initialize the camera' + } + fail(result) + complete(result) + } + } + stopRecord(options = {}) { + const { success = noop, fail = noop, complete = noop } = options + const stopRecord = this.camera.getStopRecord?.() + if (stopRecord) { + stopRecord().then((res) => { + console.log('stopRecord res', res) + const result = { + errMsg: 'stopRecord:ok', + tempVideoPath: res.path, + duration: res.duration * 1000, // 转成ms + size: res.fileSize + } + success(result) + complete(result) + }).catch((e) => { + console.log('stopRecord error', e) + const result = { + errMsg: 'stopRecord:fail' + } + fail(result) + complete(result) + }) + } else { + const result = { + errMsg: 'stopRecord:fail to initialize the camera' + } + fail(result) + complete(result) + } + } +} From ec4eb74c7f5ba667a2ca1c464caaf9de68668c96 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Fri, 10 Oct 2025 18:47:17 +0800 Subject: [PATCH 04/23] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=8A=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95CameraContext=E7=9A=84=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/platform/api/camera/rnCamera.js | 105 +++---------- .../src/platform/api/system/index.ios.js | 4 +- .../runtime/components/react/mpx-camera.tsx | 139 ++++++++++++++++-- 3 files changed, 147 insertions(+), 101 deletions(-) diff --git a/packages/api-proxy/src/platform/api/camera/rnCamera.js b/packages/api-proxy/src/platform/api/camera/rnCamera.js index 37c185bf6f..11b2d32e1d 100644 --- a/packages/api-proxy/src/platform/api/camera/rnCamera.js +++ b/packages/api-proxy/src/platform/api/camera/rnCamera.js @@ -1,11 +1,5 @@ import { noop } from '@mpxjs/utils' -const qualityValue = { - high: 90, - normal: 75, - low: 50, - original: 100 -} export default class CreateCamera { constructor () { const navigation = Object.values(global.__mpxPagesMap || {})[0]?.[1] @@ -14,98 +8,37 @@ export default class CreateCamera { setZoom (options = {}) { const { zoom, success = noop, fail = noop, complete = noop } = options - if (this.camera.setZoom) { - this.camera.setZoom(zoom) - } - } - takePhoto (options = {}) { - const { success = noop, fail = noop, complete = noop } = options - const takePhoto = this.camera.getTakePhoto?.() - if (takePhoto) { - takePhoto({ - quality: qualityValue[options.quality || 'normal'] - }).then((res) => { - const result = { - errMsg: 'takePhoto:ok', - tempImagePath: res.path - } + try { + if (this.camera.setZoom) { + const result = { errMsg: 'setZoom:ok' } success(result) complete(result) - }).catch(() => { + this.camera.setZoom(zoom) + } else { const result = { - errMsg: 'takePhoto:fail' + errMsg: 'setZoom:fail camera instance not found' } fail(result) complete(result) - }) - } - } - startRecord (options = {}) { - let { timeout = 30, success = noop, fail = noop, complete = noop, timeoutCallback = noop } = options - timeout = timeout > 300 ? 300 : timeout - let recordTimer = null - let isTimeout = false - const startRecord = this.camera.getStartRecord?.() - if (startRecord) { - const result = { - errMsg: 'startRecord:ok' } - success(result) - complete(result) - startRecord({ - onRecordingError: (res) => { - clearTimeout(recordTimer) - timeoutCallback() - }, - onRecordingFinished: (res) => { - if (isTimeout) { - console.log('record timeout, ignore', res) - } - clearTimeout(recordTimer) - console.log('record finished', res) - } - }) - recordTimer = setTimeout(() => { // 超时自动停止 - if (this.camera.stopRecord) { - this.camera.stopRecord().catch(() => {}) - } - }, timeout * 1000) - } else { + } catch (error) { const result = { - errMsg: 'startRecord:fail to initialize the camera' + errMsg: 'setZoom:fail ' + (error?.message || '') } fail(result) complete(result) } } - stopRecord(options = {}) { - const { success = noop, fail = noop, complete = noop } = options - const stopRecord = this.camera.getStopRecord?.() - if (stopRecord) { - stopRecord().then((res) => { - console.log('stopRecord res', res) - const result = { - errMsg: 'stopRecord:ok', - tempVideoPath: res.path, - duration: res.duration * 1000, // 转成ms - size: res.fileSize - } - success(result) - complete(result) - }).catch((e) => { - console.log('stopRecord error', e) - const result = { - errMsg: 'stopRecord:fail' - } - fail(result) - complete(result) - }) - } else { - const result = { - errMsg: 'stopRecord:fail to initialize the camera' - } - fail(result) - complete(result) - } + + takePhoto (options) { + this.camera?.takePhoto(options) + } + + startRecord (options) { + this.camera?.startRecord(options) + } + + stopRecord (options) { + this.camera?.stopRecord(options) } } diff --git a/packages/api-proxy/src/platform/api/system/index.ios.js b/packages/api-proxy/src/platform/api/system/index.ios.js index ae9b0697dd..18cf06973d 100644 --- a/packages/api-proxy/src/platform/api/system/index.ios.js +++ b/packages/api-proxy/src/platform/api/system/index.ios.js @@ -11,7 +11,7 @@ const getSystemInfoSync = function () { brand: DeviceInfo.getBrand(), model: DeviceInfo.getModel(), system: `${DeviceInfo.getSystemName()} ${DeviceInfo.getSystemVersion()}`, - platform: DeviceInfo.isEmulatorSync() ? 'emulator' : DeviceInfo.getSystemName(), + platform: DeviceInfo.isEmulatorSync() ? 'emulator' : DeviceInfo.getSystemName().toLowerCase(), deviceOrientation: screenWidth > screenHeight ? 'portrait' : 'landscape', fontSizeSetting: PixelRatio.getFontScale() } @@ -68,7 +68,7 @@ const getDeviceInfo = function () { brand: DeviceInfo.getBrand(), model: DeviceInfo.getModel(), system: `${DeviceInfo.getSystemName()} ${DeviceInfo.getSystemVersion()}`, - platform: DeviceInfo.isEmulatorSync() ? 'emulator' : DeviceInfo.getSystemName(), + platform: DeviceInfo.isEmulatorSync() ? 'emulator' : DeviceInfo.getSystemName().toLowerCase(), memorySize: DeviceInfo.getTotalMemorySync() / (1024 * 1024) }) return deviceInfo diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index ba5fdfacaa..745f39a8f3 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,8 +1,16 @@ import React, { forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' -import { Camera, useCameraDevice, useCodeScanner, useCameraFormat, useFrameProcessor } from 'react-native-vision-camera' +import { Camera, useCameraDevice, useCodeScanner, useCameraFormat } from 'react-native-vision-camera' import { getCustomEvent } from './getInnerListeners' +import { noop } from '@mpxjs/utils' import { RouteContext } from './context' +const qualityValue = { + high: 90, + normal: 75, + low: 50, + original: 100 +} + interface CameraProps { mode?: 'normal' | 'scanCode' resolution?: 'low' | 'medium' | 'high' @@ -16,18 +24,40 @@ interface CameraProps { bindscancode?: (result: { type: string, data: string }) => void } +interface TakePhotoOptions { + quality?: 'high' | 'normal' | 'low' | 'original' + success?: (result: { errMsg: string, tempImagePath: string }) => void + fail?: (result: { errMsg: string }) => void + complete?: (result: { errMsg: string, tempImagePath?: string }) => void +} + +interface RecordOptions { + timeout?: number + success?: (result: { errMsg: string }) => void + fail?: (result: { errMsg: string, error?: any }) => void + complete?: (result: { errMsg: string }) => void + timeoutCallback?: (result: { errMsg: string, error?: any }) => void +} + +interface StopRecordOptions { + success?: (result: { errMsg: string, tempVideoPath: string, duration: number }) => void + fail?: (result: { errMsg: string }) => void + complete?: (result: { errMsg: string, tempVideoPath?: string, duration?: number }) => void +} + interface CameraRef { setZoom: (zoom: number) => void - getTakePhoto: () => (() => Promise) | undefined - getStartRecord: () => ((options: any) => void) | undefined - getStopRecord: () => (() => void) | undefined + takePhoto: (options?: TakePhotoOptions) => void + startRecord: (options?: RecordOptions) => void + stopRecord: (options?: StopRecordOptions) => void } type HandlerRef = { - // 根据实际的 HandlerRef 类型定义调整 current: T | null } +let RecordRes: any = null + const _camera = forwardRef, CameraProps>((props: CameraProps, ref): JSX.Element | null => { const cameraRef = useRef(null) const { @@ -115,14 +145,97 @@ const _camera = forwardRef, CameraProps>((props: setZoom: (zoom: number) => { setZoomValue(zoom) }, - getTakePhoto: () => { - return cameraRef.current?.takePhoto + takePhoto: (options: TakePhotoOptions = {}) => { + const { success = noop, fail = noop, complete = noop } = options + cameraRef.current?.takePhoto?.({ + quality: qualityValue[options.quality || 'normal'] as number + } as any).then((res) => { + const result = { + errMsg: 'takePhoto:ok', + tempImagePath: res.path + } + success(result) + complete(result) + }).catch((res) => { + const result = { + errMsg: 'takePhoto:fail' + } + fail(result) + complete(result) + }) }, - getStartRecord: () => { - return cameraRef.current?.startRecording + startRecord: (options: RecordOptions = {}) => { + let { timeout = 30, success = noop, fail = noop, complete = noop, timeoutCallback = noop } = options + timeout = timeout > 300 ? 300 : timeout + let recordTimer: NodeJS.Timeout | null = null + let isTimeout = false + try { + const result = { + errMsg: 'startRecord:ok' + } + success(result) + complete(result) + + cameraRef.current?.startRecording?.({ + onRecordingError: (error) => { + if (recordTimer) clearTimeout(recordTimer) + const errorResult = { + errMsg: 'startRecord:fail during recording', + error: error + } + timeoutCallback(errorResult) + }, + onRecordingFinished: (video) => { + RecordRes = video + if (recordTimer) clearTimeout(recordTimer) + } + }) + + recordTimer = setTimeout(() => { // 超时自动停止 + isTimeout = true + cameraRef.current?.stopRecording().catch(() => { + // 忽略停止录制时的错误 + }) + }, timeout * 1000) + } catch (error: any) { + if (recordTimer) clearTimeout(recordTimer) + const result = { + errMsg: 'startRecord:fail ' + (error.message || 'unknown error') + } + fail(result) + complete(result) + } }, - getStopRecord: () => { - return cameraRef.current?.stopRecording + stopRecord: (options: StopRecordOptions = {}) => { + const { success = noop, fail = noop, complete = noop } = options + try { + cameraRef.current?.stopRecording().then(() => { + setTimeout(() => { + if (RecordRes) { + const result = { + errMsg: 'stopRecord:ok', + tempVideoPath: RecordRes?.path, + duration: RecordRes.duration * 1000 // 转成ms + } + RecordRes = null + success(result) + complete(result) + } + }, 200) // 延时200ms,确保录制结果已准备好 + }).catch((e: any) => { + const result = { + errMsg: 'stopRecord:fail ' + (e.message || 'promise rejected') + } + fail(result) + complete(result) + }) + } catch (error: any) { + const result = { + errMsg: 'stopRecord:fail ' + (error.message || 'unknown error') + } + fail(result) + complete(result) + } } } @@ -147,7 +260,7 @@ const _camera = forwardRef, CameraProps>((props: , CameraProps>((props: _camera.displayName = 'MpxCamera' -export default _camera \ No newline at end of file +export default _camera From cab0287e4f55e694ab9aad6ce043808f194b664e Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Wed, 29 Oct 2025 11:57:33 +0800 Subject: [PATCH 05/23] =?UTF-8?q?=E4=BF=AE=E6=94=B9camera=E7=9A=84?= =?UTF-8?q?=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/runtime/components/react/mpx-camera.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 745f39a8f3..a473aac09e 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,5 +1,5 @@ import React, { forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' -import { Camera, useCameraDevice, useCodeScanner, useCameraFormat } from 'react-native-vision-camera' +// import { Camera, useCameraDevice, useCodeScanner, useCameraFormat } from 'react-native-vision-camera' import { getCustomEvent } from './getInnerListeners' import { noop } from '@mpxjs/utils' import { RouteContext } from './context' @@ -58,8 +58,9 @@ type HandlerRef = { let RecordRes: any = null -const _camera = forwardRef, CameraProps>((props: CameraProps, ref): JSX.Element | null => { - const cameraRef = useRef(null) +const _camera = forwardRef, CameraProps>((props: CameraProps, ref): JSX.Element | null => { + const { Camera, useCameraDevice, useCodeScanner, useCameraFormat } = require('react-native-vision-camera') + const cameraRef = useRef(null) const { mode = 'normal', resolution = 'medium', @@ -100,7 +101,7 @@ const _camera = forwardRef, CameraProps>((props: const codeScanner = useCodeScanner({ codeTypes: ['qr', 'ean-13'], - onCodeScanned: (codes) => { + onCodeScanned: (codes: any[]) => { const result = codes.map(code => code.value).join(',') bindscancode && bindscancode(getCustomEvent('scancode', {}, { detail: { @@ -149,14 +150,14 @@ const _camera = forwardRef, CameraProps>((props: const { success = noop, fail = noop, complete = noop } = options cameraRef.current?.takePhoto?.({ quality: qualityValue[options.quality || 'normal'] as number - } as any).then((res) => { + } as any).then((res: { path: any }) => { const result = { errMsg: 'takePhoto:ok', tempImagePath: res.path } success(result) complete(result) - }).catch((res) => { + }).catch(() => { const result = { errMsg: 'takePhoto:fail' } @@ -177,7 +178,7 @@ const _camera = forwardRef, CameraProps>((props: complete(result) cameraRef.current?.startRecording?.({ - onRecordingError: (error) => { + onRecordingError: (error: any) => { if (recordTimer) clearTimeout(recordTimer) const errorResult = { errMsg: 'startRecord:fail during recording', @@ -185,7 +186,7 @@ const _camera = forwardRef, CameraProps>((props: } timeoutCallback(errorResult) }, - onRecordingFinished: (video) => { + onRecordingFinished: (video: any) => { RecordRes = video if (recordTimer) clearTimeout(recordTimer) } From 08e75c91fd178cb9fe1257384bb7b69bd4515a00 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Wed, 29 Oct 2025 11:59:32 +0800 Subject: [PATCH 06/23] =?UTF-8?q?=E8=A1=A5=E5=85=85eslint=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webpack-plugin/lib/runtime/components/react/mpx-camera.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index a473aac09e..e56e119ab9 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -59,6 +59,7 @@ type HandlerRef = { let RecordRes: any = null const _camera = forwardRef, CameraProps>((props: CameraProps, ref): JSX.Element | null => { + // eslint-disable-next-line @typescript-eslint/no-var-requires const { Camera, useCameraDevice, useCodeScanner, useCameraFormat } = require('react-native-vision-camera') const cameraRef = useRef(null) const { From faff0fd3a80ecfd175ee7ccdef21c7522d91c50b Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 30 Oct 2025 19:37:25 +0800 Subject: [PATCH 07/23] =?UTF-8?q?fix=20camera=E4=B8=ADnavigation=E6=98=AF?= =?UTF-8?q?=E5=90=8C=E4=B8=80=E4=B8=AA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/api-proxy/src/platform/api/camera/rnCamera.js | 7 ++++--- .../lib/runtime/components/react/mpx-camera.tsx | 9 +++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/api-proxy/src/platform/api/camera/rnCamera.js b/packages/api-proxy/src/platform/api/camera/rnCamera.js index 11b2d32e1d..640714b12f 100644 --- a/packages/api-proxy/src/platform/api/camera/rnCamera.js +++ b/packages/api-proxy/src/platform/api/camera/rnCamera.js @@ -1,9 +1,10 @@ -import { noop } from '@mpxjs/utils' +import { noop, getFocusedNavigation } from '@mpxjs/utils' export default class CreateCamera { constructor () { - const navigation = Object.values(global.__mpxPagesMap || {})[0]?.[1] - this.camera = navigation?.camera || {} + const navigation = getFocusedNavigation() || {} + const pageId = navigation.pageId + this.camera = pageId ? navigation.pageMap?.['page' + pageId]?.camera || {} : {} } setZoom (options = {}) { diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index e56e119ab9..4648e2b810 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -62,6 +62,7 @@ const _camera = forwardRef, CameraProps>((props: Ca // eslint-disable-next-line @typescript-eslint/no-var-requires const { Camera, useCameraDevice, useCodeScanner, useCameraFormat } = require('react-native-vision-camera') const cameraRef = useRef(null) + const { pageId } = useContext(RouteContext) || {} const { mode = 'normal', resolution = 'medium', @@ -241,8 +242,12 @@ const _camera = forwardRef, CameraProps>((props: Ca } } - if (navigation) { - navigation.camera = camera + if (navigation && pageId) { + navigation.pageMap = navigation.pageMap || {} + const pageKey = 'page' + pageId + navigation.pageMap[pageKey] = navigation.pageMap[pageKey] || {} + // 现在 camera 对象是稳定的,setZoom 方法会使用最新的 setZoomValue + navigation.pageMap[pageKey].camera = camera } // 所有 Hooks 调用完成后再进行条件判断 From cf2fbcf3d4f2d9bf35ded2e8ae99798480e05b45 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 30 Oct 2025 19:57:13 +0800 Subject: [PATCH 08/23] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api-proxy/src/platform/api/camera/rnCamera.js | 3 +-- .../lib/runtime/components/react/mpx-camera.tsx | 14 ++------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/api-proxy/src/platform/api/camera/rnCamera.js b/packages/api-proxy/src/platform/api/camera/rnCamera.js index 640714b12f..9dec79673c 100644 --- a/packages/api-proxy/src/platform/api/camera/rnCamera.js +++ b/packages/api-proxy/src/platform/api/camera/rnCamera.js @@ -3,8 +3,7 @@ import { noop, getFocusedNavigation } from '@mpxjs/utils' export default class CreateCamera { constructor () { const navigation = getFocusedNavigation() || {} - const pageId = navigation.pageId - this.camera = pageId ? navigation.pageMap?.['page' + pageId]?.camera || {} : {} + this.camera = navigation.camera || {} } setZoom (options = {}) { diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 4648e2b810..b7086b2234 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -62,7 +62,6 @@ const _camera = forwardRef, CameraProps>((props: Ca // eslint-disable-next-line @typescript-eslint/no-var-requires const { Camera, useCameraDevice, useCodeScanner, useCameraFormat } = require('react-native-vision-camera') const cameraRef = useRef(null) - const { pageId } = useContext(RouteContext) || {} const { mode = 'normal', resolution = 'medium', @@ -242,17 +241,8 @@ const _camera = forwardRef, CameraProps>((props: Ca } } - if (navigation && pageId) { - navigation.pageMap = navigation.pageMap || {} - const pageKey = 'page' + pageId - navigation.pageMap[pageKey] = navigation.pageMap[pageKey] || {} - // 现在 camera 对象是稳定的,setZoom 方法会使用最新的 setZoomValue - navigation.pageMap[pageKey].camera = camera - } - - // 所有 Hooks 调用完成后再进行条件判断 - if (hasPermission === null) { - return null + if (navigation) { + navigation.camera = camera } if (!hasPermission) { From 8ddc17395b95f5f82824efbac72d4cda566b74ad Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Tue, 4 Nov 2025 15:15:19 +0800 Subject: [PATCH 09/23] =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8F=AA=E6=9C=89=E4=B8=80=E4=B8=AAcamera?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index b7086b2234..a6bfaaba2e 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -78,6 +78,7 @@ const _camera = forwardRef, CameraProps>((props: Ca const { navigation } = useContext(RouteContext) || {} const [zoomValue, setZoomValue] = useState(1) const [hasPermission, setHasPermission] = useState(null) + const hasCamera = useRef(false) // 先定义常量,避免在条件判断后使用 const maxZoom = device?.maxZoom || 1 @@ -124,25 +125,6 @@ const _camera = forwardRef, CameraProps>((props: Ca bindstop && bindstop() }, [bindstop]) - // 检查相机权限 - useEffect(() => { - const checkCameraPermission = async () => { - try { - const cameraPermission = global?.__mpx?.config?.rnConfig?.cameraPermission - if (typeof cameraPermission === 'function') { - const permissionResult = await cameraPermission() - setHasPermission(permissionResult === true) - } else { - setHasPermission(true) - } - } catch (error) { - setHasPermission(false) - } - } - - checkCameraPermission() - }, []) - const camera: CameraRef = { setZoom: (zoom: number) => { setZoomValue(zoom) @@ -241,15 +223,32 @@ const _camera = forwardRef, CameraProps>((props: Ca } } - if (navigation) { - navigation.camera = camera - } - - if (!hasPermission) { - return null - } + useEffect(() => { + if (navigation) { + if (navigation && !navigation.camera) { + navigation.camera = camera + } else { + hasCamera.current = true + navigation.camera.multi = true + } + } + const checkCameraPermission = async () => { + try { + const cameraPermission = global?.__mpx?.config?.rnConfig?.cameraPermission + if (typeof cameraPermission === 'function') { + const permissionResult = await cameraPermission() + setHasPermission(permissionResult === true) + } else { + setHasPermission(true) + } + } catch (error) { + setHasPermission(false) + } + } + checkCameraPermission() + }, []) - if (!device) { + if (!hasPermission || hasCamera.current || !device) { return null } From 24f61f11ef3848794e4d1c0038dda89f9087d8ab Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Tue, 25 Nov 2025 15:05:07 +0800 Subject: [PATCH 10/23] =?UTF-8?q?=E4=BF=AE=E6=AD=A3flash=E9=80=BB=E8=BE=91?= =?UTF-8?q?,=E4=BB=A5=E5=8F=8A=E6=98=BE=E9=9A=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 97 ++++++++++++++----- 1 file changed, 74 insertions(+), 23 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index a6bfaaba2e..565073ffe0 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,6 +1,6 @@ -import React, { forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' -// import { Camera, useCameraDevice, useCodeScanner, useCameraFormat } from 'react-native-vision-camera' -import { getCustomEvent } from './getInnerListeners' +import React, { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' +import { useTransformStyle, useLayout, extendObject } from './utils' +import useInnerProps, { getCustomEvent } from './getInnerListeners' import { noop } from '@mpxjs/utils' import { RouteContext } from './context' @@ -22,6 +22,11 @@ interface CameraProps { binderror?: (error: { message: string }) => void bindinitdone?: (result: { type: string, data: string }) => void bindscancode?: (result: { type: string, data: string }) => void + 'parent-font-size'?: number + 'parent-width'?: number + 'parent-height'?: number + 'enable-var'?: boolean + 'external-var-context'?: any } interface TakePhotoOptions { @@ -70,9 +75,31 @@ const _camera = forwardRef, CameraProps>((props: Ca frameSize = 'medium', bindinitdone, bindstop, - bindscancode + bindscancode, + 'parent-font-size': parentFontSize, + 'parent-width': parentWidth, + 'parent-height': parentHeight, + 'enable-var': enableVar, + 'external-var-context': externalVarContext, + style = {} } = props - + const styleObj = extendObject( + {}, + style + ) + const { + normalStyle, + hasSelfPercent, + setWidth, + setHeight + } = useTransformStyle(styleObj, { + enableVar, + externalVarContext, + parentFontSize, + parentWidth, + parentHeight + }) + const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: cameraRef }) const isPhoto = mode === 'normal' const device = useCameraDevice(devicePosition || 'back') const { navigation } = useContext(RouteContext) || {} @@ -93,7 +120,6 @@ const _camera = forwardRef, CameraProps>((props: Ca large: { width: 1080, height: 810 } } - // 所有 Hooks 必须在条件判断之前调用 const format = useCameraFormat(device, [ { photoResolution: RESOLUTION_MAPPING[resolution], @@ -132,6 +158,7 @@ const _camera = forwardRef, CameraProps>((props: Ca takePhoto: (options: TakePhotoOptions = {}) => { const { success = noop, fail = noop, complete = noop } = options cameraRef.current?.takePhoto?.({ + flash, quality: qualityValue[options.quality || 'normal'] as number } as any).then((res: { path: any }) => { const result = { @@ -161,6 +188,7 @@ const _camera = forwardRef, CameraProps>((props: Ca complete(result) cameraRef.current?.startRecording?.({ + flash, onRecordingError: (error: any) => { if (recordTimer) clearTimeout(recordTimer) const errorResult = { @@ -246,29 +274,52 @@ const _camera = forwardRef, CameraProps>((props: Ca } } checkCameraPermission() + return () => { + if (navigation && navigation.camera) { + navigation.camera = null + } + } }, []) + const innerProps = useInnerProps( + extendObject( + {}, + props, + layoutProps, + { + ref: cameraRef, + style: extendObject({}, normalStyle, layoutStyle), + isActive: true, + photo: true, + video: true, + onInitialized, + onStopped, + device, + format, + codeScanner: !isPhoto ? codeScanner : undefined, + zoom: zoomValue + } + ), + [ + 'mode', + 'resolution', + 'frame-size', + 'bindinitdone', + 'bindstop', + 'flash', + 'bindscancode', + 'binderror' + ], + { + layoutRef + } + ) + if (!hasPermission || hasCamera.current || !device) { return null } - return ( - - ) + return createElement(Camera, innerProps) }) _camera.displayName = 'MpxCamera' From 2737def5c2cd06590f78297a035405cdfd126b50 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 27 Nov 2025 11:01:59 +0800 Subject: [PATCH 11/23] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/platform/patch/getDefaultOptions.ios.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 3e63144b36..38aba67943 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -642,6 +642,10 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { } useEffect(() => { + if (navigation.camera?.multi) { // RN端一个页面只能有一个camera组件 放在更新中是避免有wx:if的情况 + navigation.camera.multi = false + warn(': 一个页面只能插入一个') + } if (proxy.pendingUpdatedFlag) { proxy.pendingUpdatedFlag = false proxy.callHook(UPDATED) @@ -651,10 +655,6 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { usePageEffect(proxy, pageId) useEffect(() => { proxy.mounted() - if (navigation.camera?.multi) { - navigation.camera.multi = false - warn(': 一个页面只能插入一个') - } return () => { proxy.unmounted() proxy.target.__resetInstance() From 11ae6cccb2a2b32415a836d90ec5264286e7999e Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Wed, 17 Dec 2025 17:59:01 +0800 Subject: [PATCH 12/23] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E5=A4=9A=E4=B8=AAcamera=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/platform/patch/getDefaultOptions.ios.js | 4 ---- .../lib/runtime/components/react/mpx-camera.tsx | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 38aba67943..e39ffa7d09 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -642,10 +642,6 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { } useEffect(() => { - if (navigation.camera?.multi) { // RN端一个页面只能有一个camera组件 放在更新中是避免有wx:if的情况 - navigation.camera.multi = false - warn(': 一个页面只能插入一个') - } if (proxy.pendingUpdatedFlag) { proxy.pendingUpdatedFlag = false proxy.callHook(UPDATED) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 565073ffe0..cd9e9ab119 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,7 +1,7 @@ import React, { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' import { useTransformStyle, useLayout, extendObject } from './utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' -import { noop } from '@mpxjs/utils' +import { noop, warn } from '@mpxjs/utils' import { RouteContext } from './context' const qualityValue = { @@ -257,7 +257,7 @@ const _camera = forwardRef, CameraProps>((props: Ca navigation.camera = camera } else { hasCamera.current = true - navigation.camera.multi = true + warn(': 一个页面只能插入一个') } } const checkCameraPermission = async () => { From d9c3a4aebc0715b19fe5153ca5e46b40a4c3b966 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 22 Jan 2026 14:45:40 +0800 Subject: [PATCH 13/23] =?UTF-8?q?=E4=BF=AE=E6=AD=A3eslint=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/platform/patch/getDefaultOptions.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/platform/patch/getDefaultOptions.ios.js b/packages/core/src/platform/patch/getDefaultOptions.ios.js index 0a183face0..c5cc001391 100644 --- a/packages/core/src/platform/patch/getDefaultOptions.ios.js +++ b/packages/core/src/platform/patch/getDefaultOptions.ios.js @@ -618,7 +618,7 @@ export function getDefaultOptions ({ type, rawOptions = {}, currentInject }) { const instanceRef = useRef(null) const propsRef = useRef(null) const intersectionCtx = useContext(IntersectionObserverContext) - const { pageId, navigation } = useContext(RouteContext) || {} + const { pageId } = useContext(RouteContext) || {} const parentProvides = useContext(ProviderContext) let relation = null if (hasDescendantRelation || hasAncestorRelation) { From 5a49bef1e0b662514b5c518fcfd5fcf2c9c2556d Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 22 Jan 2026 14:57:43 +0800 Subject: [PATCH 14/23] =?UTF-8?q?=E5=9C=A8canIuse=E4=B8=AD=E8=A1=A5?= =?UTF-8?q?=E5=85=85camera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api-proxy/src/platform/api/base/rnCanIUseConfig.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/api-proxy/src/platform/api/base/rnCanIUseConfig.js b/packages/api-proxy/src/platform/api/base/rnCanIUseConfig.js index ce316fde95..2313c391cb 100644 --- a/packages/api-proxy/src/platform/api/base/rnCanIUseConfig.js +++ b/packages/api-proxy/src/platform/api/base/rnCanIUseConfig.js @@ -224,5 +224,13 @@ export const SUPPORTED_OBJECTS = { 'abort', 'onHeadersReceived', 'offHeadersReceived' + ], + + // camera + CameraContext: [ + 'setZoom', + 'takePhoto', + 'startRecord', + 'stopRecord' ] } From 086d012833771533296da89c013c8b5ef507ac22 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 22 Jan 2026 20:06:08 +0800 Subject: [PATCH 15/23] =?UTF-8?q?=E5=88=A0=E9=99=A4=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index cd9e9ab119..f171df89b5 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,4 +1,4 @@ -import React, { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect } from 'react' +import React, { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect, useMemo } from 'react' import { useTransformStyle, useLayout, extendObject } from './utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' import { noop, warn } from '@mpxjs/utils' @@ -105,7 +105,6 @@ const _camera = forwardRef, CameraProps>((props: Ca const { navigation } = useContext(RouteContext) || {} const [zoomValue, setZoomValue] = useState(1) const [hasPermission, setHasPermission] = useState(null) - const hasCamera = useRef(false) // 先定义常量,避免在条件判断后使用 const maxZoom = device?.maxZoom || 1 @@ -151,7 +150,7 @@ const _camera = forwardRef, CameraProps>((props: Ca bindstop && bindstop() }, [bindstop]) - const camera: CameraRef = { + const camera: CameraRef = useMemo(() => ({ setZoom: (zoom: number) => { setZoomValue(zoom) }, @@ -179,7 +178,6 @@ const _camera = forwardRef, CameraProps>((props: Ca let { timeout = 30, success = noop, fail = noop, complete = noop, timeoutCallback = noop } = options timeout = timeout > 300 ? 300 : timeout let recordTimer: NodeJS.Timeout | null = null - let isTimeout = false try { const result = { errMsg: 'startRecord:ok' @@ -204,7 +202,6 @@ const _camera = forwardRef, CameraProps>((props: Ca }) recordTimer = setTimeout(() => { // 超时自动停止 - isTimeout = true cameraRef.current?.stopRecording().catch(() => { // 忽略停止录制时的错误 }) @@ -249,17 +246,9 @@ const _camera = forwardRef, CameraProps>((props: Ca complete(result) } } - } + }), []) useEffect(() => { - if (navigation) { - if (navigation && !navigation.camera) { - navigation.camera = camera - } else { - hasCamera.current = true - warn(': 一个页面只能插入一个') - } - } const checkCameraPermission = async () => { try { const cameraPermission = global?.__mpx?.config?.rnConfig?.cameraPermission @@ -275,12 +264,19 @@ const _camera = forwardRef, CameraProps>((props: Ca } checkCameraPermission() return () => { - if (navigation && navigation.camera) { - navigation.camera = null + if (navigation?.camera === camera) { + delete navigation.camera } } }, []) + if (navigation && navigation.camera && navigation.camera !== camera) { + warn(': 一个页面只能插入一个') + return null + } else if (navigation) { + navigation.camera = camera + } + const innerProps = useInnerProps( extendObject( {}, @@ -315,7 +311,7 @@ const _camera = forwardRef, CameraProps>((props: Ca } ) - if (!hasPermission || hasCamera.current || !device) { + if (!hasPermission || !device) { return null } From c3577b52f31f05a7370ca5ca6d83d797e86ec127 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 22 Jan 2026 20:14:05 +0800 Subject: [PATCH 16/23] =?UTF-8?q?=E4=BF=AE=E6=AD=A3eslint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/runtime/components/react/mpx-camera.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index f171df89b5..e29492b2f2 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -270,13 +270,6 @@ const _camera = forwardRef, CameraProps>((props: Ca } }, []) - if (navigation && navigation.camera && navigation.camera !== camera) { - warn(': 一个页面只能插入一个') - return null - } else if (navigation) { - navigation.camera = camera - } - const innerProps = useInnerProps( extendObject( {}, @@ -311,6 +304,13 @@ const _camera = forwardRef, CameraProps>((props: Ca } ) + if (navigation && navigation.camera && navigation.camera !== camera) { + warn(': 一个页面只能插入一个') + return null + } else if (navigation) { + navigation.camera = camera + } + if (!hasPermission || !device) { return null } From 1bfa758df642d01316a93a04ecb8ff53a1214b5b Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Fri, 23 Jan 2026 10:35:28 +0800 Subject: [PATCH 17/23] =?UTF-8?q?=E8=A1=A5=E5=85=85scancode=E7=9A=84type?= =?UTF-8?q?=E5=92=8CscanArea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/runtime/components/react/mpx-camera.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index e29492b2f2..a51bb80986 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -129,12 +129,17 @@ const _camera = forwardRef, CameraProps>((props: Ca const codeScanner = useCodeScanner({ codeTypes: ['qr', 'ean-13'], onCodeScanned: (codes: any[]) => { - const result = codes.map(code => code.value).join(',') - bindscancode && bindscancode(getCustomEvent('scancode', {}, { - detail: { - result: codes.map(code => code.value).join(',') - } - })) + codes.forEach(code => { + const type = code.type === 'qr' ? 'QR_CODE' : code.type?.toUpperCase() + const frame = code.frame || {} + bindscancode && bindscancode(getCustomEvent('scancode', {}, { + detail: { + result: code.value, + type, + scanArea: [parseInt(frame.x) || 0, parseInt(frame.y) || 0, parseInt(frame.width) || 0, parseInt(frame.height) || 0] + } + })) + }) } }) From 9f672a64513a3a20855b4b3e6717da3b51351849 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 29 Jan 2026 10:59:13 +0800 Subject: [PATCH 18/23] =?UTF-8?q?=E4=BF=AE=E6=94=B9flash=E7=9A=84=E5=BC=80?= =?UTF-8?q?=E5=90=AF=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/runtime/components/react/mpx-camera.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index a51bb80986..38fbb537f4 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -162,7 +162,6 @@ const _camera = forwardRef, CameraProps>((props: Ca takePhoto: (options: TakePhotoOptions = {}) => { const { success = noop, fail = noop, complete = noop } = options cameraRef.current?.takePhoto?.({ - flash, quality: qualityValue[options.quality || 'normal'] as number } as any).then((res: { path: any }) => { const result = { @@ -191,7 +190,6 @@ const _camera = forwardRef, CameraProps>((props: Ca complete(result) cameraRef.current?.startRecording?.({ - flash, onRecordingError: (error: any) => { if (recordTimer) clearTimeout(recordTimer) const errorResult = { @@ -291,7 +289,8 @@ const _camera = forwardRef, CameraProps>((props: Ca device, format, codeScanner: !isPhoto ? codeScanner : undefined, - zoom: zoomValue + zoom: zoomValue, + torch: flash === 'on' ? 'on' : flash === 'off' ? 'off' : undefined } ), [ From 44a69aaab5961f1ff7e7c3632afaeee235772a9c Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 29 Jan 2026 11:29:15 +0800 Subject: [PATCH 19/23] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BC=A0=E9=80=92?= =?UTF-8?q?=E7=9A=84torch=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webpack-plugin/lib/runtime/components/react/mpx-camera.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 38fbb537f4..2cda51ddda 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -290,7 +290,7 @@ const _camera = forwardRef, CameraProps>((props: Ca format, codeScanner: !isPhoto ? codeScanner : undefined, zoom: zoomValue, - torch: flash === 'on' ? 'on' : flash === 'off' ? 'off' : undefined + torch: flash } ), [ From 1c441f7720ad7d85c8b5b6be90dcb37c77fe8b3a Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Thu, 29 Jan 2026 19:30:47 +0800 Subject: [PATCH 20/23] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=86=E8=BE=A8?= =?UTF-8?q?=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 2cda51ddda..04939ea06e 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,4 +1,4 @@ -import React, { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect, useMemo } from 'react' +import { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect, useMemo } from 'react' import { useTransformStyle, useLayout, extendObject } from './utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' import { noop, warn } from '@mpxjs/utils' @@ -14,9 +14,9 @@ const qualityValue = { interface CameraProps { mode?: 'normal' | 'scanCode' resolution?: 'low' | 'medium' | 'high' - devicePosition?: 'front' | 'back' + 'device-position'?: 'front' | 'back' flash?: 'auto' | 'on' | 'off' - frameSize?: 'small' | 'medium' | 'large' + 'frame-size'?: 'small' | 'medium' | 'large' style?: Record bindstop?: () => void binderror?: (error: { message: string }) => void @@ -70,9 +70,9 @@ const _camera = forwardRef, CameraProps>((props: Ca const { mode = 'normal', resolution = 'medium', - devicePosition = 'back', + 'device-position': devicePosition = 'back', flash = 'auto', - frameSize = 'medium', + 'frame-size': frameSize = 'medium', bindinitdone, bindstop, bindscancode, @@ -108,15 +108,15 @@ const _camera = forwardRef, CameraProps>((props: Ca // 先定义常量,避免在条件判断后使用 const maxZoom = device?.maxZoom || 1 - const RESOLUTION_MAPPING: Record = { - low: { width: 640, height: 480 }, - medium: { width: 1280, height: 720 }, - high: { width: 1920, height: 1080 } + const RESOLUTION_MAPPING: Record = { + low: { width: 1280, height: 720 }, + medium: { width: 1920, height: 1080 }, + high: 'max' } - const FRAME_SIZE_MAPPING: Record = { - small: { width: 480, height: 360 }, - medium: { width: 720, height: 540 }, - large: { width: 1080, height: 810 } + const FRAME_SIZE_MAPPING: Record = { + small: { width: 1280, height: 720 }, + medium: { width: 1920, height: 1080 }, + large: 'max' } const format = useCameraFormat(device, [ @@ -127,7 +127,7 @@ const _camera = forwardRef, CameraProps>((props: Ca ]) const codeScanner = useCodeScanner({ - codeTypes: ['qr', 'ean-13'], + codeTypes: ['qr'], onCodeScanned: (codes: any[]) => { codes.forEach(code => { const type = code.type === 'qr' ? 'QR_CODE' : code.type?.toUpperCase() From 4a1df2b565707fa207dc746de301c1708e4e7b5b Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Mon, 2 Feb 2026 10:54:45 +0800 Subject: [PATCH 21/23] =?UTF-8?q?=E8=A1=A5=E5=85=85appshow=E5=92=8Capphide?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E5=80=99camera=E7=9A=84active=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 04939ea06e..658bbc496e 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,5 +1,5 @@ import { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect, useMemo } from 'react' -import { useTransformStyle, useLayout, extendObject } from './utils' +import { getCurrentPage, useTransformStyle, useLayout, extendObject } from './utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' import { noop, warn } from '@mpxjs/utils' import { RouteContext } from './context' @@ -102,9 +102,11 @@ const _camera = forwardRef, CameraProps>((props: Ca const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: cameraRef }) const isPhoto = mode === 'normal' const device = useCameraDevice(devicePosition || 'back') - const { navigation } = useContext(RouteContext) || {} + const { navigation, pageId } = useContext(RouteContext) || {} const [zoomValue, setZoomValue] = useState(1) + const [isActive, setIsActive] = useState(true) const [hasPermission, setHasPermission] = useState(null) + const page = getCurrentPage(pageId) // 先定义常量,避免在条件判断后使用 const maxZoom = device?.maxZoom || 1 @@ -118,6 +120,21 @@ const _camera = forwardRef, CameraProps>((props: Ca medium: { width: 1920, height: 1080 }, large: 'max' } + const showHandle = useCallback(() => { + if (page.id === pageId) { + setIsActive(true) + } + }, []) + const hideHandle = useCallback(() => { + setIsActive(false) + }, []) + + if (global.__mpxAppCbs.show.indexOf(showHandle) === -1) { + global.__mpxAppCbs.show.push(showHandle) + } + if (global.__mpxAppCbs.hide.indexOf(hideHandle) === -1) { + global.__mpxAppCbs.hide.push(hideHandle) + } const format = useCameraFormat(device, [ { @@ -270,6 +287,14 @@ const _camera = forwardRef, CameraProps>((props: Ca if (navigation?.camera === camera) { delete navigation.camera } + const showHandleIndex = global.__mpxAppCbs.show.indexOf(showHandle) + const hideHandleIndex = global.__mpxAppCbs.hide.indexOf(hideHandle) + if (showHandleIndex !== -1) { + global.__mpxAppCbs.show.splice(showHandleIndex, 1) + } + if (hideHandleIndex !== -1) { + global.__mpxAppCbs.hide.splice(hideHandleIndex, 1) + } } }, []) @@ -281,7 +306,7 @@ const _camera = forwardRef, CameraProps>((props: Ca { ref: cameraRef, style: extendObject({}, normalStyle, layoutStyle), - isActive: true, + isActive, photo: true, video: true, onInitialized, From 64707279447878f50045178d884d0ce506245db2 Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Mon, 2 Feb 2026 16:31:32 +0800 Subject: [PATCH 22/23] =?UTF-8?q?=E5=A2=9E=E5=8A=A0page=20hide&show?= =?UTF-8?q?=E7=9A=84=E7=9B=91=E5=90=AC=E9=80=BB=E8=BE=91=E6=8E=A7=E5=88=B6?= =?UTF-8?q?camera=E7=9A=84=E6=BF=80=E6=B4=BB=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 658bbc496e..381cd903b3 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -1,8 +1,9 @@ import { createElement, forwardRef, useRef, useCallback, useContext, useState, useEffect, useMemo } from 'react' import { getCurrentPage, useTransformStyle, useLayout, extendObject } from './utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' -import { noop, warn } from '@mpxjs/utils' +import { noop, warn, hasOwn } from '@mpxjs/utils' import { RouteContext } from './context' +import { watch, WatchOptions } from '@mpxjs/core' const qualityValue = { high: 90, @@ -120,21 +121,6 @@ const _camera = forwardRef, CameraProps>((props: Ca medium: { width: 1920, height: 1080 }, large: 'max' } - const showHandle = useCallback(() => { - if (page.id === pageId) { - setIsActive(true) - } - }, []) - const hideHandle = useCallback(() => { - setIsActive(false) - }, []) - - if (global.__mpxAppCbs.show.indexOf(showHandle) === -1) { - global.__mpxAppCbs.show.push(showHandle) - } - if (global.__mpxAppCbs.hide.indexOf(hideHandle) === -1) { - global.__mpxAppCbs.hide.push(hideHandle) - } const format = useCameraFormat(device, [ { @@ -269,6 +255,19 @@ const _camera = forwardRef, CameraProps>((props: Ca }), []) useEffect(() => { + let unWatch: any + if (pageId && hasOwn(global.__mpxPageStatusMap, String(pageId))) { + unWatch = watch(() => global.__mpxPageStatusMap[pageId], (newVal: string) => { + if (newVal === 'show') { + if (page.id === pageId) { + setIsActive(true) + } + } + if (newVal === 'hide') { + setIsActive(false) + } + }, { sync: true } as WatchOptions) + } const checkCameraPermission = async () => { try { const cameraPermission = global?.__mpx?.config?.rnConfig?.cameraPermission @@ -287,14 +286,7 @@ const _camera = forwardRef, CameraProps>((props: Ca if (navigation?.camera === camera) { delete navigation.camera } - const showHandleIndex = global.__mpxAppCbs.show.indexOf(showHandle) - const hideHandleIndex = global.__mpxAppCbs.hide.indexOf(hideHandle) - if (showHandleIndex !== -1) { - global.__mpxAppCbs.show.splice(showHandleIndex, 1) - } - if (hideHandleIndex !== -1) { - global.__mpxAppCbs.hide.splice(hideHandleIndex, 1) - } + unWatch && unWatch() } }, []) From 71caebf89bcdb1b52bf2fdc5b8336ffa1741242a Mon Sep 17 00:00:00 2001 From: wangcuijuan Date: Wed, 4 Feb 2026 17:43:42 +0800 Subject: [PATCH 23/23] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=9C=A8=E6=89=AB?= =?UTF-8?q?=E7=A0=81=E6=A8=A1=E5=BC=8F=E4=B8=8B=E4=B8=8D=E8=83=BD=E6=8B=8D?= =?UTF-8?q?=E7=85=A7=E5=92=8C=E8=A7=86=E9=A2=91=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../runtime/components/react/mpx-camera.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx index 381cd903b3..b45b0cec14 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-camera.tsx @@ -101,7 +101,8 @@ const _camera = forwardRef, CameraProps>((props: Ca parentHeight }) const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: cameraRef }) - const isPhoto = mode === 'normal' + const isPhoto = useRef(false) + isPhoto.current = mode === 'normal' const device = useCameraDevice(devicePosition || 'back') const { navigation, pageId } = useContext(RouteContext) || {} const [zoomValue, setZoomValue] = useState(1) @@ -128,7 +129,17 @@ const _camera = forwardRef, CameraProps>((props: Ca videoResolution: FRAME_SIZE_MAPPING[frameSize] || RESOLUTION_MAPPING[resolution] } ]) - + const isScancode = useCallback((fail: (res: { errMsg: string }) => void, complete: (res: { errMsg: string }) => void) => { + if (!isPhoto.current) { + const result = { + errMsg: 'Not allow to invoke takePhoto in \'scanCode\' mode.' + } + fail(result) + complete(result) + return true + } + return false + }, []) const codeScanner = useCodeScanner({ codeTypes: ['qr'], onCodeScanned: (codes: any[]) => { @@ -164,6 +175,7 @@ const _camera = forwardRef, CameraProps>((props: Ca }, takePhoto: (options: TakePhotoOptions = {}) => { const { success = noop, fail = noop, complete = noop } = options + if (isScancode(fail, complete)) return cameraRef.current?.takePhoto?.({ quality: qualityValue[options.quality || 'normal'] as number } as any).then((res: { path: any }) => { @@ -185,6 +197,7 @@ const _camera = forwardRef, CameraProps>((props: Ca let { timeout = 30, success = noop, fail = noop, complete = noop, timeoutCallback = noop } = options timeout = timeout > 300 ? 300 : timeout let recordTimer: NodeJS.Timeout | null = null + if (isScancode(fail, complete)) return try { const result = { errMsg: 'startRecord:ok' @@ -223,6 +236,7 @@ const _camera = forwardRef, CameraProps>((props: Ca }, stopRecord: (options: StopRecordOptions = {}) => { const { success = noop, fail = noop, complete = noop } = options + if (isScancode(fail, complete)) return try { cameraRef.current?.stopRecording().then(() => { setTimeout(() => { @@ -305,7 +319,7 @@ const _camera = forwardRef, CameraProps>((props: Ca onStopped, device, format, - codeScanner: !isPhoto ? codeScanner : undefined, + codeScanner: !isPhoto.current ? codeScanner : undefined, zoom: zoomValue, torch: flash }