punchClocks.jsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. import React, { Component } from 'react'
  2. import { connect } from 'react-redux'
  3. import { Map, OpenData, View, Image, Text } from '@tarojs/components'
  4. import Taro from '@tarojs/taro';
  5. import { AtIcon, AtModal } from "taro-ui";
  6. import dayjs from 'dayjs';
  7. import Skeleton from 'taro-skeleton'
  8. import HistoricalClock from '../../components/historicalClock';
  9. import ImagePicker from '../../components/common/imagePicker';
  10. import { add, minus, asyncAdd } from '../../actions/counter'
  11. import { getPublicReleaseList, publicReleaseClockIn } from '../../utils/servers/servers';
  12. import './index.less'
  13. import switchIocn from '../../image/switch.png';
  14. import Location from '../../image/location.png';
  15. import PunchRange from '../../image/punchRange.png';
  16. import 'taro-ui/dist/style/components/noticebar.scss';
  17. import 'taro-ui/dist/style/components/icon.scss';
  18. import 'taro-ui/dist/style/components/list.scss';
  19. import { getClockState } from "../../utils/tools";
  20. const QQMapWX = require('../../components/common/mapSDK/qqmap-wx-jssdk.min.js');
  21. @connect(({ counter }) => ({
  22. counter
  23. }), (dispatch) => ({
  24. add() {
  25. dispatch(add())
  26. },
  27. dec() {
  28. dispatch(minus())
  29. },
  30. asyncAdd() {
  31. dispatch(asyncAdd())
  32. }
  33. }))
  34. class PunchClocks extends Component {
  35. constructor(props) {
  36. super(props);
  37. this.state = {
  38. imgs: [],
  39. nowTime: '00:00:00',
  40. newArrder: {},
  41. addressLatitude: '',
  42. addressLongitude: '',
  43. distance: '',
  44. dtails: {},
  45. loading: true
  46. }
  47. this.onChange = this.onChange.bind(this);
  48. this.getSpacing = this.getSpacing.bind(this);
  49. this.getData = this.getData.bind(this);
  50. this.publicReleaseClockIn = this.publicReleaseClockIn.bind(this);
  51. this.refresh = this.refresh.bind(this);
  52. this.getDtails = this.getDtails.bind(this);
  53. }
  54. nowTime = null;
  55. wxConfig = null;
  56. mapCtx = Taro.createMapContext('map');
  57. qqmapsdk = new QQMapWX({
  58. key: 'AWBBZ-GRAC2-JFYUO-CLA7I-JAHXK-YFFGT'
  59. });
  60. componentDidMount() {
  61. this.getDtails();
  62. }
  63. getDtails() {
  64. this.getData(() => {
  65. Taro.startLocationUpdate({
  66. success: () => {
  67. Taro.onLocationChange((v) => {
  68. this.getSpacing(v.latitude, v.longitude)
  69. })
  70. },
  71. fail: (err) => {
  72. Taro.getSetting({
  73. success: (res) => {
  74. if (!res.authSetting['scope.userLocation']) {
  75. Taro.authorize({
  76. scope: 'scope.userLocation',
  77. success: () => {
  78. },
  79. fail: () => {
  80. this.setState({
  81. isOpened: true
  82. })
  83. }
  84. })
  85. }
  86. }
  87. })
  88. this.setState({
  89. loading: false
  90. })
  91. }
  92. })
  93. });
  94. }
  95. componentWillMount() {
  96. this.wxConfig = Taro.getStorageSync('wxConfig');
  97. //获得当前位置
  98. Taro.getLocation({
  99. type: 'gcj02',
  100. isHighAccuracy: true,
  101. success: (res) => {
  102. const latitude = res.latitude
  103. const longitude = res.longitude
  104. this.setState({
  105. newArrder: {
  106. latitude,
  107. longitude
  108. },
  109. })
  110. }
  111. })
  112. this.nowTime = setInterval(() => {
  113. this.setState({
  114. nowTime: dayjs().format('HH:mm:ss')
  115. })
  116. }, 1000)
  117. Taro.eventCenter.on('punchClockComponentDidShow', () => {
  118. if (Object.keys(this.state.dtails).length === 0) {
  119. this.getDtails();
  120. }
  121. })
  122. Taro.eventCenter.on('GoPunchIn', (arg) => {
  123. this.setState({
  124. dtails: arg,
  125. addressLatitude: parseFloat(arg.latitude),
  126. addressLongitude: parseFloat(arg.longitude),
  127. }, () => {
  128. Taro.showToast({ title: '切换成功' });
  129. Taro.startLocationUpdate({
  130. success: () => {
  131. Taro.onLocationChange((v) => {
  132. this.getSpacing(v.latitude, v.longitude)
  133. })
  134. },
  135. fail: () => {
  136. this.setState({
  137. loading: false
  138. })
  139. }
  140. })
  141. })
  142. })
  143. }
  144. componentDidHide() {
  145. Taro.offLocationChange();
  146. this.nowTime && this.nowTime.clearTimeout();
  147. }
  148. publicReleaseClockIn() {
  149. if (this.state.dtails.status === 3) {
  150. Taro.hideLoading()
  151. Taro.showToast({ title: '申请已撤销,无法打卡', icon: 'none' });
  152. return;
  153. }
  154. if (Object.keys(this.state.dtails).length === 0) {
  155. Taro.hideLoading()
  156. Taro.showToast({ title: '暂无公出申请,无法打卡', icon: 'none' });
  157. return;
  158. }
  159. if (isNaN(parseInt(this.state.distance))) {
  160. Taro.hideLoading()
  161. Taro.showToast({ title: '定位错误,请联系管理员', icon: 'none' })
  162. return;
  163. }
  164. if (dayjs().isBefore(this.state.dtails.releaseStarts) || dayjs().isAfter(this.state.dtails.releaseEnds)) {
  165. Taro.hideLoading()
  166. Taro.showToast({ title: '当前时间不在打卡时间范围内,无法打卡', icon: 'none' })
  167. return;
  168. }
  169. if (!(this.state.distance <= this.wxConfig.clockInRange && this.state.distance >= 0)) {
  170. Taro.hideLoading()
  171. Taro.showToast({ title: '当前位置未处于可打卡范围,无法打卡', icon: 'none' })
  172. return;
  173. }
  174. if (this.state.imgs.length === 0) {
  175. Taro.hideLoading()
  176. Taro.showToast({ title: '请上传打卡照片', icon: 'none' })
  177. return;
  178. }
  179. publicReleaseClockIn({
  180. id: this.state.dtails.id,
  181. photoUrl: this.state.imgs.join(',')
  182. }).then(v => {
  183. Taro.hideLoading()
  184. if (v.error.length === 0) {
  185. Taro.showToast({ title: '打卡成功', icon: 'none' });
  186. this.imagePicker.clear();
  187. this.state.dtails.clockIn = 1;
  188. this.setState({
  189. dtails: this.state.dtails,
  190. imgs: []
  191. })
  192. } else {
  193. Taro.showToast({ title: v.error[0].message, icon: 'none' })
  194. }
  195. }).catch(() => {
  196. Taro.hideLoading()
  197. Taro.showToast({
  198. title: '系统错误,请稍后再试',
  199. icon: 'none'
  200. })
  201. })
  202. }
  203. getData(fn) {
  204. this.setState({
  205. loading: true
  206. })
  207. // 获取最近的打卡信息
  208. getPublicReleaseList({
  209. type: 0,
  210. clockTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
  211. }).then(v => {
  212. if (v.error.length === 0) {
  213. if (v.data.list.length === 0) {
  214. // 如果没有数据,则选择 -我的列表- 的打卡信息
  215. getPublicReleaseList({
  216. type: 0,
  217. }).then(value => {
  218. if (value.error.length === 0) {
  219. if (value.data.list.length === 0) {
  220. // 如果 -我的列表- 也没数据,则选择 -协单列表- 的打卡信息
  221. getPublicReleaseList({
  222. type: 3,
  223. }).then(ve => {
  224. if (ve.error.length === 0) {
  225. if (ve.data.list.length === 0) {
  226. this.setState({
  227. dtails: {},
  228. loading: false
  229. })
  230. } else {
  231. this.setState({
  232. dtails: ve.data.list[0],
  233. addressLatitude: ve.data.list[0].latitude,
  234. addressLongitude: ve.data.list[0].longitude,
  235. }, () => {
  236. fn();
  237. })
  238. }
  239. } else {
  240. this.setState({
  241. loading: false
  242. })
  243. Taro.showToast({
  244. title: ve.error[0].message,
  245. icon: 'none'
  246. })
  247. }
  248. }).catch(() => {
  249. this.setState({
  250. loading: false
  251. })
  252. Taro.showToast({
  253. title: '系统错误,请稍后再试',
  254. icon: 'none'
  255. })
  256. })
  257. } else {
  258. this.setState({
  259. dtails: value.data.list[0],
  260. addressLatitude: value.data.list[0].latitude,
  261. addressLongitude: value.data.list[0].longitude,
  262. }, () => {
  263. fn();
  264. })
  265. }
  266. } else {
  267. this.setState({
  268. loading: false
  269. })
  270. Taro.showToast({
  271. title: v.error[0].message,
  272. icon: 'none'
  273. })
  274. }
  275. }).catch(() => {
  276. this.setState({
  277. loading: false
  278. })
  279. Taro.showToast({
  280. title: '系统错误,请稍后再试',
  281. icon: 'none'
  282. })
  283. })
  284. } else {
  285. this.setState({
  286. dtails: v.data.list[0],
  287. addressLatitude: v.data.list[0].latitude,
  288. addressLongitude: v.data.list[0].longitude,
  289. }, () => {
  290. fn();
  291. })
  292. }
  293. } else {
  294. this.setState({
  295. loading: false
  296. })
  297. Taro.showToast({
  298. title: v.error[0].message,
  299. icon: 'none'
  300. })
  301. }
  302. }).catch((err) => {
  303. console.log(err)
  304. this.setState({
  305. loading: false
  306. })
  307. Taro.showToast({
  308. title: '系统错误,请稍后再试',
  309. icon: 'none'
  310. })
  311. })
  312. }
  313. getSpacing(latitude, longitude) {
  314. let _this = this;
  315. this.qqmapsdk.calculateDistance({
  316. mode: 'straight',
  317. from: {
  318. latitude: this.state.addressLatitude,
  319. longitude: this.state.addressLongitude,
  320. },
  321. to: [
  322. {
  323. latitude,
  324. longitude,
  325. }
  326. ],
  327. success: function (res) {//成功后的回调
  328. _this.setState({
  329. distance: res.result.elements[0].distance,
  330. loading: false
  331. })
  332. },
  333. fail: function (error) {
  334. _this.setState({
  335. loading: false
  336. })
  337. console.error(error);
  338. },
  339. complete: function (res) {
  340. }
  341. });
  342. }
  343. onChange(value, type) {
  344. let arr = this.state.imgs.concat([]);
  345. if (type === 'add') {
  346. arr.push(value)
  347. } else if (type === 'remove') {
  348. arr.splice(value, 1)
  349. }
  350. this.setState({
  351. imgs: arr,
  352. })
  353. }
  354. refresh() {
  355. this.getData(() => {
  356. Taro.startLocationUpdate({
  357. success: () => {
  358. Taro.onLocationChange((v) => {
  359. this.getSpacing(v.latitude, v.longitude)
  360. })
  361. }
  362. })
  363. });
  364. }
  365. render() {
  366. const { dtails } = this.state;
  367. return (
  368. <View className='punchClock'>
  369. <View className='header'>
  370. <Skeleton
  371. title
  372. avatar
  373. row={2}
  374. animateName='elastic'
  375. avatarShape='square'
  376. loading={this.state.loading}
  377. >
  378. <View className='headerContent'>
  379. <View className='userAvatarUrl'>
  380. <OpenData type='userAvatarUrl' />
  381. </View>
  382. <View className='infor'>
  383. <View className='name'>
  384. {
  385. Object.keys(this.state.dtails).length <= 0 ?
  386. Taro.getStorageSync('userInfor').name :
  387. dtails.aname
  388. }
  389. </View>
  390. <View className='address'>
  391. 公出企业:
  392. <View className='nickname'>
  393. {
  394. Object.keys(this.state.dtails).length <= 0 ?
  395. '无' :
  396. dtails.nickname
  397. }
  398. </View>
  399. </View>
  400. <View className='types'>
  401. {["业务公出", "技术公出", "行政公出", "技术协单"][dtails.type]}
  402. </View>
  403. </View>
  404. {
  405. Object.keys(this.state.dtails).length <= 0 ?
  406. <View onClick={this.refresh} className='state' style={{
  407. alignSelf: 'center',
  408. textAlign: 'center',
  409. color: '#b6b5b5',
  410. fontSize: '11px'
  411. }}>
  412. <AtIcon value='reload' size='20' />
  413. <View>刷新</View>
  414. </View> :
  415. <View className='state' style={{
  416. color: getClockState(dtails.status).color
  417. }}>
  418. <View style={{ textAlign: 'right' }}>
  419. {
  420. getClockState(dtails.status).title
  421. }
  422. </View>
  423. <View className='switchContent' onClick={() => {
  424. Taro.switchTab({
  425. url: '/pages/examine/index',
  426. })
  427. }}>
  428. <View className='switchTitle'>切换打卡</View>
  429. <Image src={switchIocn} className='switchItem' />
  430. </View>
  431. </View>
  432. }
  433. </View>
  434. </Skeleton>
  435. </View>
  436. {
  437. Object.keys(this.state.dtails).length <= 0 ?
  438. <View className='content'>
  439. <View className='punchClockContent' onClick={this.publicReleaseClockIn} style={{
  440. boxShadow: '1px 1px 15px 1px #acb8ad',
  441. background: '#828e83',
  442. marginTop: '85px',
  443. }}>
  444. <View className='punchClockTitle'>公出打卡</View>
  445. <View className='punchClockTime'>{this.state.nowTime}</View>
  446. </View>
  447. <View className='noData'>
  448. <View className='tipsNoData'>
  449. 暂无公出申请
  450. </View>
  451. <View className='goOut' onClick={() => {
  452. Taro.switchTab({
  453. url: '/pages/applyDepart/index',
  454. })
  455. }}>
  456. 点击前往申请公出
  457. </View>
  458. </View>
  459. </View> :
  460. <View>
  461. <View className='content'>
  462. <View className='timeContent'>
  463. <View className='timeItem'>
  464. <View className='timeTitle'>公出开始时间</View>
  465. <View className='timeValue'>{dtails.releaseStarts}</View>
  466. </View>
  467. <View className='timeItem'>
  468. <View className='timeTitle'>公出结束时间</View>
  469. <View className='timeValue'>{dtails.releaseEnds}</View>
  470. </View>
  471. </View>
  472. <View className='photographContent'>
  473. <View className='photographTitle'>打卡拍照<Text className='tips'>〔可以上传多张〕</Text></View>
  474. <AtIcon onClick={() => {
  475. Taro.chooseImage({
  476. count: 1,
  477. sizeType: ['compressed'],
  478. sourceType: ['camera'],
  479. success: (res) => {
  480. // tempFilePath可以作为img标签的src属性显示图片
  481. const tempFilePaths = res.tempFilePaths
  482. this.imagePicker.onImgChange([{ url: tempFilePaths[0] }], 'add', 0, 'camera')
  483. }
  484. })
  485. }} value='camera' size='25' color='#4395ff' />
  486. </View>
  487. <View style={{ alignSelf: 'flex-start', width: '100%' }}>
  488. <ImagePicker ref={ref => this.imagePicker = ref} url='/api/admin/release/upload' showAddBtn={false} onChange={this.onChange} />
  489. </View>
  490. <View className='punchClockContent' onClick={() => {
  491. let _this = this;
  492. Taro.showLoading({ title: '加载中...' })
  493. Taro.getSetting({
  494. success: (res) => {
  495. if (!res.authSetting['scope.userLocation']) {
  496. Taro.authorize({
  497. scope: 'scope.userLocation',
  498. success: () => {
  499. _this.publicReleaseClockIn();
  500. },
  501. fail: () => {
  502. Taro.hideLoading()
  503. this.setState({
  504. isOpened: true
  505. })
  506. }
  507. })
  508. } else {
  509. _this.publicReleaseClockIn();
  510. }
  511. }
  512. })
  513. }} style={{
  514. boxShadow:
  515. (!isNaN(parseInt(this.state.distance)))
  516. && this.state.distance <= this.wxConfig.clockInRange && this.state.distance >= 0 &&
  517. !(dayjs().isBefore(this.state.dtails.releaseStarts) || dayjs().isAfter(this.state.dtails.releaseEnds)) &&
  518. dtails.status !== 3
  519. ? (dtails.clockIn === 1 ? '1px 1px 15px 1px #3f82e8' : '1px 1px 15px 1px #659268') : '1px 1px 15px 1px #828e83',
  520. background: (!isNaN(parseInt(this.state.distance)))
  521. &&
  522. this.state.distance <= this.wxConfig.clockInRange &&
  523. this.state.distance >= 0 &&
  524. !(dayjs().isBefore(this.state.dtails.releaseStarts) || dayjs().isAfter(this.state.dtails.releaseEnds)) &&
  525. dtails.status !== 3
  526. ? (dtails.clockIn === 1 ? '#3f82e8' : '#72cb78') : '#828e83'
  527. }}>
  528. <View className='punchClockTitle'>
  529. {
  530. dtails.clockIn === 1 ?
  531. '刷新打卡' : '公出打卡'
  532. }
  533. </View>
  534. <View className='punchClockTime'>{this.state.nowTime}</View>
  535. </View>
  536. <View className='map'>
  537. <View className='locationContent'>
  538. <View className='location' onClick={() => {
  539. this.mapCtx.moveToLocation();
  540. }}>
  541. <Image src={Location} className='locationIcon' />
  542. </View>
  543. <View className='punchRange' onClick={() => {
  544. this.mapCtx.moveToLocation({
  545. latitude: this.state.addressLatitude,
  546. longitude: this.state.addressLongitude,
  547. });
  548. }}>
  549. <Image src={PunchRange} className='punchRangeIcon' />
  550. </View>
  551. </View>
  552. <Map
  553. id='map'
  554. {...this.state.newArrder}
  555. circles={[
  556. {
  557. latitude: this.state.addressLatitude,
  558. longitude: this.state.addressLongitude,
  559. color: '#c92ddc82',
  560. fillColor: '#c92ddc82',
  561. radius: this.wxConfig.clockInRange,
  562. strokeWidth: 1
  563. }
  564. ]}
  565. showLocation
  566. />
  567. </View>
  568. <View className='tips'>紫色区域为可打卡区域</View>
  569. </View>
  570. <View className='content' onClick={() => {
  571. this.setState({
  572. historicalIsOpened: true
  573. })
  574. }}>
  575. <View className='history'>
  576. <View>查看公出申请日志</View>
  577. <AtIcon value='chevron-right' size='20' color='#767272' />
  578. </View>
  579. </View>
  580. </View>
  581. }
  582. <AtModal
  583. isOpened={this.state.isOpened}
  584. title='提醒'
  585. cancelText='取消'
  586. confirmText='确认'
  587. onClose={() => {
  588. this.setState({
  589. isOpened: false,
  590. })
  591. }}
  592. onCancel={() => {
  593. this.setState({
  594. isOpened: false,
  595. })
  596. }}
  597. onConfirm={() => {
  598. this.setState({
  599. isOpened: false,
  600. }, () => {
  601. Taro.openSetting({
  602. success: (value) => {
  603. if (value.authSetting['scope.userLocation']) {
  604. Taro.getLocation({
  605. type: 'gcj02',
  606. isHighAccuracy: true,
  607. success: (res) => {
  608. const latitude = res.latitude
  609. const longitude = res.longitude
  610. this.setState({
  611. newArrder: {
  612. latitude,
  613. longitude
  614. },
  615. })
  616. }
  617. })
  618. this.getDtails();
  619. }
  620. }
  621. })
  622. })
  623. }}
  624. content='您拒绝了定位权限,请先前往设置打开定位权限'
  625. />
  626. {this.state.historicalIsOpened ? <HistoricalClock
  627. id={dtails.id}
  628. isOpened={this.state.historicalIsOpened}
  629. onClose={() => {
  630. this.setState({
  631. historicalIsOpened: false
  632. })
  633. }}
  634. /> : null}
  635. </View>
  636. )
  637. }
  638. }
  639. export default PunchClocks