interview.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const common_config = require("../../common/config.js");
  4. const _sfc_main = {
  5. data() {
  6. return {
  7. currentStep: 0,
  8. photos: [null, null, null, null, null, null],
  9. // 存储6张照片:左手正面、左手反面、左手握拳、右手正面、右手反面、右手握拳
  10. instructions: [
  11. "请将左手掌正面朝上,保持手指自然伸展,确保整个手掌清晰可见",
  12. "请将左手掌反面朝上,保持手指自然伸展,确保整个手背清晰可见",
  13. "请握紧左手拳头,使拳面朝向相机,确保整个拳头清晰可见",
  14. "请将右手掌正面朝上,保持手指自然伸展,确保整个手掌清晰可见",
  15. "请将右手掌反面朝上,保持手指自然伸展,确保整个手背清晰可见",
  16. "请握紧右手拳头,使拳面朝向相机,确保整个拳头清晰可见"
  17. ],
  18. previewTexts: [
  19. "左手掌正面照片预览区",
  20. "左手掌反面照片预览区",
  21. "左手握拳照片预览区",
  22. "右手掌正面照片预览区",
  23. "右手掌反面照片预览区",
  24. "右手握拳照片预览区"
  25. ],
  26. photoTypes: ["left_palm", "left_back", "left_fist", "right_palm", "right_back", "right_fist"],
  27. // 照片类型标识
  28. isH5: false,
  29. showCamera: false,
  30. mediaStream: null,
  31. cameraContext: null,
  32. cameraMode: "normal",
  33. // 添加相机模式
  34. cameraInitRetries: 0,
  35. // 添加重试计数器
  36. gestureOverlays: [
  37. "http://data.qicai321.com/minlong/18590378-6792-4a26-be18-ad9f2bcc4159.png",
  38. "http://data.qicai321.com/minlong/e08cff2b-3b1d-478f-a62b-766b38445a16.png",
  39. "http://data.qicai321.com/minlong/d30ccab7-9cfe-4386-bf0e-ac1f0045bd76.png",
  40. "http://data.qicai321.com/minlong/8068a698-ac40-4a5d-a737-54dba47a668d.png",
  41. "http://data.qicai321.com/minlong/f04a1f7e-4f79-49c7-9a02-bbad296672ea.png",
  42. "http://data.qicai321.com/minlong/1d9bbc36-2fb8-4489-bff6-c36e7a31bd37.png"
  43. /* '/static/images/palm_overlay.png', // 手掌正面引导图
  44. '/static/images/back_overlay.png', // 手掌反面引导图
  45. '/static/images/fist_overlay.png' // 握拳引导图 */
  46. ],
  47. gestureHints: [
  48. "请将左手掌对准轮廓线",
  49. "请将左手背对准轮廓线",
  50. "请将左手拳头对准轮廓线",
  51. "请将右手掌对准轮廓线",
  52. "请将右手背对准轮廓线",
  53. "请将右手拳头对准轮廓线"
  54. ],
  55. photoLinks: [null, null, null, null, null, null],
  56. // 存储上传后的图片链接
  57. cameraPosition: "front",
  58. // 修改默认为前置摄像头
  59. guideIcon: [
  60. "http://data.qicai321.com/minlong/fd080e3b-67be-4ce7-87a3-58d047cfbbb1.mp4",
  61. "http://data.qicai321.com/minlong/394a858c-672a-44bd-8206-450913863900.mp4",
  62. "http://data.qicai321.com/minlong/37153e68-a8f0-4a93-b677-5e5678379243.mp4",
  63. "http://data.qicai321.com/minlong/ebf6e43f-ffdf-49bb-8b00-adeff9ce1b56.mp4",
  64. "http://data.qicai321.com/minlong/e0ca45e2-af3e-42a0-9dd1-4de545e9c720.mp4",
  65. "http://data.qicai321.com/minlong/77028dd6-22bb-41b4-8879-19699e8b1dbb.mp4"
  66. ]
  67. };
  68. },
  69. computed: {
  70. canGoNext() {
  71. return this.photos[this.currentStep] !== null;
  72. }
  73. },
  74. onLoad() {
  75. common_vendor.index.authorize({
  76. scope: "scope.camera",
  77. success: () => {
  78. console.log("相机权限已授权");
  79. },
  80. fail: () => {
  81. common_vendor.index.showModal({
  82. title: "提示",
  83. content: "请授权相机权限以完成手部照片采集",
  84. success: (res) => {
  85. if (res.confirm) {
  86. common_vendor.index.openSetting();
  87. }
  88. }
  89. });
  90. }
  91. });
  92. },
  93. beforeDestroy() {
  94. this.stopMediaStream();
  95. },
  96. methods: {
  97. takePhoto() {
  98. if (this.isH5) {
  99. this.startCamera();
  100. } else {
  101. this.showCamera = true;
  102. this.cameraInitRetries = 0;
  103. this.$nextTick(() => {
  104. try {
  105. this.cameraContext = common_vendor.index.createCameraContext();
  106. console.log("相机上下文创建成功");
  107. } catch (err) {
  108. console.error("创建相机上下文失败:", err);
  109. this.handleCameraInitError();
  110. }
  111. });
  112. }
  113. },
  114. uploadPhoto(filePathOrData, type) {
  115. console.log(`准备上传${type}类型的手部照片`);
  116. common_vendor.index.showLoading({
  117. title: "上传中..."
  118. });
  119. const userInfo = common_vendor.index.getStorageSync("userInfo");
  120. userInfo ? JSON.parse(userInfo).openid || "" : "";
  121. const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
  122. if (!tenant_id) {
  123. common_vendor.index.hideLoading();
  124. common_vendor.index.showToast({
  125. title: "用户信息不完整,请重新登录",
  126. icon: "none"
  127. });
  128. return;
  129. }
  130. const photoDescriptions = {
  131. "left_palm": "展示完整的左手手掌",
  132. "left_back": "展示完整的左手手背",
  133. "left_fist": "展示完整的左手握拳",
  134. "right_palm": "展示完整的右手手掌",
  135. "right_back": "展示完整的右手手背",
  136. "right_fist": "展示完整的右手握拳"
  137. };
  138. const description = photoDescriptions[type] || `手部照片-${type}`;
  139. if (this.isH5 && filePathOrData.startsWith("data:image")) {
  140. const base64Data = filePathOrData.split(",")[1];
  141. const blob = this.base64ToBlob(base64Data, "image/jpeg");
  142. const formData = new FormData();
  143. formData.append("photo", blob, `${type}.jpg`);
  144. formData.append("application_id", common_vendor.index.getStorageSync("appId"));
  145. formData.append("tenant_id", tenant_id);
  146. formData.append("description", description);
  147. console.log("H5上传参数:", {
  148. application_id: common_vendor.index.getStorageSync("appId"),
  149. tenant_id,
  150. description
  151. });
  152. fetch(`${common_config.apiBaseUrl}/job/upload_posture_photo`, {
  153. method: "POST",
  154. body: formData
  155. }).then((response) => response.json()).then((result) => {
  156. console.log("照片上传成功:", result);
  157. const index = this.photoTypes.indexOf(type);
  158. if (index !== -1 && result.data && result.data.url) {
  159. this.$set(this.photoLinks, index, result.data.url);
  160. console.log(`已保存${type}类型照片链接:`, result.data.url);
  161. }
  162. common_vendor.index.hideLoading();
  163. common_vendor.index.showToast({
  164. title: "照片上传成功",
  165. icon: "success",
  166. duration: 1500,
  167. success: () => {
  168. setTimeout(() => {
  169. this.autoNextStep();
  170. }, 1500);
  171. }
  172. });
  173. }).catch((error) => {
  174. console.error("照片上传失败:", error);
  175. common_vendor.index.hideLoading();
  176. common_vendor.index.showToast({
  177. title: "照片上传失败,请重试",
  178. icon: "none"
  179. });
  180. });
  181. } else {
  182. console.log(`小程序环境上传文件路径:`, filePathOrData);
  183. console.log("小程序上传参数:", {
  184. application_id: common_vendor.index.getStorageSync("appId"),
  185. tenant_id,
  186. description
  187. });
  188. common_vendor.index.uploadFile({
  189. url: `${common_config.apiBaseUrl}/job/upload_posture_photo`,
  190. filePath: filePathOrData,
  191. name: "photo",
  192. // 确保文件参数名称正确
  193. formData: {
  194. // 使用formData替代data以确保参数正确传递
  195. "application_id": common_vendor.index.getStorageSync("appId"),
  196. "tenant_id": tenant_id,
  197. "description": description
  198. },
  199. success: (uploadRes) => {
  200. console.log("照片上传成功:", uploadRes);
  201. let result;
  202. try {
  203. if (typeof uploadRes.data === "string") {
  204. result = JSON.parse(uploadRes.data);
  205. } else {
  206. result = uploadRes.data;
  207. }
  208. console.log("解析后的上传结果:", result);
  209. const index = this.photoTypes.indexOf(type);
  210. if (index !== -1 && result.data && result.data.url) {
  211. this.$set(this.photoLinks, index, result.data.url);
  212. console.log(`已保存${type}类型照片链接:`, result.data.url);
  213. }
  214. } catch (e) {
  215. console.error("解析上传结果失败:", e);
  216. }
  217. common_vendor.index.hideLoading();
  218. common_vendor.index.showToast({
  219. title: "照片上传成功",
  220. icon: "success",
  221. duration: 1500,
  222. success: () => {
  223. setTimeout(() => {
  224. this.autoNextStep();
  225. }, 1500);
  226. }
  227. });
  228. },
  229. fail: (err) => {
  230. console.error("照片上传失败:", err);
  231. common_vendor.index.hideLoading();
  232. common_vendor.index.showToast({
  233. title: "照片上传失败,请重试",
  234. icon: "none"
  235. });
  236. },
  237. complete: () => {
  238. common_vendor.index.hideLoading();
  239. }
  240. });
  241. }
  242. },
  243. prevStep() {
  244. if (this.currentStep > 0) {
  245. this.currentStep--;
  246. }
  247. },
  248. nextStep() {
  249. if (this.currentStep < 5) {
  250. this.currentStep++;
  251. } else if (this.currentStep === 5 && this.photos[5]) {
  252. this.submitAllPhotos();
  253. }
  254. },
  255. previewPhoto(index) {
  256. if (this.photos[index]) {
  257. common_vendor.index.previewImage({
  258. urls: [this.photos[index]],
  259. current: this.photos[index]
  260. });
  261. }
  262. },
  263. submitAllPhotos() {
  264. if (this.photos.every((photo) => photo !== null)) {
  265. common_vendor.index.showLoading({
  266. title: "处理中..."
  267. });
  268. setTimeout(() => {
  269. common_vendor.index.hideLoading();
  270. common_vendor.index.showToast({
  271. title: "手部照片采集完成",
  272. icon: "success",
  273. duration: 2e3,
  274. success: () => {
  275. setTimeout(() => {
  276. common_vendor.index.redirectTo({
  277. url: "/pages/interview_success/interview_success"
  278. });
  279. }, 100);
  280. }
  281. });
  282. }, 500);
  283. } else {
  284. common_vendor.index.showToast({
  285. title: "请完成所有照片拍摄",
  286. icon: "none"
  287. });
  288. }
  289. },
  290. // H5环境下启动摄像头
  291. startCamera() {
  292. this.showCamera = true;
  293. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  294. const facingMode = this.cameraPosition === "front" ? "user" : "environment";
  295. navigator.mediaDevices.getUserMedia({
  296. video: { facingMode }
  297. }).then((stream) => {
  298. this.mediaStream = stream;
  299. this.$nextTick(() => {
  300. const videoElement = this.$refs.videoElement;
  301. if (videoElement) {
  302. videoElement.srcObject = stream;
  303. }
  304. });
  305. }).catch((error) => {
  306. console.error("获取摄像头失败:", error);
  307. common_vendor.index.showToast({
  308. title: "无法访问摄像头,请检查权限设置",
  309. icon: "none"
  310. });
  311. this.showCamera = false;
  312. });
  313. } else {
  314. common_vendor.index.showToast({
  315. title: "您的浏览器不支持摄像头功能",
  316. icon: "none"
  317. });
  318. this.showCamera = false;
  319. }
  320. },
  321. // H5环境下拍照
  322. capturePhoto() {
  323. console.log("执行H5拍照方法");
  324. const videoElement = this.$refs.videoElement;
  325. const canvasElement = this.$refs.canvasElement;
  326. if (videoElement && canvasElement) {
  327. try {
  328. const context = canvasElement.getContext("2d");
  329. canvasElement.width = videoElement.videoWidth;
  330. canvasElement.height = videoElement.videoHeight;
  331. context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
  332. const photoData = canvasElement.toDataURL("image/jpeg");
  333. this.$set(this.photos, this.currentStep, photoData);
  334. this.uploadPhoto(photoData, this.photoTypes[this.currentStep]);
  335. this.stopMediaStream();
  336. this.showCamera = false;
  337. } catch (err) {
  338. console.error("H5拍照失败:", err);
  339. common_vendor.index.showToast({
  340. title: "拍照失败,请重试",
  341. icon: "none"
  342. });
  343. }
  344. }
  345. },
  346. // 小程序环境下拍照
  347. capturePhotoMP() {
  348. console.log("执行小程序拍照方法");
  349. if (!this.cameraContext) {
  350. console.error("相机上下文不存在");
  351. this.handleCameraInitError();
  352. return;
  353. }
  354. try {
  355. common_vendor.index.showLoading({
  356. title: "拍照中...",
  357. mask: true
  358. });
  359. this.cameraContext.takePhoto({
  360. quality: "high",
  361. success: (res) => {
  362. console.log("拍照成功:", res);
  363. common_vendor.index.hideLoading();
  364. this.$set(this.photos, this.currentStep, res.tempImagePath);
  365. this.uploadPhoto(res.tempImagePath, this.photoTypes[this.currentStep]);
  366. this.showCamera = false;
  367. },
  368. fail: (err) => {
  369. common_vendor.index.hideLoading();
  370. console.error("拍照失败:", err);
  371. common_vendor.index.showToast({
  372. title: "拍照失败,请重试",
  373. icon: "none"
  374. });
  375. this.fallbackToChooseImage();
  376. }
  377. });
  378. } catch (err) {
  379. common_vendor.index.hideLoading();
  380. console.error("takePhoto方法执行失败:", err);
  381. this.fallbackToChooseImage();
  382. }
  383. },
  384. // 添加相机初始化错误处理
  385. handleCameraInitError() {
  386. if (this.cameraInitRetries < 3) {
  387. this.cameraInitRetries++;
  388. common_vendor.index.showToast({
  389. title: `相机初始化失败,正在重试(${this.cameraInitRetries}/3)`,
  390. icon: "none"
  391. });
  392. setTimeout(() => {
  393. this.showCamera = false;
  394. setTimeout(() => {
  395. this.takePhoto();
  396. }, 500);
  397. }, 1e3);
  398. } else {
  399. common_vendor.index.showToast({
  400. title: "相机初始化失败,将使用系统相机",
  401. icon: "none"
  402. });
  403. this.fallbackToChooseImage();
  404. }
  405. },
  406. // 添加备用的系统相机方法
  407. fallbackToChooseImage() {
  408. this.showCamera = false;
  409. common_vendor.index.chooseImage({
  410. count: 1,
  411. sourceType: ["camera"],
  412. success: (res) => {
  413. this.$set(this.photos, this.currentStep, res.tempFilePaths[0]);
  414. this.uploadPhoto(res.tempFilePaths[0], this.photoTypes[this.currentStep]);
  415. },
  416. fail: (err) => {
  417. console.error("选择图片失败:", err);
  418. common_vendor.index.showToast({
  419. title: "拍照失败,请重试",
  420. icon: "none"
  421. });
  422. }
  423. });
  424. },
  425. // 修改相机错误处理方法
  426. handleCameraError(e) {
  427. console.error("相机错误:", e.detail);
  428. const errorInfo = JSON.stringify(e.detail);
  429. console.log("详细错误信息:", errorInfo);
  430. common_vendor.index.showToast({
  431. title: "相机启动失败,将使用系统相机",
  432. icon: "none"
  433. });
  434. this.fallbackToChooseImage();
  435. },
  436. // 取消拍照 - 更新为同时支持H5和小程序
  437. cancelCamera() {
  438. if (this.isH5) {
  439. this.stopMediaStream();
  440. }
  441. this.showCamera = false;
  442. },
  443. // 停止摄像头流
  444. stopMediaStream() {
  445. if (this.mediaStream) {
  446. this.mediaStream.getTracks().forEach((track) => {
  447. track.stop();
  448. });
  449. this.mediaStream = null;
  450. }
  451. },
  452. // base64转blob工具方法
  453. base64ToBlob(base64, mimeType) {
  454. const byteCharacters = atob(base64);
  455. const byteArrays = [];
  456. for (let offset = 0; offset < byteCharacters.length; offset += 512) {
  457. const slice = byteCharacters.slice(offset, offset + 512);
  458. const byteNumbers = new Array(slice.length);
  459. for (let i = 0; i < slice.length; i++) {
  460. byteNumbers[i] = slice.charCodeAt(i);
  461. }
  462. const byteArray = new Uint8Array(byteNumbers);
  463. byteArrays.push(byteArray);
  464. }
  465. return new Blob(byteArrays, { type: mimeType });
  466. },
  467. retryCamera() {
  468. this.showCamera = false;
  469. setTimeout(() => {
  470. this.takePhoto();
  471. }, 500);
  472. },
  473. // 可以添加一个方法来切换蒙层显示
  474. toggleGestureOverlay() {
  475. },
  476. // 切换摄像头
  477. switchCamera() {
  478. this.cameraPosition = this.cameraPosition === "front" ? "back" : "front";
  479. if (this.isH5 && this.showCamera) {
  480. this.stopMediaStream();
  481. this.startCamera();
  482. }
  483. },
  484. // 添加自动进入下一步的方法
  485. autoNextStep() {
  486. if (this.currentStep < 5) {
  487. this.currentStep++;
  488. if (this.currentStep < 6) {
  489. this.takePhoto();
  490. }
  491. } else if (this.currentStep === 5 && this.photos[5]) {
  492. this.submitAllPhotos();
  493. }
  494. }
  495. }
  496. };
  497. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  498. return common_vendor.e({
  499. a: common_vendor.t($data.instructions[$data.currentStep]),
  500. b: $data.photos[$data.currentStep]
  501. }, $data.photos[$data.currentStep] ? {
  502. c: $data.photos[$data.currentStep]
  503. } : {
  504. d: common_vendor.t($data.previewTexts[$data.currentStep])
  505. }, {
  506. e: common_vendor.f($data.photos, (photo, index, i0) => {
  507. return common_vendor.e({
  508. a: photo
  509. }, photo ? {
  510. b: photo,
  511. c: common_vendor.o(($event) => $options.previewPhoto(index), index)
  512. } : {
  513. d: common_vendor.t(index + 1)
  514. }, {
  515. e: index,
  516. f: $data.currentStep === index ? 1 : "",
  517. g: photo ? 1 : ""
  518. });
  519. }),
  520. f: common_vendor.t($data.photos[$data.currentStep] ? "重新拍摄" : "拍摄照片"),
  521. g: common_vendor.o((...args) => $options.takePhoto && $options.takePhoto(...args)),
  522. h: $data.currentStep === 0,
  523. i: common_vendor.o((...args) => $options.prevStep && $options.prevStep(...args)),
  524. j: common_vendor.t($data.currentStep === 5 && $data.photos[5] ? "完成" : "下一步"),
  525. k: !$options.canGoNext,
  526. l: common_vendor.o((...args) => $options.nextStep && $options.nextStep(...args)),
  527. m: !$data.isH5 && $data.showCamera
  528. }, !$data.isH5 && $data.showCamera ? {
  529. n: common_vendor.t($data.gestureHints[$data.currentStep]),
  530. o: $data.cameraPosition,
  531. p: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args)),
  532. q: $data.cameraMode,
  533. r: $data.guideIcon[$data.currentStep],
  534. s: common_vendor.s($data.currentStep >= 3 ? "left:30rpx" : "right:30rpx"),
  535. t: $data.gestureOverlays[$data.currentStep],
  536. v: common_vendor.o((...args) => $options.capturePhotoMP && $options.capturePhotoMP(...args)),
  537. w: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args))
  538. } : {}, {
  539. x: $data.isH5
  540. }, $data.isH5 ? {
  541. y: $data.guideIcon[$data.currentStep],
  542. z: common_vendor.s($data.currentStep >= 3 ? "left:30rpx" : "right:30rpx"),
  543. A: $data.gestureOverlays[$data.currentStep],
  544. B: common_vendor.t($data.gestureHints[$data.currentStep]),
  545. C: common_vendor.o((...args) => $options.capturePhoto && $options.capturePhoto(...args)),
  546. D: common_vendor.o((...args) => $options.switchCamera && $options.switchCamera(...args)),
  547. E: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args)),
  548. F: $data.showCamera
  549. } : {});
  550. }
  551. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
  552. wx.createPage(MiniProgramPage);