Commit 5e2af034b569e9f765fa37ff22083d00f1ef692d

Authored by Min
2 parents a087b3d5 d2e294d5

Merge branch 'main' of http://git.xlyprint.cn/xlyErp/xlyUmi

package.json
@@ -44,6 +44,7 @@ @@ -44,6 +44,7 @@
44 "react-sortable-hoc": "^2.0.0", 44 "react-sortable-hoc": "^2.0.0",
45 "react-to-print": "^3.0.5", 45 "react-to-print": "^3.0.5",
46 "umi": "^4.4.11", 46 "umi": "^4.4.11",
  47 + "vconsole": "^3.15.1",
47 "weixin-js-sdk": "1.6.0", 48 "weixin-js-sdk": "1.6.0",
48 "xlsx": "^0.18.5" 49 "xlsx": "^0.18.5"
49 }, 50 },
src/mobile/common/CommobileListEvent.js
@@ -389,6 +389,9 @@ export default (ChildComponent) => { @@ -389,6 +389,9 @@ export default (ChildComponent) => {
389 this.handleBatchCancelExamine(); 389 this.handleBatchCancelExamine();
390 } else if (name === 'BtnAdd') { 390 } else if (name === 'BtnAdd') {
391 this.handleAdd(); 391 this.handleAdd();
  392 + } else if (name === 'BtnScanFace') {
  393 + console.log(this.props, 'BtnScanFace');
  394 +
392 } 395 }
393 }; 396 };
394 397
src/mobile/login/LoginMobile.js
@@ -30,7 +30,13 @@ class LoginMobile extends React.Component { @@ -30,7 +30,13 @@ class LoginMobile extends React.Component {
30 username: localStorage.getItem(`${commonConfig.prefix}username`) || '', 30 username: localStorage.getItem(`${commonConfig.prefix}username`) || '',
31 userpwd: localStorage.getItem(`${commonConfig.prefix}userpwd`) || '', 31 userpwd: localStorage.getItem(`${commonConfig.prefix}userpwd`) || '',
32 loginCompany: JSON.parse(localStorage.getItem(`${commonConfig.prefix}loginCompany`)) || [], 32 loginCompany: JSON.parse(localStorage.getItem(`${commonConfig.prefix}loginCompany`)) || [],
  33 + cameraVisible: false,
  34 + videoStream: null,
  35 + cameraError: null,
  36 + autoCaptureTimer: null,
  37 + faceRecognizing: false,
33 }; 38 };
  39 + this.videoRef = React.createRef();
34 } 40 }
35 async componentWillMount() { 41 async componentWillMount() {
36 const configUrl = `${commonConfig.server_host}sysbrands/getSysbrands`; 42 const configUrl = `${commonConfig.server_host}sysbrands/getSysbrands`;
@@ -42,7 +48,7 @@ class LoginMobile extends React.Component { @@ -42,7 +48,7 @@ class LoginMobile extends React.Component {
42 // const sId = commonUtils.isNotEmptyObject(this.state.sId) ? this.state.sId : companys[0].sId; 48 // const sId = commonUtils.isNotEmptyObject(this.state.sId) ? this.state.sId : companys[0].sId;
43 if (commonUtils.isNotEmptyArr(companys)) { 49 if (commonUtils.isNotEmptyArr(companys)) {
44 // eslint-disable-next-line array-callback-return 50 // eslint-disable-next-line array-callback-return
45 - companys.map((item) => { 51 + companys.forEach((item) => {
46 const map = { ...item }; 52 const map = { ...item };
47 map.label = item.sName; 53 map.label = item.sName;
48 map.value = item.sId; 54 map.value = item.sId;
@@ -92,7 +98,7 @@ class LoginMobile extends React.Component { @@ -92,7 +98,7 @@ class LoginMobile extends React.Component {
92 }, 98 },
93 ] 99 ]
94 ); 100 );
95 - }else { 101 + } else {
96 102
97 103
98 alert( 104 alert(
@@ -106,32 +112,128 @@ class LoginMobile extends React.Component { @@ -106,32 +112,128 @@ class LoginMobile extends React.Component {
106 3.其他以颜色或加粗进行标识的重要条款。<br /> 112 3.其他以颜色或加粗进行标识的重要条款。<br />
107 如您对以上协议有任何疑问,可通过人工客服或发邮件至yanghl@xlyerp.com与我们联系。您点击“同意并继续”的行为即表示您已阅读完毕并同意以上协议的全部内容。 113 如您对以上协议有任何疑问,可通过人工客服或发邮件至yanghl@xlyerp.com与我们联系。您点击“同意并继续”的行为即表示您已阅读完毕并同意以上协议的全部内容。
108 </p>, [ 114 </p>, [
109 - {  
110 - text: '不同意',  
111 - onPress: () => {  
112 - const { plus } = window;  
113 - if (plus) {  
114 - plus.runtime.quit();  
115 - }  
116 - }, 115 + {
  116 + text: '不同意',
  117 + onPress: () => {
  118 + const { plus } = window;
  119 + if (plus) {
  120 + plus.runtime.quit();
  121 + }
117 }, 122 },
118 - {  
119 - text: '同意并继续',  
120 - onPress: () => {  
121 - localStorage.setItem(`${commonConfig.prefix}privacyPolicy`, 'agree');  
122 - }, 123 + },
  124 + {
  125 + text: '同意并继续',
  126 + onPress: () => {
  127 + localStorage.setItem(`${commonConfig.prefix}privacyPolicy`, 'agree');
123 }, 128 },
124 - ], 129 + },
  130 + ],
125 ); 131 );
126 } 132 }
127 } 133 }
128 // vConsole = new VConsole(); 134 // vConsole = new VConsole();
129 } 135 }
130 136
131 - // componentWillUnmount() {  
132 - // vConsole.destroy();  
133 - // } 137 + componentWillUnmount() {
  138 + // vConsole.destroy();
  139 + this.closeCamera();
  140 + if (this.state.autoCaptureTimer) {
  141 + clearTimeout(this.state.autoCaptureTimer);
  142 + }
  143 + }
  144 + closeCamera = () => {
  145 + if (this.state.videoStream) {
  146 + this.state.videoStream.getTracks().forEach(track => track.stop());
  147 + this.setState({ videoStream: null });
  148 + }
  149 + if (this.videoRef.current) {
  150 + this.videoRef.current.srcObject = null;
  151 + }
  152 + if (this.state.autoCaptureTimer) {
  153 + clearTimeout(this.state.autoCaptureTimer);
  154 + }
  155 + };
  156 +
  157 + startCamera = () => {
  158 + navigator.mediaDevices.getUserMedia({
  159 + video: {
  160 + facingMode: 'user',
  161 + width: { ideal: 640 },
  162 + height: { ideal: 480 },
  163 + },
  164 + })
  165 + .then(stream => {
  166 + this.setState({
  167 + cameraVisible: true,
  168 + videoStream: stream,
  169 + cameraError: null,
  170 + autoCaptureTimer: setTimeout(() => {
  171 + this.capturePhoto();
  172 + }, 2000),
  173 + }, () => {
  174 + if (this.videoRef.current) {
  175 + this.videoRef.current.srcObject = stream;
  176 + this.videoRef.current.play();
  177 + }
  178 + });
  179 + })
  180 + .catch(error => {
  181 + console.error('无法访问摄像头:', error);
  182 + this.setState({
  183 + cameraError: '无法访问摄像头,请检查权限设置',
  184 + cameraVisible: false,
  185 + });
  186 + Toast.fail('无法访问摄像头,请检查权限');
  187 + });
  188 + };
  189 + capturePhoto = () => {
  190 + if (!this.videoRef.current || !this.state.cameraVisible) return;
  191 +
  192 + const video = this.videoRef.current;
  193 + const canvas = document.createElement('canvas');
  194 + canvas.width = video.videoWidth;
  195 + canvas.height = video.videoHeight;
  196 + const ctx = canvas.getContext('2d');
  197 + ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  198 +
  199 + const base64 = canvas.toDataURL('image/jpeg', 0.85).split(',')[1];
  200 + // 不再立即 closeCamera(),而是进入识别状态
  201 + this.setState({ faceRecognizing: true });
  202 + this.processFaceRecognition(base64);
  203 + };
134 204
  205 + processFaceRecognition = (base64) => {
  206 + const url = `${commonConfig.face_host}/face/faceSearch`;
  207 + commonServices.postValueService(null, [{
  208 + image: base64,
  209 + image_type: "BASE64",
  210 + face_field: "age",
  211 + option: "COMMON"
  212 + }], url)
  213 + .then(({ data: dataReturn }) => {
  214 + if (dataReturn?.code === 1 && dataReturn.dataset.rows.length > 0) {
  215 + const value = {
  216 + sEmployeeNo: dataReturn.dataset.rows[0].sEmployeeNo,
  217 + sParentId: dataReturn.dataset.rows[0].sBrandsId,
  218 + sId: dataReturn.dataset.rows[0].sSubsidiaryId
  219 + };
  220 + this.handleLogin(value);
  221 + } else {
  222 + Toast.fail(dataReturn?.msg || '人脸识别失败,请重试');
  223 + }
  224 + })
  225 + .catch(err => {
  226 + console.error('人脸识别失败:', err);
  227 + Toast.fail('人脸识别失败');
  228 + })
  229 + .finally(() => {
  230 + this.closeCamera();
  231 + this.setState({
  232 + cameraVisible: false,
  233 + faceRecognizing: false
  234 + });
  235 + });
  236 + };
135 onChange = (name, newValue) => { 237 onChange = (name, newValue) => {
136 if (name === 'username') { 238 if (name === 'username') {
137 this.setState({ username: newValue }); 239 this.setState({ username: newValue });
@@ -145,7 +247,7 @@ class LoginMobile extends React.Component { @@ -145,7 +247,7 @@ class LoginMobile extends React.Component {
145 }); 247 });
146 } 248 }
147 249
148 - takeTenPhotos = () => { 250 + takeTenPhotos = () => {
149 if (!window.plus) { 251 if (!window.plus) {
150 return Promise.reject(new Error('请在 HBuilder 打包的 App 中运行')); 252 return Promise.reject(new Error('请在 HBuilder 打包的 App 中运行'));
151 } 253 }
@@ -279,18 +381,32 @@ class LoginMobile extends React.Component { @@ -279,18 +381,32 @@ class LoginMobile extends React.Component {
279 } 381 }
280 382
281 render() { 383 render() {
282 - const { loginCompany, companys } = this.state; 384 + const { loginCompany, companys, cameraVisible, cameraError } = this.state;
283 console.log('2loginCompany22', loginCompany, companys); 385 console.log('2loginCompany22', loginCompany, companys);
284 const { getFieldProps } = this.props.form; 386 const { getFieldProps } = this.props.form;
285 let sLanguage = commonUtils.isNotEmptyArr(companys) ? companys[0].sLanguage : 'sChinese'; 387 let sLanguage = commonUtils.isNotEmptyArr(companys) ? companys[0].sLanguage : 'sChinese';
286 - const settingTitle = sLanguage === 'sEnglish' ? 'server settings' :'服务器设置';  
287 - const loginTitle = sLanguage === 'sEnglish' ? 'Login' :'登录';  
288 - const loginFaceTitle = sLanguage === 'sEnglish' ? 'Face Login' :'人脸登录';  
289 - const pleaseInputUser = sLanguage === 'sEnglish' ? 'please Input User' :'请输入用户';  
290 - const pleaseInputPassword = sLanguage === 'sEnglish' ? 'please Input Password' :'请输入密码'; 388 + const settingTitle = sLanguage === 'sEnglish' ? 'server settings' : '服务器设置';
  389 + const loginTitle = sLanguage === 'sEnglish' ? 'Login' : '登录';
  390 + const loginFaceTitle = sLanguage === 'sEnglish' ? 'Face Login' : '人脸登录';
  391 + const pleaseInputUser = sLanguage === 'sEnglish' ? 'please Input User' : '请输入用户';
  392 + const pleaseInputPassword = sLanguage === 'sEnglish' ? 'please Input Password' : '请输入密码';
291 393
292 return ( 394 return (
293 <div className={styles.wraper}> 395 <div className={styles.wraper}>
  396 + {/* 摄像头预览层 - 仅在cameraVisible为true时显示 */}
  397 + {cameraVisible && (
  398 + <div className={styles.cameraOverlay}>
  399 + <video
  400 + ref={this.videoRef}
  401 + autoPlay
  402 + playsInline
  403 + style={{ width: '100%', height: '100vh', objectFit: 'cover' }}
  404 + />
  405 + {cameraError && (
  406 + <div className={styles.cameraError}>{cameraError}</div>
  407 + )}
  408 + </div>
  409 + )}
294 <div className={styles.logo}> 410 <div className={styles.logo}>
295 <img src={LoginIcon} alt="login" /> 411 <img src={LoginIcon} alt="login" />
296 </div> 412 </div>
@@ -349,7 +465,7 @@ class LoginMobile extends React.Component { @@ -349,7 +465,7 @@ class LoginMobile extends React.Component {
349 <Button type="primary" onClick={() => { this.handleLogin(); }} style={{ backgroundColor: "#5e81e5" }}> 465 <Button type="primary" onClick={() => { this.handleLogin(); }} style={{ backgroundColor: "#5e81e5" }}>
350 {loginTitle} 466 {loginTitle}
351 </Button> 467 </Button>
352 - <Button type="primary" onClick={this.handleFaceLogin.bind(this)} style={{ backgroundColor: "#5e81e5" }}> 468 + <Button type="primary" onClick={this.startCamera} style={{ backgroundColor: "#5e81e5" }}>
353 {loginFaceTitle} 469 {loginFaceTitle}
354 </Button> 470 </Button>
355 </div> 471 </div>