clockIn.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <template>
  2. <view class="container">
  3. <!-- 时间日期信息 -->
  4. <view class="header">
  5. <text class="day">{{ currentDay }}</text>
  6. <text class="date">{{ currentDate }}</text>
  7. <text class="status">{{clockInStatus}}</text>
  8. </view>
  9. <!-- 打卡记录 -->
  10. <view class="record">
  11. <view class="record-item">
  12. <text class="ygoa-icon icon-goToWork"></text>
  13. <text class="title">上班</text>
  14. <text class="time">{{ signInTime ||'未打卡'}}</text>
  15. </view>
  16. <view class="record-item">
  17. <text class="ygoa-icon icon-afterWork"></text>
  18. <text class="title">下班</text>
  19. <text class="time">{{ signOutTime ||'未打卡'}}</text>
  20. </view>
  21. <view class="record-item" @click="getAddress">
  22. <text class="ygoa-icon icon-location"></text>
  23. <text class="title">当前位置</text>
  24. <text class="time">{{ address ||'点击获取定位'}}</text>
  25. </view>
  26. </view>
  27. <!-- 地图 -->
  28. <atl-map :mapKey="mapKey" :mapType="mapType" @confirm="confirm" :marker="marker" :polygons="polygons"
  29. :isPolygons="true">
  30. <template v-slot:content>
  31. <view style="position: absolute; bottom: 0; width: 100%; height: 24px; background-color: white">
  32. <view style="display: flex; align-items: center; justify-content: center">
  33. </view>
  34. </view>
  35. </template>
  36. </atl-map>
  37. <!-- 打卡按钮 -->
  38. <view class="footer">
  39. <button type="primary" @click="signIn">上班签到</button>
  40. <button type="default" @click="signOut">下班签退</button>
  41. </view>
  42. </view>
  43. </template>
  44. <script setup>
  45. import {
  46. onMounted,
  47. reactive,
  48. ref
  49. } from 'vue';
  50. // 导入地理位置相关函数
  51. import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
  52. import {
  53. point,
  54. polygon
  55. } from "@turf/helpers";
  56. import {
  57. CheckAttendance
  58. } from '@/api/mine/checkIn';
  59. import {
  60. useUserStore
  61. } from '@/store/user';
  62. import {createAttendance} from '@/api/mine/clockIn.js'
  63. const userStore = useUserStore();
  64. const thisUser=ref();
  65. const userId = ref('')
  66. // 组件挂载后初始化当前日期与位置
  67. onMounted(() => {
  68. thisUser.value=userStore.user;
  69. console.log('thisUser',thisUser.value);
  70. userId.value = userStore.user.useId;
  71. dateInit();
  72. getlocation();
  73. });
  74. // 当前日期和时间相关信息
  75. const currentDate = ref('2023-04-01');
  76. const currentDay = ref('星期三');
  77. const clockInStatus = ref(''); // 打卡状态
  78. const weekArr = reactive(['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']); // 星期数组
  79. const signInTime = ref(''); // 上班签到时间
  80. const signOutTime = ref(''); // 下班签到时间
  81. // 初始化日期
  82. function dateInit() {
  83. const nowDate = new Date();
  84. const year = nowDate.getFullYear(); // 获取年份
  85. const month = nowDate.getMonth() + 1; // 获取月份
  86. const day = nowDate.getDate(); // 获取日期
  87. // 格式化月份和日期为两位数
  88. const formattedMonth = month < 10 ? '0' + month : month;
  89. const formattedDay = day < 10 ? '0' + day : day;
  90. currentDate.value = `${year}-${formattedMonth}-${formattedDay}`; // 设置当前日期
  91. const dayOfWeek = nowDate.getDay(); // 获取当前星期
  92. currentDay.value = weekArr[dayOfWeek]; // 设置当前星期
  93. getTodayAtt();
  94. }
  95. //获取今天考勤状态
  96. function getTodayAtt() {
  97. const params = {
  98. universalid: userId.value,
  99. rizi: currentDate.value
  100. }
  101. CheckAttendance(params).then(res => {
  102. if ("success" == res.returnMsg) {
  103. // console.log("kaoqin",res);
  104. const attList = res.returnParams.list;
  105. const time1 = attList.find(item => item.att_type_id === '1');
  106. const time2 = attList.find(item => item.att_type_id === '2');
  107. if (time1 !== undefined) {
  108. signInTime.value = time1.att_time.split(' ')[1];
  109. }
  110. if (time2 !== undefined) {
  111. signOutTime.value = time2.att_time.split(' ')[1];
  112. }
  113. }
  114. })
  115. }
  116. const attType=ref(0)
  117. // 上班签到
  118. function signIn() {
  119. const now = getNowTime();
  120. //判断打卡时间是否符合
  121. if (!isTimeInRange(now, '08:30:00', '10:00:00')) {
  122. uni.showToast({
  123. title: "不在签到时间8:30--10:00内",
  124. icon: 'none'
  125. });
  126. return;
  127. }
  128. attType.value=1;
  129. getAddress();
  130. clockIn(); // 执行打卡
  131. };
  132. // 下班签退
  133. function signOut() {
  134. const now = getNowTime();
  135. //判断打卡时间是否符合
  136. if (!isTimeInRange(now, '15:00:00', '20:00:00')) {
  137. uni.showToast({
  138. title: "不在签退时间15:00--20:00内",
  139. icon: 'none'
  140. });
  141. return;
  142. }
  143. attType.value=2;
  144. getAddress();
  145. clockIn(); // 执行打卡并获取结果
  146. };
  147. //获取当前时间
  148. function getNowTime() {
  149. var now = new Date();
  150. // 获取小时、分钟和秒
  151. var hours = now.getHours(); // 获取小时(0-23)
  152. var minutes = now.getMinutes(); // 获取分钟(0-59)
  153. var seconds = now.getSeconds(); // 获取秒(0-59)
  154. // 将小时、分钟或秒格式化为两位数
  155. hours = hours < 10 ? '0' + hours : hours;
  156. minutes = minutes < 10 ? '0' + minutes : minutes;
  157. seconds = seconds < 10 ? '0' + seconds : seconds;
  158. return `${hours}:${minutes}:${seconds}`;
  159. }
  160. //判断时间是否在区间内
  161. function isTimeInRange(time, startTime, endTime) {
  162. // 将时间字符串转换为Date对象
  163. var timeObj = new Date('1970-01-01T' + time + 'Z');
  164. var startObj = new Date('1970-01-01T' + startTime + 'Z');
  165. var endObj = new Date('1970-01-01T' + endTime + 'Z');
  166. // 比较时间
  167. return timeObj >= startObj && timeObj <= endObj;
  168. }
  169. // 地图配置信息
  170. const mapKey = ref('KJBBZ-5JCLZ-3NLXD-742CK-Y26UZ-X7BJN'); // 地图API密钥
  171. const mapType = ref('tmap'); // 地图类型
  172. const address = ref(''); // 初始化地址
  173. // 打卡定位信息
  174. const longitude = ref(0); // 经度
  175. const latitude = ref(0); // 纬度
  176. // 标记信息
  177. const marker = reactive({
  178. id: 1,
  179. latitude: 25.958812,
  180. longitude: 119.213036,
  181. width: 50, // 标记宽度
  182. height: 50, // 标记高度
  183. title: '宇光同行' // 标记标题
  184. });
  185. // 获取当前定位经纬度
  186. function getlocation() {
  187. wx.getLocation({
  188. // type: 'gcj02',
  189. success: (res) => {
  190. // console.log('当前位置的经度:' + JSON.stringify(res));
  191. // console.log('当前位置的经度:','gcj02' + JSON.stringify(res));
  192. console.log('当前位置的经度:' + res.longitude);
  193. console.log('当前位置的纬度:' + res.latitude);
  194. longitude.value = res.longitude; // 保存经度
  195. latitude.value = res.latitude; // 保存纬度
  196. },
  197. fail: (err) => {
  198. // 获取位置失败时提示用户打开权限
  199. uni.showModal({
  200. content: '检测到您没打开获取位置功能权限,是否去设置打开?',
  201. confirmText: "确认",
  202. cancelText: '取消',
  203. success: (res) => {
  204. if (res.confirm) {
  205. uni.openSetting({
  206. success: (res) => {
  207. uni.showToast({
  208. title: '授权后请重新打开此页面',
  209. icon: 'none'
  210. });
  211. },
  212. fail: (err) => {
  213. // console.log(err);
  214. }
  215. });
  216. } else {
  217. uni.showToast({
  218. title: '获取地理位置授权失败',
  219. icon: 'none',
  220. success: () => {
  221. // 返回上一页
  222. setTimeout(() => {
  223. uni.showToast({
  224. title: "返回上一页",
  225. icon: 'none'
  226. });
  227. }, 500);
  228. }
  229. });
  230. }
  231. }
  232. });
  233. },
  234. });
  235. }
  236. // 获取当前位置地址
  237. async function getAddress() {
  238. getlocation(); // 获取当前位置
  239. tranAddress(); // 执行地址转换
  240. }
  241. // 经纬度转地址
  242. function tranAddress() {
  243. let locationStr = latitude.value + ',' + longitude.value; // 组合经纬度
  244. uni.request({
  245. url: 'https://apis.map.qq.com/ws/geocoder/v1/?location=' + locationStr + '&key=' + mapKey.value +
  246. '&get_poi=1',
  247. method: 'GET',
  248. success: function(res) {
  249. console.log('请求成功', res.data);
  250. address.value = res.data.result.address; // 保存地址
  251. },
  252. fail: function(err) {
  253. console.error('请求失败', err); // 请求错误处理
  254. }
  255. });
  256. }
  257. // 多边形区域设置
  258. const polygons = reactive([{
  259. points: [{
  260. latitude: 25.9591401,
  261. longitude: 119.21292356
  262. },
  263. {
  264. latitude: 25.95828592,
  265. longitude: 119.21261955
  266. },
  267. {
  268. latitude: 25.9576709,
  269. longitude: 119.21458294
  270. },
  271. {
  272. latitude: 25.95845106,
  273. longitude: 119.21486162
  274. },
  275. {
  276. latitude: 25.9591401,
  277. longitude: 119.21292356
  278. }
  279. ],
  280. strokeWidth: 1, // 边框宽度
  281. strokeColor: "#ff000066", // 边框颜色
  282. fillColor: "#ff000016", // 填充颜色
  283. }]);
  284. // 打卡
  285. function clockIn() {
  286. //TODO: 发起打卡请求相关业务逻辑
  287. var now = new Date();
  288. // 使用toISOString()获取一个ISO格式的字符串,然后替换T为空格,并去除最后的Z(表示UTC时间)
  289. var formatted = now.toISOString().replace('T', ' ').replace('Z', '');
  290. var localString = new Date(formatted + '+08:00').toISOString().replace('T', ' ').replace('Z', '');
  291. const params = {
  292. user_name: thisUser.value.userName,
  293. user_id: thisUser.value.useId,
  294. kaoqin_type: attType.value,
  295. now_date: localString,
  296. longitude: longitude.value,
  297. latitude: latitude.value,
  298. address: address.value,
  299. unitId: thisUser.value.unitId
  300. };
  301. createAttendance(params).then(res=>{
  302. // console.log('createAttendance',res);
  303. if ("success"==res.returnMsg) {
  304. uni.showToast({
  305. title: "打卡成功",
  306. icon: 'none'
  307. });
  308. getTodayAtt(); //更新今日考勤数据
  309. } else {
  310. uni.showToast({
  311. title: "未在指定范围,打卡失败",
  312. icon: 'none'
  313. });
  314. }
  315. })
  316. // const _polygons = polygons.map((polygon) => {
  317. // return polygon.points.map((i) => [
  318. // Number(i.longitude),
  319. // Number(i.latitude),
  320. // ]);
  321. // });
  322. // const _point = point([longitude.value, latitude.value]); // 用当前坐标创建点
  323. // const _polygon = polygon(_polygons); // 创建多边形
  324. // const f = booleanPointInPolygon(_point, _polygon); // 判断点是否在多边形内
  325. }
  326. </script>
  327. <style>
  328. @import "@/static/font/ygoa/iconfont.css";
  329. .ygoa-icon {
  330. margin-right: 20rpx;
  331. font-size: 50rpx;
  332. }
  333. .container {
  334. display: flex;
  335. flex-direction: column;
  336. align-items: center;
  337. padding: 20rpx;
  338. }
  339. .header {
  340. width: 100%;
  341. height: 200rpx;
  342. background-color: #f8f8f8;
  343. display: flex;
  344. flex-direction: column;
  345. align-items: center;
  346. justify-content: center;
  347. }
  348. .day {
  349. font-size: 55rpx;
  350. font-weight: bold;
  351. }
  352. .date {
  353. font-size: 30rpx;
  354. color: #666;
  355. }
  356. .status {
  357. font-size: 24rpx;
  358. color: black;
  359. }
  360. .record {
  361. width: 100%;
  362. margin-top: 20rpx;
  363. border-top: 1px solid #ddd;
  364. border-bottom: 1px solid #ddd;
  365. padding: 0 15rpx;
  366. }
  367. .record-item {
  368. display: flex;
  369. align-items: center;
  370. height: 100rpx;
  371. border-bottom: 1px solid #eee;
  372. }
  373. .icon {
  374. width: 50rpx;
  375. height: 50rpx;
  376. margin-right: 10rpx;
  377. }
  378. .title {
  379. font-size: 28rpx;
  380. }
  381. .time {
  382. font-size: 24rpx;
  383. color: #999;
  384. margin-left: auto;
  385. }
  386. .map {
  387. width: 100%;
  388. height: 500rpx;
  389. margin-top: 20rpx;
  390. }
  391. .footer {
  392. width: 100%;
  393. margin-top: 20rpx;
  394. display: flex;
  395. justify-content: space-around;
  396. }
  397. button {
  398. width: 45%;
  399. }
  400. </style>