interview.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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://121.36.251.245:9000/minlong/%E5%B7%A6%E6%89%8B%E6%89%8B%E6%8E%8C.png",
  38. "http://121.36.251.245:9000/minlong/%E5%B7%A6%E6%89%8B%E6%89%8B%E8%83%8C.png",
  39. "http://121.36.251.245:9000/minlong/%E5%B7%A6%E6%89%8B%E6%8F%A1%E6%8B%B3.png",
  40. "http://121.36.251.245:9000/minlong/%E5%8F%B3%E6%89%8B%E6%89%8B%E6%8E%8C.png",
  41. "http://121.36.251.245:9000/minlong/%E5%8F%B3%E6%89%8B%E6%89%8B%E8%83%8C.png",
  42. "http://121.36.251.245:9000/minlong/%E5%8F%B3%E6%89%8B%E6%8F%A1%E6%8B%B3.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://121.36.251.245:9000/minlong/01%E5%B7%A6%E6%89%8B%E6%89%8B%E6%8E%8C.mp4",
  61. "http://121.36.251.245:9000/minlong/02%E5%B7%A6%E6%89%8B%E6%89%8B%E8%83%8C.mp4",
  62. "http://121.36.251.245:9000/minlong/03%E5%B7%A6%E6%89%8B%E6%8F%A1%E6%8B%B3.mp4",
  63. "http://121.36.251.245:9000/minlong/04%E5%8F%B3%E6%89%8B%E6%89%8B%E6%8E%8C.mp4",
  64. "http://121.36.251.245:9000/minlong/05%E5%8F%B3%E6%89%8B%E6%89%8B%E8%83%8C.mp4",
  65. "http://121.36.251.245:9000/minlong/06%E5%8F%B3%E6%89%8B%E6%8F%A1%E6%8B%B3.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. if (this.isH5 && filePathOrData.startsWith("data:image")) {
  131. const base64Data = filePathOrData.split(",")[1];
  132. const blob = this.base64ToBlob(base64Data, "image/jpeg");
  133. const formData = new FormData();
  134. formData.append("photo", blob, `${type}.jpg`);
  135. formData.append("application_id", common_vendor.index.getStorageSync("appId"));
  136. formData.append("tenant_id", tenant_id);
  137. formData.append("description", `手部照片-${type}`);
  138. console.log("H5上传参数:", {
  139. application_id: common_vendor.index.getStorageSync("appId"),
  140. tenant_id,
  141. description: `手部照片-${type}`
  142. });
  143. fetch(`${common_config.apiBaseUrl}/job/upload_posture_photo`, {
  144. method: "POST",
  145. body: formData
  146. }).then((response) => response.json()).then((result) => {
  147. console.log("照片上传成功:", result);
  148. const index = this.photoTypes.indexOf(type);
  149. if (index !== -1 && result.data && result.data.url) {
  150. this.$set(this.photoLinks, index, result.data.url);
  151. console.log(`已保存${type}类型照片链接:`, result.data.url);
  152. }
  153. common_vendor.index.hideLoading();
  154. common_vendor.index.showToast({
  155. title: "照片上传成功",
  156. icon: "success",
  157. duration: 1500,
  158. success: () => {
  159. setTimeout(() => {
  160. this.autoNextStep();
  161. }, 1500);
  162. }
  163. });
  164. }).catch((error) => {
  165. console.error("照片上传失败:", error);
  166. common_vendor.index.hideLoading();
  167. common_vendor.index.showToast({
  168. title: "照片上传失败,请重试",
  169. icon: "none"
  170. });
  171. });
  172. } else {
  173. console.log(`小程序环境上传文件路径:`, filePathOrData);
  174. console.log("小程序上传参数:", {
  175. application_id: common_vendor.index.getStorageSync("appId"),
  176. tenant_id,
  177. description: `手部照片-${type}`
  178. });
  179. common_vendor.index.uploadFile({
  180. url: `${common_config.apiBaseUrl}/job/upload_posture_photo`,
  181. filePath: filePathOrData,
  182. name: "photo",
  183. // 确保文件参数名称正确
  184. formData: {
  185. // 使用formData替代data以确保参数正确传递
  186. "application_id": common_vendor.index.getStorageSync("appId"),
  187. "tenant_id": tenant_id,
  188. "description": `手部照片-${type}`
  189. },
  190. success: (uploadRes) => {
  191. console.log("照片上传成功:", uploadRes);
  192. let result;
  193. try {
  194. if (typeof uploadRes.data === "string") {
  195. result = JSON.parse(uploadRes.data);
  196. } else {
  197. result = uploadRes.data;
  198. }
  199. console.log("解析后的上传结果:", result);
  200. const index = this.photoTypes.indexOf(type);
  201. if (index !== -1 && result.data && result.data.url) {
  202. this.$set(this.photoLinks, index, result.data.url);
  203. console.log(`已保存${type}类型照片链接:`, result.data.url);
  204. }
  205. } catch (e) {
  206. console.error("解析上传结果失败:", e);
  207. }
  208. common_vendor.index.hideLoading();
  209. common_vendor.index.showToast({
  210. title: "照片上传成功",
  211. icon: "success",
  212. duration: 1500,
  213. success: () => {
  214. setTimeout(() => {
  215. this.autoNextStep();
  216. }, 1500);
  217. }
  218. });
  219. },
  220. fail: (err) => {
  221. console.error("照片上传失败:", err);
  222. common_vendor.index.hideLoading();
  223. common_vendor.index.showToast({
  224. title: "照片上传失败,请重试",
  225. icon: "none"
  226. });
  227. },
  228. complete: () => {
  229. common_vendor.index.hideLoading();
  230. }
  231. });
  232. }
  233. },
  234. prevStep() {
  235. if (this.currentStep > 0) {
  236. this.currentStep--;
  237. }
  238. },
  239. nextStep() {
  240. if (this.currentStep < 5) {
  241. this.currentStep++;
  242. } else if (this.currentStep === 5 && this.photos[5]) {
  243. this.submitAllPhotos();
  244. }
  245. },
  246. previewPhoto(index) {
  247. if (this.photos[index]) {
  248. common_vendor.index.previewImage({
  249. urls: [this.photos[index]],
  250. current: this.photos[index]
  251. });
  252. }
  253. },
  254. submitAllPhotos() {
  255. if (this.photos.every((photo) => photo !== null)) {
  256. common_vendor.index.showLoading({
  257. title: "处理中..."
  258. });
  259. setTimeout(() => {
  260. common_vendor.index.hideLoading();
  261. common_vendor.index.showToast({
  262. title: "手部照片采集完成",
  263. icon: "success",
  264. duration: 2e3,
  265. success: () => {
  266. setTimeout(() => {
  267. common_vendor.index.navigateTo({
  268. url: "/pages/interview_success/interview_success"
  269. });
  270. }, 2e3);
  271. }
  272. });
  273. }, 500);
  274. } else {
  275. common_vendor.index.showToast({
  276. title: "请完成所有照片拍摄",
  277. icon: "none"
  278. });
  279. }
  280. },
  281. // H5环境下启动摄像头
  282. startCamera() {
  283. this.showCamera = true;
  284. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  285. const facingMode = this.cameraPosition === "front" ? "user" : "environment";
  286. navigator.mediaDevices.getUserMedia({
  287. video: { facingMode }
  288. }).then((stream) => {
  289. this.mediaStream = stream;
  290. this.$nextTick(() => {
  291. const videoElement = this.$refs.videoElement;
  292. if (videoElement) {
  293. videoElement.srcObject = stream;
  294. }
  295. });
  296. }).catch((error) => {
  297. console.error("获取摄像头失败:", error);
  298. common_vendor.index.showToast({
  299. title: "无法访问摄像头,请检查权限设置",
  300. icon: "none"
  301. });
  302. this.showCamera = false;
  303. });
  304. } else {
  305. common_vendor.index.showToast({
  306. title: "您的浏览器不支持摄像头功能",
  307. icon: "none"
  308. });
  309. this.showCamera = false;
  310. }
  311. },
  312. // H5环境下拍照
  313. capturePhoto() {
  314. console.log("执行H5拍照方法");
  315. const videoElement = this.$refs.videoElement;
  316. const canvasElement = this.$refs.canvasElement;
  317. if (videoElement && canvasElement) {
  318. try {
  319. const context = canvasElement.getContext("2d");
  320. canvasElement.width = videoElement.videoWidth;
  321. canvasElement.height = videoElement.videoHeight;
  322. context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
  323. const photoData = canvasElement.toDataURL("image/jpeg");
  324. this.$set(this.photos, this.currentStep, photoData);
  325. this.uploadPhoto(photoData, this.photoTypes[this.currentStep]);
  326. this.stopMediaStream();
  327. this.showCamera = false;
  328. } catch (err) {
  329. console.error("H5拍照失败:", err);
  330. common_vendor.index.showToast({
  331. title: "拍照失败,请重试",
  332. icon: "none"
  333. });
  334. }
  335. }
  336. },
  337. // 小程序环境下拍照
  338. capturePhotoMP() {
  339. console.log("执行小程序拍照方法");
  340. if (!this.cameraContext) {
  341. console.error("相机上下文不存在");
  342. this.handleCameraInitError();
  343. return;
  344. }
  345. try {
  346. common_vendor.index.showLoading({
  347. title: "拍照中...",
  348. mask: true
  349. });
  350. this.cameraContext.takePhoto({
  351. quality: "high",
  352. success: (res) => {
  353. console.log("拍照成功:", res);
  354. common_vendor.index.hideLoading();
  355. this.$set(this.photos, this.currentStep, res.tempImagePath);
  356. this.uploadPhoto(res.tempImagePath, this.photoTypes[this.currentStep]);
  357. this.showCamera = false;
  358. },
  359. fail: (err) => {
  360. common_vendor.index.hideLoading();
  361. console.error("拍照失败:", err);
  362. common_vendor.index.showToast({
  363. title: "拍照失败,请重试",
  364. icon: "none"
  365. });
  366. this.fallbackToChooseImage();
  367. }
  368. });
  369. } catch (err) {
  370. common_vendor.index.hideLoading();
  371. console.error("takePhoto方法执行失败:", err);
  372. this.fallbackToChooseImage();
  373. }
  374. },
  375. // 添加相机初始化错误处理
  376. handleCameraInitError() {
  377. if (this.cameraInitRetries < 3) {
  378. this.cameraInitRetries++;
  379. common_vendor.index.showToast({
  380. title: `相机初始化失败,正在重试(${this.cameraInitRetries}/3)`,
  381. icon: "none"
  382. });
  383. setTimeout(() => {
  384. this.showCamera = false;
  385. setTimeout(() => {
  386. this.takePhoto();
  387. }, 500);
  388. }, 1e3);
  389. } else {
  390. common_vendor.index.showToast({
  391. title: "相机初始化失败,将使用系统相机",
  392. icon: "none"
  393. });
  394. this.fallbackToChooseImage();
  395. }
  396. },
  397. // 添加备用的系统相机方法
  398. fallbackToChooseImage() {
  399. this.showCamera = false;
  400. common_vendor.index.chooseImage({
  401. count: 1,
  402. sourceType: ["camera"],
  403. success: (res) => {
  404. this.$set(this.photos, this.currentStep, res.tempFilePaths[0]);
  405. this.uploadPhoto(res.tempFilePaths[0], this.photoTypes[this.currentStep]);
  406. },
  407. fail: (err) => {
  408. console.error("选择图片失败:", err);
  409. common_vendor.index.showToast({
  410. title: "拍照失败,请重试",
  411. icon: "none"
  412. });
  413. }
  414. });
  415. },
  416. // 修改相机错误处理方法
  417. handleCameraError(e) {
  418. console.error("相机错误:", e.detail);
  419. const errorInfo = JSON.stringify(e.detail);
  420. console.log("详细错误信息:", errorInfo);
  421. common_vendor.index.showToast({
  422. title: "相机启动失败,将使用系统相机",
  423. icon: "none"
  424. });
  425. this.fallbackToChooseImage();
  426. },
  427. // 取消拍照 - 更新为同时支持H5和小程序
  428. cancelCamera() {
  429. if (this.isH5) {
  430. this.stopMediaStream();
  431. }
  432. this.showCamera = false;
  433. },
  434. // 停止摄像头流
  435. stopMediaStream() {
  436. if (this.mediaStream) {
  437. this.mediaStream.getTracks().forEach((track) => {
  438. track.stop();
  439. });
  440. this.mediaStream = null;
  441. }
  442. },
  443. // base64转blob工具方法
  444. base64ToBlob(base64, mimeType) {
  445. const byteCharacters = atob(base64);
  446. const byteArrays = [];
  447. for (let offset = 0; offset < byteCharacters.length; offset += 512) {
  448. const slice = byteCharacters.slice(offset, offset + 512);
  449. const byteNumbers = new Array(slice.length);
  450. for (let i = 0; i < slice.length; i++) {
  451. byteNumbers[i] = slice.charCodeAt(i);
  452. }
  453. const byteArray = new Uint8Array(byteNumbers);
  454. byteArrays.push(byteArray);
  455. }
  456. return new Blob(byteArrays, { type: mimeType });
  457. },
  458. retryCamera() {
  459. this.showCamera = false;
  460. setTimeout(() => {
  461. this.takePhoto();
  462. }, 500);
  463. },
  464. // 可以添加一个方法来切换蒙层显示
  465. toggleGestureOverlay() {
  466. },
  467. // 切换摄像头
  468. switchCamera() {
  469. this.cameraPosition = this.cameraPosition === "front" ? "back" : "front";
  470. if (this.isH5 && this.showCamera) {
  471. this.stopMediaStream();
  472. this.startCamera();
  473. }
  474. },
  475. // 添加自动进入下一步的方法
  476. autoNextStep() {
  477. if (this.currentStep < 5) {
  478. this.currentStep++;
  479. if (this.currentStep < 6) {
  480. this.takePhoto();
  481. }
  482. } else if (this.currentStep === 5 && this.photos[5]) {
  483. this.submitAllPhotos();
  484. }
  485. }
  486. }
  487. };
  488. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  489. return common_vendor.e({
  490. a: common_vendor.t($data.instructions[$data.currentStep]),
  491. b: $data.photos[$data.currentStep]
  492. }, $data.photos[$data.currentStep] ? {
  493. c: $data.photos[$data.currentStep]
  494. } : {
  495. d: common_vendor.t($data.previewTexts[$data.currentStep])
  496. }, {
  497. e: common_vendor.f($data.photos, (photo, index, i0) => {
  498. return common_vendor.e({
  499. a: photo
  500. }, photo ? {
  501. b: photo,
  502. c: common_vendor.o(($event) => $options.previewPhoto(index), index)
  503. } : {
  504. d: common_vendor.t(index + 1)
  505. }, {
  506. e: index,
  507. f: $data.currentStep === index ? 1 : "",
  508. g: photo ? 1 : ""
  509. });
  510. }),
  511. f: common_vendor.t($data.photos[$data.currentStep] ? "重新拍摄" : "拍摄照片"),
  512. g: common_vendor.o((...args) => $options.takePhoto && $options.takePhoto(...args)),
  513. h: $data.currentStep === 0,
  514. i: common_vendor.o((...args) => $options.prevStep && $options.prevStep(...args)),
  515. j: common_vendor.t($data.currentStep === 5 && $data.photos[5] ? "完成" : "下一步"),
  516. k: !$options.canGoNext,
  517. l: common_vendor.o((...args) => $options.nextStep && $options.nextStep(...args)),
  518. m: !$data.isH5 && $data.showCamera
  519. }, !$data.isH5 && $data.showCamera ? {
  520. n: common_vendor.t($data.gestureHints[$data.currentStep]),
  521. o: $data.cameraPosition,
  522. p: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args)),
  523. q: $data.cameraMode,
  524. r: $data.guideIcon[$data.currentStep],
  525. s: common_vendor.s($data.currentStep >= 3 ? "left:30rpx" : "right:30rpx"),
  526. t: $data.gestureOverlays[$data.currentStep],
  527. v: common_vendor.o((...args) => $options.capturePhotoMP && $options.capturePhotoMP(...args)),
  528. w: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args))
  529. } : {}, {
  530. x: $data.isH5
  531. }, $data.isH5 ? {
  532. y: $data.guideIcon[$data.currentStep],
  533. z: common_vendor.s($data.currentStep >= 3 ? "left:30rpx" : "right:30rpx"),
  534. A: $data.gestureOverlays[$data.currentStep],
  535. B: common_vendor.t($data.gestureHints[$data.currentStep]),
  536. C: common_vendor.o((...args) => $options.capturePhoto && $options.capturePhoto(...args)),
  537. D: common_vendor.o((...args) => $options.switchCamera && $options.switchCamera(...args)),
  538. E: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args)),
  539. F: $data.showCamera
  540. } : {});
  541. }
  542. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
  543. wx.createPage(MiniProgramPage);