check-modal.vue 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <template>
  2. <div class="container2" :style="{ backgroundImage: `url(${bg})` }">
  3. <div class="tracking-wrap">
  4. <img class="border" :src="border" alt="" />
  5. <div
  6. class="tracking"
  7. :style="{ opacity: tabIndex === 0 ? '1' : '0' }"
  8. >
  9. <video
  10. id="video"
  11. width="620"
  12. height="460"
  13. preload
  14. autoplay
  15. loop
  16. muted
  17. ></video>
  18. <canvas id="canvas" width="620" height="460"></canvas>
  19. </div>
  20. </div>
  21. <!-- <div class="tabs">-->
  22. <!-- <div-->
  23. <!-- class="tab"-->
  24. <!-- :class="tabIndex === index? 'active' : ''"-->
  25. <!-- v-for="(item, index) in tabs" :key="index"-->
  26. <!-- @click="tabHandle(index)"-->
  27. <!-- >-->
  28. <!-- <Icon :type="item.icon" size="80" color="#fff" />-->
  29. <!-- <span class="label">{{ item.text === '扫码登录' ? (isUpdateFace ? '人脸录入' : item.text) : item.text }}</span>-->
  30. <!-- </div>-->
  31. <!-- </div>-->
  32. <div
  33. class="form"
  34. :style="{ opacity: tabIndex === 1 ? '1' : '0' }"
  35. @keyup.enter="enterHandle"
  36. >
  37. <div class="title">登录</div>
  38. <div class="content">
  39. <Form ref="form" :model="form" :rules="rules">
  40. <FormItem prop="user">
  41. <Input
  42. type="text"
  43. v-model="form.account"
  44. placeholder="请输入用户"
  45. >
  46. <Icon
  47. type="ios-person-outline"
  48. slot="prepend"
  49. ></Icon>
  50. </Input>
  51. </FormItem>
  52. <FormItem prop="pwd">
  53. <Input
  54. type="password"
  55. v-model="form.password"
  56. placeholder="请输入密码"
  57. >
  58. <Icon type="ios-lock-outline" slot="prepend" />
  59. </Input>
  60. </FormItem>
  61. </Form>
  62. </div>
  63. <div class="btn">
  64. <Button type="primary" @click="login">密码登录</Button>
  65. </div>
  66. </div>
  67. </div>
  68. </template>
  69. <script>
  70. import '@/assets/js/tracking'
  71. import '@/assets/js/face-min.js'
  72. import { Login, UpdateFace, getUserInfo } from '@/api/account'
  73. import { logOut, getStoreTitle, getToken } from '@/libs/util'
  74. import { FindByFace } from '@/api/account'
  75. import border from '_static/images/border.png'
  76. import bg from '_static/images/bg.jpg'
  77. export default {
  78. name: 'Home',
  79. data() {
  80. return {
  81. modal: false,
  82. loginTimer: null,
  83. saveFaceTimer: null,
  84. tabIndex: 1,
  85. tabs: [
  86. { icon: 'md-qr-scanner', text: '扫码登录' },
  87. { icon: 'ios-contact-outline', text: '账户登录' },
  88. ],
  89. type: '',
  90. form: {
  91. account: '',
  92. password: '',
  93. },
  94. rules: {
  95. account: [
  96. { required: true, message: '请输入用户', trigger: 'blur' },
  97. ],
  98. password: [
  99. { required: true, message: '请输入密码', trigger: 'blur' },
  100. ],
  101. },
  102. border,
  103. bg,
  104. trackerTask: null,
  105. tracking: true, // 捕获人脸中
  106. isUpdateFace: false, // 登录人脸录入
  107. video: null,
  108. tokenCopy:getToken(),
  109. }
  110. },
  111. props: {
  112. // 是否显示表单
  113. value: {
  114. type: Boolean,
  115. default: false,
  116. require: true,
  117. },
  118. },
  119. watch: {
  120. value: {
  121. handler(n) {
  122. this.$emit('input', this.value)
  123. },
  124. immediate: true,
  125. },
  126. // modal (n) {
  127. // this.$emit('input', n)
  128. // if (n) {
  129. // this.$nextTick(() => {
  130. // setTimeout(() => {
  131. // this.openCamera()
  132. // }, 300)
  133. // })
  134. // } else {
  135. // this.trackerTask.stop()
  136. // this.stopMediaStreamTrack()
  137. // }
  138. // }
  139. },
  140. methods: {
  141. enterHandle() {
  142. this.login()
  143. },
  144. login() {
  145. this.$refs.form.validate((valid) => {
  146. if (valid) {
  147. Login(this.form).then((res) => {
  148. if (res.code === 0) {
  149. this.$store.commit('setToken', res.result.token)
  150. getUserInfo().then((res) => {
  151. if (res.code === 0) {
  152. if (res.result.isBindFace) {
  153. this.toNext()
  154. } else {
  155. this.$Message.error(
  156. '当前用户未绑定人脸,请绑定人脸后登入'
  157. )
  158. this.tabIndex = 0
  159. this.tracking = true
  160. this.trackerTask.run()
  161. this.isUpdateFace = true
  162. }
  163. }
  164. })
  165. }
  166. })
  167. }
  168. })
  169. },
  170. /* 选项卡切换 */
  171. tabHandle(index) {
  172. this.tabIndex = index
  173. if (index === 0) {
  174. this.tracking = true
  175. this.trackerTask.run()
  176. } else {
  177. this.trackerTask.stop()
  178. this.tracking = false
  179. this.isUpdateFace = false
  180. clearTimeout(this.loginTimer)
  181. clearTimeout(this.saveFaceTimer)
  182. }
  183. },
  184. /* 登录成功后跳转 */
  185. toNext() {
  186. const _this = this
  187. this.$store.dispatch('getUserInfo').then((res) => {
  188. if (
  189. ['storekeeper', 'supervisor'].includes(res.result.roleKey)
  190. ) {
  191. this.$Message.success('登录成功!')
  192. this.$store.commit('setToken', _this.tokenCopy)
  193. this.$store.dispatch('getUserInfo')
  194. _this.$emit('face-check', true)
  195. return
  196. PlcStorageOut({
  197. inOutStorageNo: this.data.inOutStorageNo,
  198. storageDoor: sessionStorage.getItem('door'),
  199. plcStationCode: sessionStorage.getItem('plcCode'),
  200. rfidList: this.scanData.scanTagMesList.map(
  201. (item) => item.rfidCode
  202. ),
  203. }).then((res) => {
  204. if (res.code === 0) {
  205. this.$Message.info('出库成功!')
  206. setTimeout(() => {
  207. this.$router.push({
  208. name: 'Home',
  209. query: {
  210. plcCode:
  211. sessionStorage.getItem('plcCode'),
  212. door: sessionStorage.getItem('door'),
  213. },
  214. })
  215. }, 500)
  216. }
  217. })
  218. logOut()
  219. // this.trackerTask.stop();
  220. // this.$router.push({
  221. // name: 'warning-list',
  222. // query: {
  223. // data: this.data
  224. // }
  225. // })
  226. } else {
  227. this.$Message.error('请联系仓库员或总监处理!')
  228. }
  229. })
  230. },
  231. stopMediaStreamTrack() {
  232. if (typeof window.stream === 'object') {
  233. // this.videoEl是视频流容器,也就是video标签,需要在data中声明这个变量,然后在打开摄像头的方法中this.videoEl.srcObject = window.stream
  234. this.video.srcObject = null
  235. window.stream.stop()
  236. }
  237. },
  238. openCamera() {
  239. let _this = this
  240. let video = (this.video = document.getElementById('video'))
  241. let canvas = document.getElementById('canvas')
  242. let context = canvas.getContext('2d')
  243. context.lineWidth = 2
  244. context.strokeStyle = 'blue'
  245. let tracker = new tracking.ObjectTracker('face')
  246. tracker.setInitialScale(4)
  247. tracker.setStepSize(1)
  248. tracker.setEdgesDensity(0.1)
  249. this.trackerTask = tracking.track('#video', tracker, {
  250. camera: true,
  251. })
  252. tracker.on('track', function (event) {
  253. context.clearRect(0, 0, canvas.width, canvas.height)
  254. if (_this.tracking && event.data.length === 1) {
  255. console.log('获取人脸--')
  256. let rect = event.data[0]
  257. context.strokeRect(rect.x, rect.y, rect.width, rect.height)
  258. _this.tracking = false
  259. let cut = document.createElement('canvas')
  260. cut.width = video.width
  261. cut.height = video.height
  262. if (_this.isUpdateFace) {
  263. cut.getContext('2d').drawImage(
  264. video,
  265. rect.x - 100,
  266. rect.y - 100,
  267. rect.width + 500,
  268. rect.height + 500,
  269. 0,
  270. 0,
  271. 800,
  272. 800
  273. )
  274. } else {
  275. cut.getContext('2d').drawImage(
  276. video,
  277. rect.x - 100,
  278. rect.y - 100,
  279. rect.width + 500,
  280. rect.height + 500,
  281. 0,
  282. 0,
  283. 400,
  284. 400
  285. )
  286. }
  287. let url = cut.toDataURL('image/png')
  288. FindByFace({
  289. data: {
  290. base64: url,
  291. },
  292. sign: '_sign',
  293. source: '_source',
  294. }).then((res) => {
  295. if (
  296. res.code === 0 &&
  297. ['storekeeper', 'ckjy'].includes(res.result.roleKey)
  298. ) {
  299. _this.modal = false
  300. _this.$emit('face-check', true)
  301. } else {
  302. _this.$Message.info('请联系仓库员扫脸登录!')
  303. _this.loginTimer = setTimeout(() => {
  304. _this.tracking = true
  305. }, 2000)
  306. }
  307. })
  308. }
  309. })
  310. },
  311. },
  312. mounted() {},
  313. beforeDestroy() {
  314. if (this.trackerTask) {
  315. this.trackerTask.stop()
  316. this.stopMediaStreamTrack()
  317. }
  318. },
  319. }
  320. </script>
  321. <style lang="scss" scoped>
  322. .container2 {
  323. position: relative;
  324. width: 100vw;
  325. height: 80vh;
  326. min-width: 500px;
  327. min-height: 400px;
  328. background-repeat: no-repeat;
  329. background-size: cover;
  330. .tabs {
  331. display: none;
  332. position: absolute;
  333. bottom: 0;
  334. left: 0;
  335. width: 100%;
  336. display: flex;
  337. justify-content: center;
  338. background: rgba(5, 10, 30, 0.5);
  339. .tab {
  340. padding: 20px 40px;
  341. display: flex;
  342. flex-direction: column;
  343. align-items: center;
  344. justify-content: center;
  345. cursor: pointer;
  346. &.active {
  347. background: rgba(255, 255, 255, 0.2);
  348. transition: all 0.5s;
  349. }
  350. .label {
  351. padding: 10px;
  352. font-size: 18px;
  353. font-weight: bold;
  354. color: #ffffff;
  355. }
  356. }
  357. }
  358. .update-face {
  359. position: absolute;
  360. top: 20px;
  361. left: 20px;
  362. font-size: 20px;
  363. font-weight: bold;
  364. color: #ffffff;
  365. }
  366. }
  367. .form {
  368. z-index: 999;
  369. padding: 0 40px;
  370. position: absolute;
  371. top: 50%;
  372. left: 50%;
  373. transform: translate(-50%, -50%);
  374. width: 400px;
  375. border-radius: 10px;
  376. background: rgba(0, 0, 0, 0.5);
  377. .title {
  378. padding: 20px 0;
  379. font-size: 30px;
  380. font-weight: bold;
  381. color: #ffffff;
  382. }
  383. .btn {
  384. padding: 20px 0;
  385. }
  386. }
  387. .tracking-wrap {
  388. position: absolute;
  389. top: 50%;
  390. left: 50%;
  391. transform: translate(-50%, -50%);
  392. .border {
  393. position: absolute;
  394. top: -60px;
  395. left: -40px;
  396. width: 700px;
  397. height: 580px;
  398. }
  399. .tracking {
  400. position: relative;
  401. width: 620px;
  402. height: 460px;
  403. > * {
  404. position: absolute;
  405. left: 0;
  406. top: 0;
  407. border-radius: 10px;
  408. }
  409. video,
  410. canvas {
  411. top: 0;
  412. }
  413. }
  414. }
  415. .back {
  416. position: fixed;
  417. top: 20px;
  418. left: 20px;
  419. }
  420. </style>