CheckInfo.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. <template>
  2. <view class="check-info">
  3. <view class="register" v-if="isProvince">
  4. <text>欢迎您登陆核验系统</text>
  5. <text @click="handleRegister">注册账号</text>
  6. </view>
  7. <view class="clock-container">
  8. <!-- 打卡圆形按钮 -->
  9. <view class="clock-circle" @click="handleClockIn">
  10. <text>{{ clockInInfo.dkdd ? "已打卡" : "打卡" }}</text>
  11. </view>
  12. <!-- 打卡信息 -->
  13. <view class="info-section">
  14. <view class="info-item">
  15. <text class="label">打卡时间:</text>
  16. <text class="value">{{ clockInInfo.dksj || "未打卡" }}</text>
  17. </view>
  18. <view class="info-item">
  19. <text class="label">打卡地点:</text>
  20. <text class="value">{{ clockInInfo.dkdd || "未打卡" }}</text>
  21. </view>
  22. </view>
  23. <!-- 拍照按钮 -->
  24. <button class="photo-btn" @click="takePhoto">拍照</button>
  25. </view>
  26. <!-- 图片预览区域 -->
  27. <view class="preview-section">
  28. <view class="preview-list" v-if="previewList.length > 0">
  29. <view
  30. v-for="(item, index) in previewList"
  31. :key="index"
  32. class="preview-item"
  33. >
  34. <image
  35. v-if="item.url"
  36. :src="item.url"
  37. mode="aspectFill"
  38. class="preview-image"
  39. @click="previewImage(index)"
  40. />
  41. <text v-else class="preview-text">预览</text>
  42. <text class="delete-icon" @click="deleteImage(index)">×</text>
  43. </view></view
  44. >
  45. <text v-else>暂无图片信息</text>
  46. </view>
  47. </view>
  48. </template>
  49. <script>
  50. import { getClockInInfoApi, saveClockInInfo, getImageInfo } from "@/api/task";
  51. export default {
  52. name: "CheckInfo",
  53. props: {
  54. taskInfo: {
  55. type: Object, // 根据实际数据类型调整
  56. default: () => ({}), // 默认值
  57. },
  58. },
  59. data() {
  60. return {
  61. previewList: [],
  62. clockInInfo: {
  63. dksj: "",
  64. dkdd: "",
  65. // latitude: "",
  66. // longitude: "",
  67. },
  68. };
  69. },
  70. // uni-app中的vue的生命周期执行顺序:
  71. // onLoad
  72. // created
  73. // onShow
  74. // beforeMount
  75. // onReady
  76. // mounted
  77. // beforeUpdate
  78. // updated
  79. // 返回上一页时,会执行onUnLoad,并未有执行destroyed,并未执行onHide
  80. // 进入下一页时,会执行onHide
  81. // 获取本地打卡记录
  82. // created() {
  83. // this.getLocalClockInInfo();
  84. // },
  85. mounted() {
  86. // 判断是否是省级角色
  87. const userInfo = uni.getStorageSync("userInfo");
  88. if (userInfo.roles.includes("provincial_verification_personnel")) {
  89. this.isProvince = true;
  90. }
  91. this.getClockInInfo();
  92. },
  93. methods: {
  94. // 传递taskInfo并跳转
  95. handleRegister() {
  96. uni.navigateTo({
  97. url:
  98. "/pages/register/register?taskInfo=" + JSON.stringify(this.taskInfo),
  99. });
  100. },
  101. // handleRegister() {
  102. // uni.navigateTo({
  103. // url: "/pages/register/register",
  104. // });
  105. // },
  106. getClockInInfo() {
  107. // const userInfo = uni.getStorageSync("userInfo");
  108. // 加载
  109. // uni.showLoading({
  110. // title: "加载中...",
  111. // });
  112. getClockInInfoApi({
  113. kqId: this.taskInfo.kqId,
  114. // userId: userInfo.user.id,
  115. })
  116. .then((res) => {
  117. // console.log("初始化获取打卡信息:", res);
  118. // if (res.data.userId) {
  119. if (res.code === 0 && res.data !== null) {
  120. this.clockInInfo = res.data;
  121. // 添加这行,向父组件发送打卡信息
  122. this.$emit("update-clock-info", this.clockInInfo);
  123. if (
  124. this.clockInInfo.dkdd &&
  125. this.clockInInfo.pzxx &&
  126. this.clockInInfo.pzxx.length
  127. ) {
  128. getImageInfo({
  129. ids: res.data.pzxx,
  130. }).then((res) => {
  131. this.previewList = res.data.map((item) => {
  132. return {
  133. url: item.url,
  134. id: item.id,
  135. };
  136. });
  137. });
  138. }
  139. // getImageInfo({
  140. // fileIds: this.clockInInfo.pzxx,
  141. // }).then((res) => {
  142. // console.log(res, "图片信息");
  143. // });
  144. } else {
  145. this.clockInInfo = {
  146. dksj: "",
  147. dkdd: "",
  148. };
  149. // 当没有打卡信息时也要通知父组件
  150. this.$emit("update-clock-info", this.clockInInfo);
  151. }
  152. // }
  153. })
  154. .finally(() => {
  155. // uni.hideLoading();
  156. });
  157. },
  158. // 获取本地的打卡信息
  159. getLocalClockInInfo() {
  160. const today = this.getToday();
  161. const storageKey = `clockIn_${today}`;
  162. try {
  163. const clockInData = uni.getStorageSync(storageKey);
  164. if (clockInData) {
  165. this.clockInInfo = JSON.parse(clockInData);
  166. }
  167. } catch (e) {
  168. console.error("获取本地打卡记录失败:", e);
  169. }
  170. },
  171. // 获取今日日期
  172. getToday() {
  173. const now = new Date();
  174. const year = now.getFullYear();
  175. const month = String(now.getMonth() + 1).padStart(2, "0");
  176. const day = String(now.getDate()).padStart(2, "0");
  177. return `${year}-${month}-${day}`;
  178. },
  179. async handleClockIn() {
  180. // const ClockData = await getClockInInfo({
  181. // kqId: this.taskInfo.kqId,
  182. // });
  183. // 获取位置信息
  184. uni.getLocation({
  185. type: "gcj02",
  186. success: (res) => {
  187. this.clockInInfo.latitude = res.latitude;
  188. this.clockInInfo.longitude = res.longitude;
  189. // 获取详细地址
  190. this.getAddress(res.latitude, res.longitude);
  191. },
  192. fail: (err) => {
  193. uni.showToast({
  194. title: "获取位置信息失败,请检查定位权限",
  195. icon: "none",
  196. });
  197. console.error("获取位置失败:", err);
  198. },
  199. });
  200. },
  201. // 根据经纬度获取详细地址
  202. getAddress(latitude, longitude) {
  203. uni.request({
  204. url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=SOEBZ-J2BHS-7YSOI-64K33-6RO65-G2FEJ`, // 需要替换为您的腾讯地图 key
  205. success: (res) => {
  206. if (res.data.status === 0) {
  207. this.clockInInfo.dkdd = res.data.result.address;
  208. // 记录打卡时间
  209. this.recordClockIn();
  210. } else {
  211. uni.showToast({
  212. title: "获取地址信息失败",
  213. icon: "none",
  214. });
  215. }
  216. },
  217. fail: () => {
  218. uni.showToast({
  219. title: "获取地址信息失败",
  220. icon: "none",
  221. });
  222. },
  223. });
  224. },
  225. recordClockIn() {
  226. const now = new Date();
  227. const year = now.getFullYear();
  228. const month = String(now.getMonth() + 1).padStart(2, "0");
  229. const day = String(now.getDate()).padStart(2, "0");
  230. const hours = String(now.getHours()).padStart(2, "0");
  231. const minutes = String(now.getMinutes()).padStart(2, "0");
  232. const seconds = String(now.getSeconds()).padStart(2, "0");
  233. this.clockInInfo.dksj = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  234. this.$emit("update-clock-info", this.clockInInfo);
  235. // 保存打卡记录到本地存储
  236. // const today = this.getToday();
  237. // const storageKey = `clockIn_${today}`;
  238. // try {
  239. // uni.setStorageSync(storageKey, JSON.stringify(this.clockInInfo));
  240. // } catch (e) {
  241. // console.error("保存本地打卡记录失败:", e);
  242. // }
  243. // 这里可以调用接口保存打卡记录
  244. this.saveClockInRecord();
  245. },
  246. // 保存打卡记录到服务器
  247. saveClockInRecord() {
  248. // 加载
  249. let clockInData;
  250. uni.showLoading({
  251. title: "打卡中...",
  252. });
  253. // clockInData = {
  254. // // id: this.clockInInfo.id,
  255. // kqId: this.taskInfo.kqId,
  256. // dkdd: this.clockInInfo.dkdd,
  257. // dksj: this.clockInInfo.dksj,
  258. // // latitude: this.clockInInfo.latitude,
  259. // // longitude: this.clockInInfo.longitude,
  260. // };
  261. // 示例:调用保存打卡记录的接口
  262. // 曾经打过卡
  263. if (this.clockInInfo.id) {
  264. clockInData = {
  265. id: this.clockInInfo.id,
  266. kqId: this.taskInfo.kqId,
  267. dkdd: this.clockInInfo.dkdd,
  268. dksj: this.clockInInfo.dksj,
  269. // latitude: this.clockInInfo.latitude,
  270. // longitude: this.clockInInfo.longitude,
  271. };
  272. } else {
  273. clockInData = {
  274. // userId: uni.getStorageSync("userInfo").user.id,
  275. // id: this.clockInInfo.id,
  276. kqId: this.taskInfo.kqId,
  277. dkdd: this.clockInInfo.dkdd,
  278. dksj: this.clockInInfo.dksj,
  279. // latitude: this.clockInInfo.latitude,
  280. // longitude: this.clockInInfo.longitude,
  281. };
  282. }
  283. saveClockInInfo(clockInData)
  284. .then((res) => {
  285. if (res.code === 0) {
  286. uni.showToast({
  287. title: "打卡成功",
  288. icon: "success",
  289. });
  290. this.getClockInInfo();
  291. } else {
  292. uni.showToast({
  293. title: "打卡失败",
  294. icon: "none",
  295. });
  296. }
  297. })
  298. .finally(() => {
  299. // uni.hideLoading();
  300. });
  301. },
  302. takePhoto() {
  303. if (!this.clockInInfo.dkdd) {
  304. uni.showToast({
  305. title: "请先打卡",
  306. icon: "none",
  307. });
  308. return;
  309. }
  310. uni.chooseImage({
  311. count: 1,
  312. sizeType: ["compressed"],
  313. sourceType: ["camera", "album"],
  314. success: (res) => {
  315. // 获取图片 转化为二进制
  316. // uni.getFileSystemManager().readFile({
  317. // filePath: res.tempFilePaths[0],
  318. // encoding: "base64",
  319. // // encoding: "binary",
  320. // success: (base64Res) => {
  321. // this.previewList.push({
  322. // url: res.tempFilePaths[0],
  323. // base64: base64Res.data,
  324. // });
  325. // this.uploadImage(base64Res.data);
  326. // },
  327. // fail: (err) => {
  328. // console.error("图片转base64失败:", err);
  329. // uni.showToast({
  330. // title: "图片处理失败",
  331. // icon: "none",
  332. // });
  333. // },
  334. // });
  335. this.uploadImage(res.tempFilePaths[0]);
  336. },
  337. fail: (err) => {
  338. console.error("选择图片失败:", err);
  339. uni.showToast({
  340. title: "选择图片失败",
  341. icon: "none",
  342. });
  343. },
  344. });
  345. },
  346. uploadImage(base64Data) {
  347. uni.uploadFile({
  348. url: "http://121.36.17.6:8002/admin-api/infra/file/uploadForFile",
  349. method: "POST",
  350. header: {
  351. authorization: "Bearer " + uni.getStorageSync("token"),
  352. },
  353. filePath: base64Data,
  354. name: "file",
  355. success: (resp) => {
  356. let res = JSON.parse(resp.data);
  357. if (res.code === 0) {
  358. uni.showToast({
  359. title: "图片上传成功",
  360. icon: "success",
  361. });
  362. this.previewList.push({
  363. url: res.data.url,
  364. id: res.data.id,
  365. });
  366. // getClockInInfo;
  367. let pzxx = this.previewList.map((item) => {
  368. return item.id;
  369. });
  370. if (this.clockInInfo.dkdd) {
  371. saveClockInInfo({
  372. id: this.clockInInfo.id,
  373. kqId: this.clockInInfo.kqId,
  374. pzxx,
  375. });
  376. } else {
  377. // 弹出
  378. uni.showToast({
  379. title: "请先打卡",
  380. icon: "none",
  381. });
  382. }
  383. } else {
  384. uni.showToast({
  385. title: "图片上传失败",
  386. icon: "none",
  387. });
  388. }
  389. },
  390. fail: (err) => {
  391. uni.showToast({
  392. title: "图片上传失败",
  393. icon: "none",
  394. });
  395. },
  396. });
  397. },
  398. deleteImage(index) {
  399. this.previewList.splice(index, 1);
  400. saveClockInInfo({
  401. id: this.clockInInfo.id,
  402. kqId: this.clockInInfo.kqId,
  403. pzxx: this.previewList.map((item) => {
  404. return item.id;
  405. }),
  406. }).then((res) => {
  407. if (res.code === 0) {
  408. uni.showToast({
  409. title: "删除图片成功",
  410. });
  411. } else {
  412. uni.showToast({
  413. title: "删除图片失败",
  414. });
  415. }
  416. });
  417. },
  418. // 添加图片预览方法
  419. previewImage(index) {
  420. const urls = this.previewList
  421. .map((item) => item.url)
  422. .filter((url) => url); // 获取所有图片url
  423. uni.previewImage({
  424. current: index, // 当前显示图片的索引
  425. urls: urls, // 需要预览的图片url列表
  426. indicator: "number",
  427. loop: true,
  428. success: () => {
  429. console.log("图片预览成功");
  430. },
  431. fail: (err) => {
  432. console.error("图片预览失败:", err);
  433. uni.showToast({
  434. title: "图片预览失败",
  435. icon: "none",
  436. });
  437. },
  438. });
  439. },
  440. },
  441. };
  442. </script>
  443. <style lang="scss" scoped>
  444. .check-info {
  445. height: 100%;
  446. padding: 20rpx;
  447. padding-bottom: 120rpx;
  448. display: flex;
  449. flex-direction: column;
  450. align-items: center;
  451. overflow: auto;
  452. .register {
  453. width: 100%;
  454. height: 70rpx;
  455. font-size: 28rpx;
  456. color: #333;
  457. display: flex;
  458. justify-content: space-between;
  459. align-items: center;
  460. background-color: #fff;
  461. border-radius: 10rpx;
  462. margin-bottom: 10rpx;
  463. padding: 0 20rpx;
  464. box-sizing: border-box;
  465. }
  466. }
  467. .clock-container {
  468. width: 100%;
  469. display: flex;
  470. flex-direction: column;
  471. align-items: center;
  472. background-color: #fff;
  473. border-radius: 10rpx;
  474. }
  475. .clock-circle {
  476. width: 300rpx;
  477. height: 300rpx;
  478. border-radius: 50%;
  479. background-color: #2979ff;
  480. display: flex;
  481. align-items: center;
  482. justify-content: center;
  483. margin: 40rpx 0;
  484. cursor: pointer;
  485. box-shadow: 5px 0px 15px rgba(0, 0, 0, 0.4); /* 添加阴影 */
  486. text {
  487. color: #fff;
  488. font-size: 48rpx;
  489. }
  490. // &:active {
  491. // opacity: 0.8;
  492. // }
  493. }
  494. .info-section {
  495. width: 100%;
  496. padding: 20rpx;
  497. display: flex;
  498. flex-direction: column;
  499. align-items: center;
  500. .info-item {
  501. margin-bottom: 20rpx;
  502. font-size: 28rpx;
  503. color: #333;
  504. .label {
  505. color: #666;
  506. }
  507. }
  508. }
  509. .photo-btn {
  510. width: 240rpx;
  511. height: 80rpx;
  512. line-height: 80rpx;
  513. background-color: #2979ff;
  514. color: #fff;
  515. border-radius: 10rpx;
  516. font-size: 28rpx;
  517. margin: 30rpx 0;
  518. }
  519. .preview-section {
  520. height: 540rpx;
  521. margin-top: 30rpx;
  522. width: 100%;
  523. display: flex;
  524. justify-content: center;
  525. align-items: center;
  526. background-color: #fff;
  527. .preview-list {
  528. height: 100%;
  529. width: 100%;
  530. display: flex;
  531. justify-content: space-between;
  532. flex-wrap: wrap;
  533. overflow: auto;
  534. }
  535. .preview-item {
  536. margin: 20rpx;
  537. width: 310rpx;
  538. height: 270rpx;
  539. border: 2rpx solid #eee;
  540. position: relative;
  541. display: flex;
  542. align-items: center;
  543. justify-content: center;
  544. .preview-image {
  545. width: 100%;
  546. height: 100%;
  547. cursor: pointer; // 添加手型光标提示可点击
  548. }
  549. .preview-text {
  550. color: #999;
  551. font-size: 28rpx;
  552. }
  553. .delete-icon {
  554. position: absolute;
  555. top: -20rpx;
  556. right: -20rpx;
  557. width: 40rpx;
  558. height: 40rpx;
  559. line-height: 34rpx;
  560. text-align: center;
  561. background-color: rgba(0, 0, 0, 0.3);
  562. color: #fff;
  563. border-radius: 50%;
  564. font-size: 32rpx;
  565. }
  566. }
  567. }
  568. </style>