interview.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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. /* '/static/images/palm_overlay.png', // 手掌正面引导图
  38. '/static/images/back_overlay.png', // 手掌反面引导图
  39. '/static/images/fist_overlay.png' // 握拳引导图 */
  40. ],
  41. gestureHints: [
  42. "请将左手掌对准轮廓线",
  43. "请将左手背对准轮廓线",
  44. "请将左手拳头对准轮廓线",
  45. "请将右手掌对准轮廓线",
  46. "请将右手背对准轮廓线",
  47. "请将右手拳头对准轮廓线"
  48. ],
  49. photoLinks: [null, null, null, null, null, null]
  50. // 存储上传后的图片链接
  51. };
  52. },
  53. computed: {
  54. canGoNext() {
  55. return this.photos[this.currentStep] !== null;
  56. }
  57. },
  58. onLoad() {
  59. common_vendor.index.authorize({
  60. scope: "scope.camera",
  61. success: () => {
  62. console.log("相机权限已授权");
  63. },
  64. fail: () => {
  65. common_vendor.index.showModal({
  66. title: "提示",
  67. content: "请授权相机权限以完成手部照片采集",
  68. success: (res) => {
  69. if (res.confirm) {
  70. common_vendor.index.openSetting();
  71. }
  72. }
  73. });
  74. }
  75. });
  76. },
  77. beforeDestroy() {
  78. this.stopMediaStream();
  79. },
  80. methods: {
  81. takePhoto() {
  82. if (this.isH5) {
  83. this.startCamera();
  84. } else {
  85. this.showCamera = true;
  86. this.cameraInitRetries = 0;
  87. this.$nextTick(() => {
  88. try {
  89. this.cameraContext = common_vendor.index.createCameraContext();
  90. console.log("相机上下文创建成功");
  91. } catch (err) {
  92. console.error("创建相机上下文失败:", err);
  93. this.handleCameraInitError();
  94. }
  95. });
  96. }
  97. },
  98. uploadPhoto(filePathOrData, type) {
  99. console.log(`准备上传${type}类型的手部照片`);
  100. common_vendor.index.showLoading({
  101. title: "上传中..."
  102. });
  103. const userInfo = common_vendor.index.getStorageSync("userInfo");
  104. userInfo ? JSON.parse(userInfo).openid || "" : "";
  105. const tenant_id = common_vendor.index.getStorageSync("tenant_id") || "1";
  106. if (!tenant_id) {
  107. common_vendor.index.hideLoading();
  108. common_vendor.index.showToast({
  109. title: "用户信息不完整,请重新登录",
  110. icon: "none"
  111. });
  112. return;
  113. }
  114. if (this.isH5 && filePathOrData.startsWith("data:image")) {
  115. const base64Data = filePathOrData.split(",")[1];
  116. const blob = this.base64ToBlob(base64Data, "image/jpeg");
  117. const formData = new FormData();
  118. formData.append("photo_file", blob, `${type}.jpg`);
  119. formData.append("application_id", "1");
  120. formData.append("tenant_id", tenant_id);
  121. formData.append("description", `手部照片-${type}`);
  122. console.log("H5上传参数:", {
  123. application_id: "1",
  124. tenant_id,
  125. description: `手部照片-${type}`
  126. });
  127. fetch(`${common_config.apiBaseUrl}/job/upload_posture_photo`, {
  128. method: "POST",
  129. body: formData
  130. }).then((response) => response.json()).then((result) => {
  131. console.log("照片上传成功:", result);
  132. const index = this.photoTypes.indexOf(type);
  133. if (index !== -1 && result.data && result.data.url) {
  134. this.$set(this.photoLinks, index, result.data.url);
  135. console.log(`已保存${type}类型照片链接:`, result.data.url);
  136. }
  137. common_vendor.index.hideLoading();
  138. common_vendor.index.showToast({
  139. title: "照片上传成功",
  140. icon: "success"
  141. });
  142. }).catch((error) => {
  143. console.error("照片上传失败:", error);
  144. common_vendor.index.hideLoading();
  145. common_vendor.index.showToast({
  146. title: "照片上传失败,请重试",
  147. icon: "none"
  148. });
  149. });
  150. } else {
  151. console.log(`小程序环境上传文件路径:`, filePathOrData);
  152. console.log("小程序上传参数:", {
  153. application_id: "1",
  154. tenant_id,
  155. description: `手部照片-${type}`
  156. });
  157. common_vendor.index.uploadFile({
  158. url: `${common_config.apiBaseUrl}/job/upload_posture_photo`,
  159. filePath: filePathOrData,
  160. name: "photo_file",
  161. // 确保文件参数名称正确
  162. formData: {
  163. // 使用formData替代data以确保参数正确传递
  164. "application_id": "1",
  165. "tenant_id": tenant_id,
  166. "description": `手部照片-${type}`
  167. },
  168. success: (uploadRes) => {
  169. console.log("照片上传成功:", uploadRes);
  170. let result;
  171. try {
  172. if (typeof uploadRes.data === "string") {
  173. result = JSON.parse(uploadRes.data);
  174. } else {
  175. result = uploadRes.data;
  176. }
  177. console.log("解析后的上传结果:", result);
  178. const index = this.photoTypes.indexOf(type);
  179. if (index !== -1 && result.data && result.data.url) {
  180. this.$set(this.photoLinks, index, result.data.url);
  181. console.log(`已保存${type}类型照片链接:`, result.data.url);
  182. }
  183. } catch (e) {
  184. console.error("解析上传结果失败:", e);
  185. }
  186. common_vendor.index.hideLoading();
  187. common_vendor.index.showToast({
  188. title: "照片上传成功",
  189. icon: "success"
  190. });
  191. },
  192. fail: (err) => {
  193. console.error("照片上传失败:", err);
  194. common_vendor.index.hideLoading();
  195. common_vendor.index.showToast({
  196. title: "照片上传失败,请重试",
  197. icon: "none"
  198. });
  199. },
  200. complete: () => {
  201. common_vendor.index.hideLoading();
  202. }
  203. });
  204. }
  205. },
  206. prevStep() {
  207. if (this.currentStep > 0) {
  208. this.currentStep--;
  209. }
  210. },
  211. nextStep() {
  212. if (this.currentStep < 5) {
  213. this.currentStep++;
  214. } else if (this.currentStep === 5 && this.photos[5]) {
  215. this.submitAllPhotos();
  216. }
  217. },
  218. previewPhoto(index) {
  219. if (this.photos[index]) {
  220. common_vendor.index.previewImage({
  221. urls: [this.photos[index]],
  222. current: this.photos[index]
  223. });
  224. }
  225. },
  226. submitAllPhotos() {
  227. if (this.photos.every((photo) => photo !== null)) {
  228. common_vendor.index.showLoading({
  229. title: "处理中..."
  230. });
  231. setTimeout(() => {
  232. common_vendor.index.hideLoading();
  233. common_vendor.index.showToast({
  234. title: "手部照片采集完成",
  235. icon: "success",
  236. duration: 2e3,
  237. success: () => {
  238. setTimeout(() => {
  239. common_vendor.index.navigateTo({
  240. url: "/pages/ResumeEvaluation/ResumeEvaluation"
  241. });
  242. }, 2e3);
  243. }
  244. });
  245. }, 500);
  246. } else {
  247. common_vendor.index.showToast({
  248. title: "请完成所有照片拍摄",
  249. icon: "none"
  250. });
  251. }
  252. },
  253. // H5环境下启动摄像头
  254. startCamera() {
  255. this.showCamera = true;
  256. if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
  257. navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then((stream) => {
  258. this.mediaStream = stream;
  259. this.$nextTick(() => {
  260. const videoElement = this.$refs.videoElement;
  261. if (videoElement) {
  262. videoElement.srcObject = stream;
  263. }
  264. });
  265. }).catch((error) => {
  266. console.error("获取摄像头失败:", error);
  267. common_vendor.index.showToast({
  268. title: "无法访问摄像头,请检查权限设置",
  269. icon: "none"
  270. });
  271. this.showCamera = false;
  272. });
  273. } else {
  274. common_vendor.index.showToast({
  275. title: "您的浏览器不支持摄像头功能",
  276. icon: "none"
  277. });
  278. this.showCamera = false;
  279. }
  280. },
  281. // H5环境下拍照
  282. capturePhoto() {
  283. const videoElement = this.$refs.videoElement;
  284. const canvasElement = this.$refs.canvasElement;
  285. if (videoElement && canvasElement) {
  286. const context = canvasElement.getContext("2d");
  287. canvasElement.width = videoElement.videoWidth;
  288. canvasElement.height = videoElement.videoHeight;
  289. context.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
  290. const photoData = canvasElement.toDataURL("image/jpeg");
  291. this.$set(this.photos, this.currentStep, photoData);
  292. this.uploadPhoto(photoData, this.photoTypes[this.currentStep]);
  293. this.stopMediaStream();
  294. this.showCamera = false;
  295. }
  296. },
  297. // 小程序环境下拍照
  298. capturePhotoMP() {
  299. if (!this.cameraContext) {
  300. console.error("相机上下文不存在");
  301. this.handleCameraInitError();
  302. return;
  303. }
  304. try {
  305. this.cameraContext.takePhoto({
  306. quality: "high",
  307. success: (res) => {
  308. console.log("拍照成功:", res);
  309. this.$set(this.photos, this.currentStep, res.tempImagePath);
  310. this.uploadPhoto(res.tempImagePath, this.photoTypes[this.currentStep]);
  311. this.showCamera = false;
  312. },
  313. fail: (err) => {
  314. console.error("拍照失败:", err);
  315. common_vendor.index.showToast({
  316. title: "拍照失败,请重试",
  317. icon: "none"
  318. });
  319. this.fallbackToChooseImage();
  320. }
  321. });
  322. } catch (err) {
  323. console.error("takePhoto方法执行失败:", err);
  324. this.fallbackToChooseImage();
  325. }
  326. },
  327. // 添加相机初始化错误处理
  328. handleCameraInitError() {
  329. if (this.cameraInitRetries < 3) {
  330. this.cameraInitRetries++;
  331. common_vendor.index.showToast({
  332. title: `相机初始化失败,正在重试(${this.cameraInitRetries}/3)`,
  333. icon: "none"
  334. });
  335. setTimeout(() => {
  336. this.showCamera = false;
  337. setTimeout(() => {
  338. this.takePhoto();
  339. }, 500);
  340. }, 1e3);
  341. } else {
  342. common_vendor.index.showToast({
  343. title: "相机初始化失败,将使用系统相机",
  344. icon: "none"
  345. });
  346. this.fallbackToChooseImage();
  347. }
  348. },
  349. // 添加备用的系统相机方法
  350. fallbackToChooseImage() {
  351. this.showCamera = false;
  352. common_vendor.index.chooseImage({
  353. count: 1,
  354. sourceType: ["camera"],
  355. success: (res) => {
  356. this.$set(this.photos, this.currentStep, res.tempFilePaths[0]);
  357. this.uploadPhoto(res.tempFilePaths[0], this.photoTypes[this.currentStep]);
  358. },
  359. fail: (err) => {
  360. console.error("选择图片失败:", err);
  361. common_vendor.index.showToast({
  362. title: "拍照失败,请重试",
  363. icon: "none"
  364. });
  365. }
  366. });
  367. },
  368. // 修改相机错误处理方法
  369. handleCameraError(e) {
  370. console.error("相机错误:", e.detail);
  371. const errorInfo = JSON.stringify(e.detail);
  372. console.log("详细错误信息:", errorInfo);
  373. common_vendor.index.showToast({
  374. title: "相机启动失败,将使用系统相机",
  375. icon: "none"
  376. });
  377. this.fallbackToChooseImage();
  378. },
  379. // 取消拍照 - 更新为同时支持H5和小程序
  380. cancelCamera() {
  381. if (this.isH5) {
  382. this.stopMediaStream();
  383. }
  384. this.showCamera = false;
  385. },
  386. // 停止摄像头流
  387. stopMediaStream() {
  388. if (this.mediaStream) {
  389. this.mediaStream.getTracks().forEach((track) => {
  390. track.stop();
  391. });
  392. this.mediaStream = null;
  393. }
  394. },
  395. // base64转blob工具方法
  396. base64ToBlob(base64, mimeType) {
  397. const byteCharacters = atob(base64);
  398. const byteArrays = [];
  399. for (let offset = 0; offset < byteCharacters.length; offset += 512) {
  400. const slice = byteCharacters.slice(offset, offset + 512);
  401. const byteNumbers = new Array(slice.length);
  402. for (let i = 0; i < slice.length; i++) {
  403. byteNumbers[i] = slice.charCodeAt(i);
  404. }
  405. const byteArray = new Uint8Array(byteNumbers);
  406. byteArrays.push(byteArray);
  407. }
  408. return new Blob(byteArrays, { type: mimeType });
  409. },
  410. retryCamera() {
  411. this.showCamera = false;
  412. setTimeout(() => {
  413. this.takePhoto();
  414. }, 500);
  415. },
  416. // 可以添加一个方法来切换蒙层显示
  417. toggleGestureOverlay() {
  418. }
  419. }
  420. };
  421. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  422. return common_vendor.e({
  423. a: common_vendor.t($data.instructions[$data.currentStep]),
  424. b: $data.photos[$data.currentStep]
  425. }, $data.photos[$data.currentStep] ? {
  426. c: $data.photos[$data.currentStep]
  427. } : {
  428. d: common_vendor.t($data.previewTexts[$data.currentStep])
  429. }, {
  430. e: common_vendor.f($data.photos, (photo, index, i0) => {
  431. return common_vendor.e({
  432. a: photo
  433. }, photo ? {
  434. b: photo,
  435. c: common_vendor.o(($event) => $options.previewPhoto(index), index)
  436. } : {
  437. d: common_vendor.t(index + 1)
  438. }, {
  439. e: index,
  440. f: $data.currentStep === index ? 1 : "",
  441. g: photo ? 1 : ""
  442. });
  443. }),
  444. f: common_vendor.t($data.photos[$data.currentStep] ? "重新拍摄" : "拍摄照片"),
  445. g: common_vendor.o((...args) => $options.takePhoto && $options.takePhoto(...args)),
  446. h: $data.currentStep === 0,
  447. i: common_vendor.o((...args) => $options.prevStep && $options.prevStep(...args)),
  448. j: common_vendor.t($data.currentStep === 5 && $data.photos[5] ? "完成" : "下一步"),
  449. k: !$options.canGoNext,
  450. l: common_vendor.o((...args) => $options.nextStep && $options.nextStep(...args)),
  451. m: !$data.isH5 && $data.showCamera
  452. }, !$data.isH5 && $data.showCamera ? {
  453. n: common_vendor.o((...args) => $options.handleCameraError && $options.handleCameraError(...args)),
  454. o: $data.cameraMode,
  455. p: $data.gestureOverlays[$data.currentStep],
  456. q: common_vendor.t($data.gestureHints[$data.currentStep]),
  457. r: common_vendor.o((...args) => $options.capturePhotoMP && $options.capturePhotoMP(...args)),
  458. s: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args))
  459. } : {}, {
  460. t: $data.isH5
  461. }, $data.isH5 ? {
  462. v: $data.gestureOverlays[$data.currentStep],
  463. w: common_vendor.t($data.gestureHints[$data.currentStep]),
  464. x: common_vendor.o((...args) => $options.capturePhoto && $options.capturePhoto(...args)),
  465. y: common_vendor.o((...args) => $options.cancelCamera && $options.cancelCamera(...args)),
  466. z: $data.showCamera
  467. } : {});
  468. }
  469. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
  470. wx.createPage(MiniProgramPage);