index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <template>
  2. <!-- 考勤打卡 -->
  3. <view class="attendance-clock">
  4. <view class="main-container">
  5. <c-navbar title="考勤打卡"> </c-navbar>
  6. <view class="content">
  7. <!--时间相差过大-->
  8. <excessiveDce v-if="isShow" />
  9. <!-- 时间相差不大 -->
  10. <view v-else>
  11. <view class="user-info-header">
  12. <view class="u-info">
  13. <userAvatar></userAvatar>
  14. <view class="col">
  15. <text class="u-name">{{ detailsInfo.name }}</text>
  16. <view class="u-office">
  17. <text class="department"> 考勤组 </text>
  18. <view
  19. :class="
  20. detailsInfo.isGroup === '0'
  21. ? 'grey-txt'
  22. : 'position'
  23. "
  24. >
  25. {{ groupName }}
  26. </view>
  27. </view>
  28. </view>
  29. </view>
  30. <view
  31. class="statistics com-fun"
  32. @click="
  33. $u.route(
  34. '/pages/attendanceClock/statistics/attendStatistics/index'
  35. )
  36. "
  37. >
  38. <image
  39. class="fun-img"
  40. src="@/static/images/attendanceClock/statistics.png"
  41. ></image>
  42. <text class="fun-txt">统计</text>
  43. </view>
  44. <view
  45. class="rule com-fun"
  46. @click="
  47. $u.route(
  48. '/pages/attendanceClock/attendanceRules/index',
  49. {
  50. nickName: detailsInfo.name,
  51. isGroup: detailsInfo.isGroup,
  52. groupName: groupName,
  53. }
  54. )
  55. "
  56. >
  57. <image
  58. class="fun-img"
  59. src="@/static/images/attendanceClock/rule.png"
  60. ></image>
  61. <text class="fun-txt">规则</text>
  62. </view>
  63. </view>
  64. <!-- 不在考勤组 isGroup==='0' -->
  65. <notGroup
  66. :enterRangeCode="enterRangeCode"
  67. :detailsInfo="detailsInfo"
  68. :current="current"
  69. :currentTime="currentTime"
  70. :clockAddr="clockAddr"
  71. @reloadLocation="getAddrRange"
  72. @punchClock="punchClock"
  73. v-if="showNotGroup"
  74. ></notGroup>
  75. <!--在考勤组-steps-->
  76. <clockSteps
  77. v-else
  78. :enterRangeCode="enterRangeCode"
  79. :detailsInfo="detailsInfo"
  80. :current="current"
  81. :currentTime="currentTime"
  82. :clockAddr="clockAddr"
  83. @reloadLocation="getAddrRange"
  84. @punchClock="punchClock"
  85. ></clockSteps>
  86. </view>
  87. </view>
  88. <view class="footer">
  89. <c-button type="primary">
  90. <view class="dflex acenter">
  91. <image
  92. class="btn-icon"
  93. src="@/static/images/attendanceClock/clock-btn-icon.png"
  94. ></image>
  95. 打卡
  96. </view>
  97. </c-button>
  98. <c-button
  99. @click="
  100. $u.route(
  101. '/pages/attendanceClock/statistics/attendStatistics/index'
  102. )
  103. "
  104. >
  105. <view class="dflex acenter">
  106. <image
  107. class="btn-icon"
  108. src="@/static/images/attendanceClock/sta-btn-icon.png"
  109. ></image>
  110. 统计
  111. </view>
  112. </c-button>
  113. </view>
  114. </view>
  115. <u-modal
  116. @close="isShowCard = false"
  117. @confirm="isShowCard = false"
  118. closeOnClickOverlay
  119. confirmText="关闭"
  120. :show="isShowCard"
  121. >
  122. <view class="punch-container">
  123. <image
  124. class="punch-img"
  125. v-if="punchStatus"
  126. src="@/static/images/attendanceClock/punch-success.png"
  127. ></image>
  128. <image
  129. class="punch-img"
  130. v-else
  131. src="@/static/images/attendanceClock/punch-fail.png"
  132. ></image>
  133. <view
  134. :class="
  135. punchStatus ? 'punch-footer-success' : 'punch-footer-fail'
  136. "
  137. >
  138. <view class="punch-time">
  139. {{ punchServerTime }}
  140. </view>
  141. <view class="class-txt">
  142. {{ clockTxt }}
  143. </view>
  144. </view>
  145. </view>
  146. </u-modal>
  147. </view>
  148. </template>
  149. <script>
  150. import userAvatar from '@/pages/my/userAvatar.vue';
  151. import { getLocation } from '@/utils/getLocation';
  152. import { userCheck, check, getLock } from '@/api/attendanceClock';
  153. import clockSteps from '@/pages/attendanceClock/com/clock-steps.vue'; // 在考勤组
  154. import notGroup from '@/pages/attendanceClock/com/notGroup.vue'; // 不在考勤组
  155. import excessiveDce from '@/pages/attendanceClock/com/excessiveDce.vue'; // 时间相差过大显示的页面
  156. export default {
  157. name: 'attendanceClock',
  158. components: {
  159. userAvatar,
  160. clockSteps,
  161. notGroup,
  162. excessiveDce,
  163. },
  164. data() {
  165. return {
  166. clockType: '', //0上班,1下班,2更新打卡
  167. isShowCard: false, // 显示打卡后的提示
  168. punchStatus: false, // 打卡后的状态
  169. punchServerTime: '', // MM:ss 打卡成功/失败后的时间
  170. clockAddr: '', // 地点名
  171. detailsInfo: {}, // 详情
  172. enterRangeCode: '', // 0不在范围内 1在范围内
  173. isShow: false, // 是否显示时间差距大的提示
  174. currentTime: '', //hh:MM:ss 本机时间
  175. serverTime: '', //yyyy-mm-dd hh:MM:ss 服务器时间
  176. localTime: '', //yyyy-mm-dd hh:MM:ss 本地时间
  177. };
  178. },
  179. computed: {
  180. clockTxt() {
  181. const { clockType, punchStatus } = this; //0上班,1下班,2更新打卡
  182. const status = punchStatus ? '成功' : '失败';
  183. const types = {
  184. 0: '上班打卡',
  185. 1: '下班打卡',
  186. 2: '更新打卡',
  187. };
  188. return types[clockType] + status;
  189. },
  190. showNotGroup() {
  191. const isGroup = this.detailsInfo?.isGroup;
  192. return isGroup === '0'; // 不在考勤组 并且 显示
  193. },
  194. current() {
  195. const clockInDetailVO = this.detailsInfo?.clockInDetailVO || {};
  196. if (clockInDetailVO.status) {
  197. // 已有上班打卡的状态 或者 时间(不在考勤组打卡后没有状态)
  198. return 1;
  199. }
  200. return 0;
  201. },
  202. groupName() {
  203. const isGroup = this.detailsInfo.isGroup; // 是否加入考勤组 0未加入 1加入
  204. if (isGroup === '1') {
  205. return this.detailsInfo.groupName;
  206. }
  207. return '未设置';
  208. },
  209. },
  210. onShow() {
  211. this.getCheckInfo(); // 获取打卡页信息
  212. this.getAddrRange(); // 获取定位/打卡范围
  213. this.timeRun(); // 时间
  214. },
  215. onUnload() {
  216. // 页面卸载
  217. clearInterval(this.timer);
  218. },
  219. methods: {
  220. async punchClock(type) {
  221. try {
  222. // 打卡
  223. this.clockType = type;
  224. const params = await this.getPosition(); // 返回定位经纬度
  225. uni.$c.loading('打卡中');
  226. const res = await userCheck(params);
  227. uni.hideLoading();
  228. const sDate = res.header.date;
  229. this.punchServerTime = uni.$u.timeFormat(sDate, 'hh:MM');
  230. this.punchStatus = true; // 打卡成功
  231. this.isShowCard = true; //打开mdl
  232. await this.getCheckInfo(); // 刷新
  233. } catch (e) {
  234. uni.hideLoading();
  235. this.punchStatus = false; // 打卡失败
  236. throw new Error(e);
  237. }
  238. },
  239. thereIsAGap(serverDateTime) {
  240. // 判断服务器时间跟本地时间相差
  241. this.isShow = false;
  242. if (!serverDateTime) {
  243. return;
  244. }
  245. const curDate = +new Date(); // 当前时间
  246. const sDate = +new Date(serverDateTime); // 服务器上时间(会慢3-5秒)
  247. const difference = Math.abs(curDate - sDate); // 时间差
  248. const maxTime = 1000 * 60 * 60; // 一小时
  249. if (difference > maxTime) {
  250. this.serverTime = uni.$u.timeFormat(
  251. serverDateTime,
  252. 'yyyy-mm-dd hh:MM:ss'
  253. );
  254. this.localTime = uni.$u.timeFormat(
  255. new Date(),
  256. 'yyyy-mm-dd hh:MM:ss'
  257. );
  258. this.isShow = true;
  259. }
  260. },
  261. async getCheckInfo() {
  262. // 获取打卡页信息
  263. const res = await check(); // 详情
  264. this.detailsInfo = res.data.data || {};
  265. const sDate = res.header.date;
  266. const serverDateTime = sDate ? +new Date(sDate) : '';
  267. this.thereIsAGap(serverDateTime); // 本地date跟服务器上对比
  268. },
  269. async getAddrRange() {
  270. // 根据经纬度判断是否进入打卡范围
  271. try {
  272. const params = await this.getPosition(); // 获取定位
  273. uni.$c.loading();
  274. const { data } = await getLock(params); // 获取当前定位是否进入考勤范围
  275. uni.hideLoading();
  276. this.enterRangeCode = data.code; // 0不在范围内 1在范围内
  277. this.clockAddr = data.clockAddr; // 地点名
  278. } catch (e) {
  279. uni.hideLoading();
  280. throw new Error(e);
  281. }
  282. },
  283. async getPosition() {
  284. // 获取定位
  285. try {
  286. uni.$c.loading('正在获取定位');
  287. const res = await getLocation({
  288. type: 'gcj02', // wgs84 返回 gps 坐标,gcj02 返回国测局坐标
  289. isHighAccuracy: true, // 开启高精度定位
  290. }); // 获取定位
  291. uni.hideLoading();
  292. const { latitude, longitude } = res;
  293. const params = {
  294. pointLat: latitude,
  295. pointLong: longitude,
  296. };
  297. return params;
  298. } catch (e) {
  299. uni.hideLoading();
  300. throw new Error(e);
  301. }
  302. },
  303. getCurrentTime() {
  304. const timeStr = uni.$u.timeFormat(+new Date(), 'hh:MM:ss');
  305. this.currentTime = timeStr;
  306. },
  307. timeRun() {
  308. this.getCurrentTime();
  309. clearInterval(this.timer);
  310. this.timer = setInterval(() => {
  311. this.getCurrentTime();
  312. }, 1000);
  313. },
  314. },
  315. };
  316. </script>
  317. <style scoped lang="scss">
  318. .attendance-clock {
  319. .main-container {
  320. height: 100%;
  321. display: flex;
  322. flex-direction: column;
  323. .content {
  324. padding: 28rpx;
  325. flex: 1;
  326. overflow-y: auto;
  327. .user-info-header {
  328. position: relative;
  329. display: flex;
  330. align-items: center;
  331. justify-content: space-between;
  332. background-color: #fff;
  333. padding: 30rpx;
  334. border-radius: $bd-radius;
  335. height: 70px;
  336. margin-bottom: 28rpx;
  337. .u-info {
  338. display: flex;
  339. align-items: center;
  340. }
  341. .com-fun {
  342. display: flex;
  343. align-items: center;
  344. padding: 20rpx;
  345. border-radius: 38rpx;
  346. background: #f8f9fc;
  347. opacity: 1;
  348. .fun-img {
  349. width: 30rpx;
  350. height: 30rpx;
  351. margin-right: 10rpx;
  352. }
  353. .fun-txt {
  354. color: #000048;
  355. font-size: 13px;
  356. }
  357. }
  358. .col {
  359. display: flex;
  360. flex-direction: column;
  361. margin-left: 20rpx;
  362. .u-name {
  363. font-size: 13px;
  364. color: #000000;
  365. }
  366. .u-office {
  367. display: flex;
  368. align-items: center;
  369. margin-top: 20rpx;
  370. .department {
  371. font-size: 13px;
  372. color: #8c93a3;
  373. }
  374. .grey-txt {
  375. font-size: 12px;
  376. color: #8c93a3;
  377. margin-left: 10rpx;
  378. }
  379. .position {
  380. color: #ff9935;
  381. padding: 0px 6rpx;
  382. font-size: 12px;
  383. border: 1px solid #ff9935;
  384. border-radius: 10rpx;
  385. margin-left: 10rpx;
  386. background-color: rgba(239, 140, 39, 0.1);
  387. }
  388. }
  389. }
  390. }
  391. }
  392. .footer {
  393. display: flex;
  394. margin-bottom: 20rpx;
  395. .c-button {
  396. flex: 1;
  397. }
  398. .btn-icon {
  399. width: 32rpx;
  400. height: 32rpx;
  401. margin-right: 5px;
  402. }
  403. }
  404. }
  405. .punch-container {
  406. display: flex;
  407. flex-direction: column;
  408. align-items: center;
  409. .punch-img {
  410. width: 156px;
  411. height: 127px;
  412. }
  413. .punch-time {
  414. font-size: 36px;
  415. margin-bottom: 40rpx;
  416. }
  417. .punch-footer-success {
  418. color: #436ff6;
  419. text-align: center;
  420. }
  421. .punch-footer-fail {
  422. color: #7b7e8c;
  423. text-align: center;
  424. }
  425. .class-txt {
  426. font-size: 16px;
  427. }
  428. }
  429. }
  430. </style>