home.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. <template>
  2. <view class="container">
  3. <view class="placeholder"></view>
  4. <!-- 头部学生信息区域 -->
  5. <view class="header">
  6. <view class="img">
  7. <img :src="userInfo.headImage||'../static/imgs/headImage.png'">
  8. </view>
  9. <view class="msg">
  10. <view class="name">
  11. {{userInfo.name||"用户"}}
  12. </view>
  13. <view class="major">
  14. {{userInfo.college||"南昌交通学院"}}
  15. </view>
  16. </view>
  17. </view>
  18. <!-- 主体打卡区域 -->
  19. <view class="body">
  20. <!-- 卡片区域 -->
  21. <view class="card" v-if="list.length">
  22. <!-- 每一个卡片区域 -->
  23. <view :class="activeid ==item.id?'active_box':'item'" v-for="item in list" :key="item.id"
  24. @click="handleClick(item)">
  25. <view class="item_box">
  26. <view class="title">
  27. {{item.ruleName}}
  28. </view>
  29. <view class="time">
  30. {{item.timeRange}}
  31. </view>
  32. <view class="type">
  33. <img v-if="item.status==4" src="./imgs/success.png">
  34. <span v-if="item.status==4">
  35. {{format_time(item.updateTime)}}
  36. </span>
  37. <span v-if="item.status==4"> 已打卡</span>
  38. <span v-if="item.status==3">已超时</span>
  39. <span v-if="item.status==1||item.status==2">未打卡</span>
  40. </view>
  41. </view>
  42. </view>
  43. </view>
  44. <!-- list数组为空时的占位盒子 -->
  45. <view class="card" v-else></view>
  46. <!-- 打卡区域 -->
  47. <view :class="{clock:flags,active:!flags}" @click="handlePunch(contrastObj)">
  48. <view class="info" v-if="flags">
  49. 打卡
  50. </view>
  51. <view class="info" v-else>
  52. 无法打卡
  53. </view>
  54. <view class="time">
  55. {{nowTime}}
  56. </view>
  57. </view>
  58. <!-- 提示信息位置 -->
  59. <view class="address" v-if="flags">
  60. {{address}}
  61. </view>
  62. <view class="address" v-else>
  63. {{notes}}
  64. </view>
  65. </view>
  66. <!-- 底部导航栏区域 -->
  67. <view class="tab_bar">
  68. <navigator open-type="redirect" url="/pagesClockIn/home/home" class="tab_box">
  69. <img v-if="pageUrl=='pagesClockIn/home/home'" src="../static/imgs/home_active.png">
  70. <img v-else src="../static/imgs/home.png">
  71. <view v-if="pageUrl=='pagesClockIn/home/home'" class="tab_title_active">
  72. 首页
  73. </view>
  74. <view v-else class="tab_title">
  75. 首页
  76. </view>
  77. </navigator>
  78. <navigator open-type="redirect" url="/pagesClockIn/stat/stat" class="tab_box">
  79. <img v-if="pageUrl=='pagesClockIn/stat/stat'" src="../static/imgs/stat_active.png">
  80. <img v-else src="../static/imgs/stat.png">
  81. <view v-if="pageUrl=='pagesClockIn/stat/stat'" class="tab_title_active">
  82. 统计
  83. </view>
  84. <view v-else class="tab_title">
  85. 统计
  86. </view>
  87. </navigator>
  88. <navigator open-type="redirect" v-if="showTab" url="/pagesClockIn/my/my" class="tab_box">
  89. <img v-if="pageUrl=='pagesClockIn/my/my'" src="../static/imgs/my_active.png">
  90. <img v-else src="../static/imgs/my.png">
  91. <view v-if="pageUrl=='pagesClockIn/my/my'" class="tab_title_active">
  92. 我的
  93. </view>
  94. <view v-else class="tab_title">
  95. 我的
  96. </view>
  97. </navigator>
  98. </view>
  99. <!-- 认证结果弹窗 -->
  100. <uni-popup ref="popup" :is-mask-click="false">
  101. <view class="popup-content">
  102. <view class="title">
  103. <view class="icon">
  104. <img src="./imgs/success2.png">
  105. </view>
  106. <view class="title_info">
  107. 打卡成功
  108. </view>
  109. </view>
  110. <view class="time">
  111. {{nowTime_pop}}
  112. </view>
  113. <view class="popup_button" @click="handleGoHome">
  114. 我知道了
  115. </view>
  116. </view>
  117. </uni-popup>
  118. </view>
  119. </template>
  120. <script>
  121. var QQMapWX = require('../util/qqmap-wx-jssdk1.1/qqmap-wx-jssdk');
  122. var qqmapsdk;
  123. export default {
  124. data() {
  125. return {
  126. // 用户信息
  127. userInfo: {},
  128. // 打卡规则列表
  129. list: [],
  130. // 当前时间
  131. nowTime: "",
  132. // 弹窗当前时间
  133. nowTime_pop: "",
  134. // 当前定位位置信息
  135. address: "",
  136. // 定时器标识
  137. timer: null,
  138. // 提示信息
  139. notes: "",
  140. // 当前显示的是哪个规则id
  141. activeid: null,
  142. // 当前时间的时间戳
  143. timestamp: null,
  144. // 当前显示的规则具体信息
  145. contrastObj: {},
  146. // 当前用户定位经度
  147. myLng: 0,
  148. // 当前用户定位纬度
  149. myLat: 0,
  150. // 签到点中心经度
  151. centerLng: 0,
  152. // 签到点中心纬度
  153. centerLat: 0,
  154. // 签到半径
  155. radius: 0,
  156. // 距离签到点的距离
  157. distance: 0,
  158. // 是否可以打卡的标识
  159. flags: false,
  160. // 当前页面的路由地址
  161. pageUrl: "",
  162. // 是否显示底部 我的 导航栏
  163. showTab: false,
  164. // 是否需要人脸识别
  165. faceRecognition: true,
  166. // 是否需要拍摄场景照片
  167. takePicture: true
  168. };
  169. },
  170. onLoad() {
  171. // 获取用户的个人信息数据
  172. this.getUserInfo()
  173. // 实例化API核心类
  174. qqmapsdk = new QQMapWX({
  175. // 申请的key
  176. key: 'X57BZ-ZISE3-KTN3O-3P45H-C3J7Q-D5B67'
  177. });
  178. // 获取当前系统时间
  179. this.getNowTime()
  180. },
  181. onShow() {
  182. this.getPageUrl()
  183. // 获取当前位置的详细信息
  184. this.getLocationData()
  185. // 获取当前时间的时间戳
  186. this.getTimestamp()
  187. },
  188. onUnload() {
  189. if (this.timer) {
  190. clearInterval(this.timer)
  191. }
  192. },
  193. // 下拉刷新
  194. onPullDownRefresh() {
  195. uni.removeStorageSync("manager")
  196. uni.removeStorageSync("sub-administrator")
  197. qqmapsdk = new QQMapWX({
  198. // 申请的key
  199. key: 'X57BZ-ZISE3-KTN3O-3P45H-C3J7Q-D5B67'
  200. });
  201. this.getNowTime()
  202. this.getTimestamp()
  203. this.getUserInfo()
  204. this.getLocationData()
  205. setTimeout(() => {
  206. uni.stopPullDownRefresh()
  207. }, 1500)
  208. },
  209. methods: {
  210. getPageUrl() {
  211. // 获取当前打开过的页面路由数组
  212. let routes = getCurrentPages();
  213. // 获取当前页面路由,也就是最后一个打开的页面路由
  214. let curRoute = routes[routes.length - 1].route
  215. this.pageUrl = curRoute
  216. },
  217. // 获取当前时间
  218. getNowTime() {
  219. if (this.timer) {
  220. clearInterval(this.timer)
  221. }
  222. this.timer = setInterval(() => {
  223. let date = new Date()
  224. let hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
  225. let minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
  226. let seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
  227. this.nowTime = hours + ':' + minutes + ':' + seconds
  228. }, 1000)
  229. },
  230. // 获取当前时间的时间戳
  231. getTimestamp() {
  232. let dates = new Date()
  233. let times = dates.getTime()
  234. this.timestamp = times
  235. },
  236. // 获取用户详细信息
  237. async getUserInfo() {
  238. let res = await this.$myRequest_clockIn({
  239. url: `/attendance/api/system/user/detail`,
  240. })
  241. // console.log(res);
  242. if (res.code == 200) {
  243. this.userInfo = res.data
  244. uni.setStorageSync("userInfo", this.userInfo)
  245. if (this.userInfo.roles) {
  246. let temList = []
  247. this.userInfo.roles.forEach((ele) => {
  248. temList.push(ele.id)
  249. })
  250. let flag = temList.includes(2)
  251. let flag2 = temList.includes(3)
  252. if (flag) {
  253. uni.setStorageSync("manager", flag)
  254. } else {
  255. uni.removeStorageSync("manager")
  256. }
  257. if (flag2) {
  258. uni.setStorageSync("sub-administrator", flag2)
  259. } else {
  260. uni.removeStorageSync("sub-administrator")
  261. }
  262. let flag_show = uni.getStorageSync("manager")
  263. let flag_show2 = uni.getStorageSync("sub-administrator")
  264. if (flag_show || flag_show2) {
  265. this.showTab = true
  266. } else {
  267. this.showTab = false
  268. }
  269. }
  270. }
  271. },
  272. // 获取当前定位位置信息
  273. getLocationData() {
  274. qqmapsdk.reverseGeocoder({
  275. success: (res) => {
  276. // console.log(res);
  277. if (res.status == 0) {
  278. // 获取详细地址信息 经纬度
  279. this.address = res.result.address
  280. this.myLat = res.result.location.lat
  281. this.myLng = res.result.location.lng
  282. // 获取当天的打卡列表数组
  283. this.getRulesList()
  284. } else {
  285. uni.showToast({
  286. title: "请求定位失败",
  287. icon: 'none'
  288. })
  289. }
  290. },
  291. fail: (error) => {
  292. uni.showModal({
  293. title: '提示',
  294. content: '请先开启定位权限,否则将无法使用定位功能',
  295. cancelText: '不授权',
  296. confirmText: '授权',
  297. success: function(res) {
  298. if (res.confirm) {
  299. uni.openSetting({
  300. success(res) {}
  301. })
  302. } else if (res.cancel) {
  303. uni.redirectTo({
  304. url: "/pagesClockIn/index/index"
  305. })
  306. }
  307. },
  308. });
  309. },
  310. })
  311. },
  312. // 获取打卡规则列表
  313. async getRulesList() {
  314. let res = await this.$myRequest_clockIn({
  315. url: "/attendance/api/sign/check/in/list/today",
  316. })
  317. // console.log(res);
  318. if (res.code == 200) {
  319. if (res.data.length == 0) {
  320. this.flags = false
  321. this.notes = "无打卡任务无需打卡"
  322. } else {
  323. this.flags = true
  324. this.list = res.data
  325. this.activeid = this.list[0].id
  326. this.faceRecognition = this.list[0].faceRecognition
  327. this.takePicture = this.list[0].takePicture
  328. this.contrastObj = this.list[0]
  329. this.changeType()
  330. }
  331. }
  332. },
  333. // 对比信息改变打卡的状态显示
  334. changeType() {
  335. if (this.contrastObj.status == 4) {
  336. this.flags = false
  337. this.notes = "已打卡,无需再次打卡"
  338. } else {
  339. // 没有到打卡时间 或者 超过打卡时间 的状态
  340. if (this.timestamp < this.contrastObj.beginTime || this.timestamp > this.contrastObj.endTime) {
  341. this.flags = false
  342. this.notes = "不在打卡时间段内,无法打卡"
  343. }
  344. // 到了打卡时间,判断是否在打卡范围内
  345. else {
  346. if (this.contrastObj.locations.length) {
  347. let temList = []
  348. temList = this.contrastObj.locations.map((ele) => {
  349. this.centerLat = ele.lat
  350. this.centerLng = ele.lng
  351. this.radius = ele.radius
  352. let red1 = this.myLat * Math.PI / 180.0;
  353. let red2 = this.centerLat * Math.PI / 180.0;
  354. let a = red1 - red2;
  355. let b = this.myLng * Math.PI / 180.0 - this.centerLng * Math.PI / 180.0;
  356. let R = 6378137;
  357. let distance = R * 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(
  358. red1) *
  359. Math.cos(red2) *
  360. Math.pow(Math.sin(b / 2), 2)));
  361. this.distance = distance.toFixed(2) * 1;
  362. // console.log(this.distance);
  363. // console.log(this.radius);
  364. if (this.distance <= this.radius) {
  365. return 1
  366. } else {
  367. return 2
  368. }
  369. })
  370. let temFlag = temList.indexOf(1)
  371. if (temFlag == -1) {
  372. this.flags = false
  373. this.notes = "不在管理员设定范围内,无法打卡"
  374. } else {
  375. this.flags = true
  376. }
  377. }
  378. }
  379. }
  380. },
  381. // 点击每一个打卡规则回调
  382. handleClick(item) {
  383. // console.log(item);
  384. this.getTimestamp()
  385. this.contrastObj = item
  386. this.activeid = item.id
  387. this.faceRecognition = item.faceRecognition
  388. this.takePicture = item.takePicture
  389. this.changeType()
  390. },
  391. // 点击打卡按钮回调
  392. handlePunch(info) {
  393. if (this.flags) {
  394. let obj = JSON.stringify(info)
  395. // 如果场景照片和人脸识别都需要
  396. if (this.faceRecognition && this.takePicture) {
  397. uni.navigateTo({
  398. url: `/pagesClockIn/location/location?obj=${obj}`
  399. })
  400. }
  401. // 如果都不需要
  402. else if (!this.faceRecognition && !this.takePicture) {
  403. this.handleUploading()
  404. }
  405. // 如果只需要场景照片
  406. else if (this.takePicture) {
  407. uni.navigateTo({
  408. url: `/pagesClockIn/location/location?obj=${obj}&flag=1`
  409. })
  410. }
  411. // 如果只需要人脸识别
  412. else if (this.faceRecognition) {
  413. uni.navigateTo({
  414. url: `/pagesClockIn/authentication/authentication?id=${this.contrastObj.id}&address=${this.address}&latitude=${this.myLat}&longitude=${this.myLng}&flag=1`
  415. })
  416. }
  417. }
  418. },
  419. // 打卡请求
  420. async handleUploading() {
  421. let res = await this.$myRequest_clockIn({
  422. url: "/attendance/api/sign/check/in/update",
  423. method: "put",
  424. header: {
  425. 'Authorization': uni.getStorageSync("token"),
  426. 'platform': 2,
  427. 'Accept-Language': 'zh-CN,zh;q=0.9'
  428. },
  429. data: {
  430. id: this.contrastObj.id,
  431. lat: this.myLat,
  432. lng: this.myLng,
  433. location: this.address,
  434. }
  435. })
  436. // console.log(res);
  437. if (res.code == 200) {
  438. this.getNowTimePop()
  439. this.$refs.popup.open()
  440. }
  441. },
  442. // 点击 我知道了按钮 跳回首页
  443. handleGoHome() {
  444. this.$refs.popup.close()
  445. uni.reLaunch({
  446. url: "/pagesClockIn/home/home"
  447. })
  448. },
  449. getNowTimePop() {
  450. let date = new Date()
  451. let hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
  452. let minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
  453. let seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
  454. this.nowTime_pop = hours + ':' + minutes + ':' + seconds
  455. },
  456. // 格式化时间
  457. format_time(timestamp) {
  458. //时间戳为10位需*1000,时间戳为13位的话不需乘1000
  459. var date = new Date(timestamp);
  460. var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
  461. var m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes());
  462. let strDate = h + m;
  463. return strDate;
  464. }
  465. }
  466. }
  467. </script>
  468. <style lang="scss" scoped>
  469. .container {
  470. width: 100vw;
  471. height: 100vh;
  472. background-color: #F2F2F2;
  473. .placeholder {
  474. height: 20rpx;
  475. }
  476. .header {
  477. display: flex;
  478. margin: 0 30rpx 30rpx 30rpx;
  479. width: 690rpx;
  480. height: 130rpx;
  481. background-color: #fff;
  482. .img {
  483. margin: 30rpx;
  484. width: 70rpx;
  485. height: 70rpx;
  486. img {
  487. width: 100%;
  488. height: 100%;
  489. }
  490. }
  491. .msg {
  492. margin: 30rpx 0;
  493. height: 70rpx;
  494. line-height: 36rpx;
  495. .name {
  496. font-size: 32rpx;
  497. font-weight: 400;
  498. }
  499. .major {
  500. font-size: 24rpx;
  501. font-weight: 400;
  502. color: #A6A6A6;
  503. }
  504. }
  505. }
  506. .body {
  507. margin: 0 30rpx;
  508. width: 690rpx;
  509. height: 75vh;
  510. background-color: #fff;
  511. .card {
  512. padding-top: 30rpx;
  513. white-space: nowrap;
  514. height: 160rpx;
  515. overflow-x: auto;
  516. .active_box {
  517. display: inline-block;
  518. margin: 0 15rpx;
  519. width: 300rpx;
  520. height: 130rpx;
  521. line-height: 12rpx;
  522. border-radius: 8rpx;
  523. font-weight: 400;
  524. background-color: #E6E6E6;
  525. border: 1rpx solid #0094FC;
  526. .item_box {
  527. display: flex;
  528. flex-direction: column;
  529. justify-content: space-evenly;
  530. height: 130rpx;
  531. padding-left: 30rpx;
  532. .title {
  533. font-size: 28rpx;
  534. }
  535. .time {
  536. font-size: 24rpx;
  537. }
  538. .type {
  539. font-size: 24rpx;
  540. color: #808080;
  541. img {
  542. margin-right: 10rpx;
  543. width: 20rpx;
  544. height: 20rpx;
  545. }
  546. }
  547. }
  548. }
  549. .item {
  550. display: inline-block;
  551. margin: 0 15rpx;
  552. width: 300rpx;
  553. height: 130rpx;
  554. line-height: 12rpx;
  555. border-radius: 8rpx;
  556. font-weight: 400;
  557. background-color: #E6E6E6;
  558. .item_box {
  559. display: flex;
  560. flex-direction: column;
  561. justify-content: space-evenly;
  562. height: 130rpx;
  563. padding-left: 30rpx;
  564. .title {
  565. font-size: 28rpx;
  566. }
  567. .time {
  568. font-size: 24rpx;
  569. }
  570. .type {
  571. font-size: 24rpx;
  572. color: #808080;
  573. img {
  574. margin-right: 10rpx;
  575. width: 20rpx;
  576. height: 20rpx;
  577. }
  578. }
  579. }
  580. }
  581. }
  582. .clock {
  583. display: flex;
  584. flex-direction: column;
  585. justify-content: center;
  586. align-items: center;
  587. margin: auto;
  588. margin-top: 145rpx;
  589. width: 300rpx;
  590. height: 300rpx;
  591. border-radius: 150rpx;
  592. text-align: center;
  593. color: #fff;
  594. background: linear-gradient(180deg, #00ACFC 0%, #0082FC 100%);
  595. .info {
  596. height: 58rpx;
  597. font-size: 40rpx;
  598. font-weight: 700;
  599. }
  600. .time {
  601. height: 41rpx;
  602. font-size: 28rpx;
  603. font-weight: 400;
  604. }
  605. }
  606. .active {
  607. display: flex;
  608. flex-direction: column;
  609. justify-content: center;
  610. align-items: center;
  611. margin: auto;
  612. margin-top: 145rpx;
  613. width: 300rpx;
  614. height: 300rpx;
  615. border-radius: 150rpx;
  616. text-align: center;
  617. color: #fff;
  618. background: linear-gradient(180deg, #CCCCCC 0%, #B3B3B3 100%);
  619. .info {
  620. height: 58rpx;
  621. font-size: 40rpx;
  622. font-weight: 700;
  623. }
  624. .time {
  625. height: 41rpx;
  626. font-size: 28rpx;
  627. font-weight: 400;
  628. }
  629. }
  630. .address {
  631. margin-top: 34rpx;
  632. height: 35rpx;
  633. line-height: 35rpx;
  634. text-align: center;
  635. font-size: 24rpx;
  636. font-weight: 400;
  637. color: #808080;
  638. }
  639. }
  640. .tab_bar {
  641. position: fixed;
  642. left: 0;
  643. bottom: 0;
  644. display: flex;
  645. width: 750rpx;
  646. height: 128rpx;
  647. border-top: 1rpx solid #CCC;
  648. background-color: #fff;
  649. .tab_box {
  650. flex: 1;
  651. display: flex;
  652. flex-direction: column;
  653. justify-content: center;
  654. align-items: center;
  655. img {
  656. margin-bottom: 10rpx;
  657. width: 54rpx;
  658. height: 48rpx;
  659. }
  660. .tab_title {
  661. font-size: 20rpx;
  662. }
  663. .tab_title_active {
  664. font-size: 20rpx;
  665. color: #0082FC;
  666. }
  667. }
  668. }
  669. .popup-content {
  670. display: flex;
  671. flex-direction: column;
  672. justify-content: space-around;
  673. align-items: center;
  674. width: 630rpx;
  675. height: 376rpx;
  676. border-radius: 33rpx;
  677. background-color: #fff;
  678. .title {
  679. display: flex;
  680. justify-content: center;
  681. align-items: center;
  682. .icon {
  683. width: 40rpx;
  684. height: 40rpx;
  685. img {
  686. width: 100%;
  687. height: 100%;
  688. }
  689. }
  690. .title_info {
  691. margin-left: 8rpx;
  692. font-size: 36rpx;
  693. font-weight: 800;
  694. color: #0082FC;
  695. }
  696. }
  697. .time {
  698. font-size: 32rpx;
  699. }
  700. .popup_button {
  701. width: 50%;
  702. text-align: center;
  703. font-size: 32rpx;
  704. color: #0082FC;
  705. }
  706. }
  707. }
  708. </style>