OCRComponent.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <template>
  2. <view class="page">
  3. <u-popup v-model="ShowModal" :mode="mode" @close="close" :border-radius="borderRadius" :zoom="zoom">
  4. <view class="stips">
  5. <view class="tabs">
  6. <u-tabs style="justify-content: flex-start;" :list="tabslist" :show-bar="false"
  7. active-color="#232832" :is-scroll="false" :current="tabsCurrent" @change="tabschange"
  8. bar-width="60"></u-tabs>
  9. </view>
  10. <view class="upload dis j-s">
  11. <template v-if="checkType=='carInfo'">
  12. <view class="imgOcr-border">
  13. <image :src=" carfrontImg?carfrontImg:'/static/image/car-insure/carfront.png'" mode=""
  14. @click="carfrontChange"></image>
  15. </view>
  16. <view class="imgOcr-border">
  17. <image :src="carbackImg?carbackImg:'/static/image/car-insure/carback.png'" mode=""
  18. @click="carbackChange"></image>
  19. </view>
  20. </template>
  21. <template v-else>
  22. <view class="imgOcr-border">
  23. <image :src=" userfrontImg?userfrontImg:'/static/image/car-insure/userfront.png'" mode=""
  24. @click="userfrontChange"></image>
  25. </view>
  26. <view class="imgOcr-border">
  27. <image :src="userbackImg?userbackImg:'/static/image/car-insure/userback.png'" mode=""
  28. @click="userbackChange"></image>
  29. </view>
  30. </template>
  31. </view>
  32. <view class="operateBtn dis ">
  33. <view class="cancel dis a-c j-c" @click="ShowModal=false">
  34. 关闭
  35. </view>
  36. <view v-if="checkType=='carInfo'" class="confirm dis a-c j-c" @click="ocrInt">
  37. 识别
  38. </view>
  39. <view v-if="checkType=='ownerInfo'" class="confirm dis a-c j-c" @click="userocrInt1">
  40. 识别
  41. </view>
  42. <view v-if="checkType=='policyHolderInfo'" class="confirm dis a-c j-c" @click="userocrInt2">
  43. 识别
  44. </view>
  45. <view v-if="checkType=='insuredPersonInfo'" class="confirm dis a-c j-c" @click="userocrInt3">
  46. 识别
  47. </view>
  48. </view>
  49. </view>
  50. </u-popup>
  51. <view class="mask mask-show" v-if="lodingshow">
  52. <!-- 加载动画开始 -->
  53. <view class="preloader">
  54. <view class="loader"></view>
  55. </view>
  56. <!-- 加载动画结束 -->
  57. <view class="title">加载中...</view>
  58. </view>
  59. </view>
  60. </template>
  61. <script>
  62. import {
  63. getAgeByIdCard,
  64. addressCode,
  65. } from '@/plugins/utils';
  66. import store from '@/store';
  67. import {
  68. props
  69. } from 'bluebird';
  70. export default {
  71. name: 'name', //插件名称
  72. props: {
  73. showModal: {
  74. type: Boolean,
  75. required: true
  76. },
  77. mode: {
  78. type: String,
  79. default: 'bottom'
  80. },
  81. borderRadius: {
  82. type: String,
  83. default: '16'
  84. },
  85. zoom: {
  86. type: Boolean,
  87. default: false,
  88. },
  89. tabsCurrent: {
  90. type: Number,
  91. default: 0,
  92. },
  93. tabslist: Array,
  94. checkType: {
  95. type: String,
  96. required: true
  97. }
  98. },
  99. computed: {
  100. },
  101. data() {
  102. return {
  103. ShowModal: this.showModal,
  104. Current: this.tabsCurrent,
  105. carfrontImg: "",
  106. carbackImg: "",
  107. carInfoPositiveList: [],
  108. carfront: {},
  109. carback: {},
  110. userfront: {},
  111. userback: {},
  112. userfrontImg: "",
  113. userbackImg: "",
  114. ownerInfoPositiveList: [],
  115. applicantInfoPositiveList: [],
  116. InsuredInfoPositiveList: [],
  117. lodingshow: false,
  118. };
  119. },
  120. watch: {
  121. showModal(newVal) {
  122. this.ShowModal = newVal;
  123. },
  124. },
  125. methods: {
  126. isEmptyObject(obj) {
  127. return JSON.stringify(obj) === '{}';
  128. },
  129. userocrInt1() {
  130. let Info = Object.assign({}, this.userfront, this.userback)
  131. if (this.isEmptyObject(Info)) {
  132. uni.showToast({
  133. icon: 'none',
  134. title: '未检测到图片',
  135. duration: 1500
  136. });
  137. } else {
  138. let ImgList = this.ownerInfoPositiveList;
  139. let img = this.userfrontImg;
  140. this.$emit('ownerInfoCallback', {
  141. Info,
  142. ImgList,
  143. img,
  144. });
  145. this.userfrontImg = '';
  146. this.userbackImg = '';
  147. this.carfrontImg = '';
  148. this.carbackImg = '';
  149. }
  150. },
  151. userocrInt2() {
  152. let Info = Object.assign({}, this.userfront, this.userback)
  153. console.log(Info);
  154. if (this.isEmptyObject(Info)) {
  155. uni.showToast({
  156. icon: 'none',
  157. title: '未检测到图片',
  158. duration: 1500
  159. });
  160. } else {
  161. let ImgList = this.applicantInfoPositiveList;
  162. let img = this.userfrontImg;
  163. this.$emit('applicantInfoCallback', {
  164. Info,
  165. ImgList,
  166. img,
  167. });
  168. this.userfrontImg = '';
  169. this.userbackImg = '';
  170. this.carfrontImg = '';
  171. this.carbackImg = '';
  172. }
  173. },
  174. userocrInt3() {
  175. let Info = Object.assign({}, this.userfront, this.userback)
  176. if (this.isEmptyObject(Info)) {
  177. uni.showToast({
  178. icon: 'none',
  179. title: '未检测到图片',
  180. duration: 1500
  181. });
  182. } else {
  183. let ImgList = this.InsuredInfoPositiveList;
  184. let img = this.userfrontImg;
  185. this.$emit('InsuredInfoCallback', {
  186. Info,
  187. ImgList,
  188. img,
  189. });
  190. this.userfrontImg = '';
  191. this.userbackImg = '';
  192. this.carfrontImg = '';
  193. this.carbackImg = '';
  194. }
  195. },
  196. ocrInt() {
  197. let Info = Object.assign({}, this.carfront, this.carback)
  198. if (this.isEmptyObject(Info)) {
  199. uni.showToast({
  200. icon: 'none',
  201. title: '未检测到图片',
  202. duration: 1500
  203. });
  204. } else {
  205. let ImgList = this.carInfoPositiveList;
  206. let img = this.carfrontImg;
  207. this.$emit('carCallback', {
  208. Info,
  209. ImgList,
  210. img,
  211. });
  212. this.userfrontImg = '';
  213. this.userbackImg = '';
  214. this.carfrontImg = '';
  215. this.carbackImg = '';
  216. }
  217. },
  218. async carfrontChange() {
  219. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  220. count: 1,
  221. sizeType: ['compressed']
  222. });
  223. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  224. if (!size) {
  225. this.$refs.uToast.show({
  226. title: '上传图片大小不能超过 5MB!',
  227. type: 'error',
  228. })
  229. return false
  230. }
  231. if (chooseImageRes) {
  232. this.lodingshow = true;
  233. this.carfrontImg = chooseImageRes.tempFilePaths[0];
  234. uni.uploadFile({
  235. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  236. filePath: chooseImageRes.tempFilePaths[0],
  237. name: "multipartFile",
  238. formData: {
  239. 'type': 'image',
  240. },
  241. header: {
  242. Authorization: store.state.token,
  243. },
  244. success: (imgRes) => {
  245. let data = JSON.parse(imgRes.data);
  246. if (data.code == '200') {
  247. if (this.carInfoPositiveList.some(v => v.imageType == 'C01')) {
  248. this.carInfoPositiveList.map(val => {
  249. if (val.imageType == 'C01') {
  250. val.imageId = data.data.id;
  251. }
  252. })
  253. } else {
  254. this.carInfoPositiveList.push({
  255. imageId: data.data.id,
  256. imageType: "C01",
  257. })
  258. }
  259. uni.uploadFile({
  260. url: this.$base.baseUrl + '/order/identify/drivingPermit',
  261. filePath: chooseImageRes.tempFilePaths[0],
  262. name: "image1",
  263. header: {
  264. Authorization: store.state.token,
  265. },
  266. success: (uploadFileRes) => {
  267. let data = JSON.parse(uploadFileRes.data).data;
  268. let rdate = '';
  269. if (!!data.carInfo.registerDate) {
  270. rdate = data.carInfo.registerDate.substr(0,
  271. 4) +
  272. '-' + data.carInfo
  273. .registerDate.substr(4, 2) + '-' + data
  274. .carInfo
  275. .registerDate
  276. .substr(6,
  277. 2)
  278. }
  279. let isdate = '';
  280. if (!!data.carInfo.issueDate) {
  281. isdate = data.carInfo.issueDate.substr(0, 4) +
  282. '-' + data.carInfo
  283. .issueDate.substr(4, 2) + '-' + data
  284. .carInfo
  285. .issueDate.substr(6, 2)
  286. }
  287. this.carfront = {
  288. carOwner: data.carInfo.carOwner, //车主
  289. licenseNo: data.carInfo.plateNo, //车牌号
  290. modelcname: data.carInfo.backOcrID, //品牌型号
  291. frameNo: data.carInfo.vin, //车架号
  292. engineNo: data.carInfo.engine, //发动机号
  293. vinNo: data.carInfo.vin, //车架号
  294. issueDate: isdate, //发证日期
  295. registerDate: rdate, //注册日期
  296. cimodelclass: data.carInfo
  297. .plateType, //车辆种类
  298. cartype: data.carInfo.category, //车辆类型
  299. }
  300. this.lodingshow = false;
  301. }
  302. });
  303. }
  304. }
  305. });
  306. } else {
  307. this.lodingshow = false;
  308. }
  309. },
  310. async carbackChange() {
  311. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  312. count: 1,
  313. sizeType: ['compressed']
  314. });
  315. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  316. if (!size) {
  317. this.$refs.uToast.show({
  318. title: '上传图片大小不能超过 5MB!',
  319. type: 'error',
  320. })
  321. return false
  322. }
  323. if (chooseImageRes) {
  324. this.lodingshow = true;
  325. this.carbackImg = chooseImageRes.tempFilePaths[0];
  326. uni.uploadFile({
  327. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  328. filePath: chooseImageRes.tempFilePaths[0],
  329. name: "multipartFile",
  330. formData: {
  331. 'type': 'image',
  332. },
  333. header: {
  334. Authorization: store.state.token,
  335. },
  336. success: (imgRes) => {
  337. let data = JSON.parse(imgRes.data);
  338. if (data.code == '200') {
  339. if (this.carInfoPositiveList.some(v => v.imageType == 'D01')) {
  340. this.carInfoPositiveList.map(val => {
  341. if (val.imageType == 'D01') {
  342. val.imageId = data.data.id;
  343. }
  344. })
  345. } else {
  346. this.carInfoPositiveList.push({
  347. imageId: data.data.id,
  348. imageType: "D01",
  349. })
  350. }
  351. uni.uploadFile({
  352. url: this.$base.baseUrl + '/order/identify/drivingPermit',
  353. filePath: chooseImageRes.tempFilePaths[0],
  354. name: "image2",
  355. header: {
  356. Authorization: store.state.token,
  357. },
  358. success: (uploadFileRes) => {
  359. let data1 = JSON.parse(uploadFileRes.data).data;
  360. this.carback = {
  361. vehicleweight: data1.carInfo
  362. .grossMass, //总质量
  363. completeKerbMass: data1.carInfo
  364. .unladenMass, //整备质量
  365. seatCount: data1.carInfo
  366. .approvedPassengersCapacity, //核定载客数
  367. limitLoad: data1.carInfo
  368. .limitLoad, //核定载质量
  369. }
  370. this.lodingshow = false;
  371. }
  372. });
  373. }
  374. }
  375. });
  376. } else {
  377. this.lodingshow = false;
  378. }
  379. },
  380. async userfrontChange() {
  381. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  382. count: 1,
  383. sizeType: ['compressed']
  384. });
  385. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  386. if (!size) {
  387. this.$refs.uToast.show({
  388. title: '上传图片大小不能超过 5MB!',
  389. type: 'error',
  390. })
  391. return false
  392. }
  393. if (chooseImageRes) {
  394. this.lodingshow = true;
  395. this.userfrontImg = chooseImageRes.tempFilePaths[0];
  396. uni.uploadFile({
  397. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  398. filePath: chooseImageRes.tempFilePaths[0],
  399. name: "multipartFile",
  400. formData: {
  401. 'type': 'image',
  402. },
  403. header: {
  404. Authorization: store.state.token,
  405. },
  406. success: (imgRes) => {
  407. let data = JSON.parse(imgRes.data);
  408. if (data.code == '200') {
  409. switch (this.checkType) {
  410. case 'ownerInfo':
  411. if (this.ownerInfoPositiveList.some(v => v.imageType == 'C02')) {
  412. this.ownerInfoPositiveList.map(val => {
  413. if (val.imageType == 'C02') {
  414. val.imageId = data.data.id;
  415. }
  416. })
  417. } else {
  418. this.ownerInfoPositiveList.push({
  419. imageId: data.data.id,
  420. imageType: "C02",
  421. })
  422. }
  423. break;
  424. case 'policyHolderInfo':
  425. if (this.applicantInfoPositiveList.some(v => v.imageType ==
  426. 'C03')) {
  427. this.applicantInfoPositiveList.map(val => {
  428. if (val.imageType == 'C03') {
  429. val.imageId = data.data.id;
  430. }
  431. })
  432. } else {
  433. this.applicantInfoPositiveList.push({
  434. imageId: data.data.id,
  435. imageType: "C03",
  436. })
  437. }
  438. break;
  439. case 'insuredPersonInfo':
  440. if (this.InsuredInfoPositiveList.some(v => v.imageType == 'C04')) {
  441. this.InsuredInfoPositiveList.map(val => {
  442. if (val.imageType == 'C04') {
  443. val.imageId = data.data.id;
  444. }
  445. })
  446. } else {
  447. this.InsuredInfoPositiveList.push({
  448. imageId: data.data.id,
  449. imageType: "C04",
  450. })
  451. }
  452. break;
  453. default:
  454. break;
  455. };
  456. uni.uploadFile({
  457. url: this.$base.baseUrl + '/order/identify/idCard',
  458. filePath: chooseImageRes.tempFilePaths[0],
  459. name: "image1",
  460. header: {
  461. Authorization: store.state.token,
  462. },
  463. success: (uploadFileRes) => {
  464. let data = JSON.parse(uploadFileRes.data).data;
  465. this.userfront = {
  466. age: getAgeByIdCard(data.customerInfo
  467. .identifyNumber),
  468. name: data.customerInfo.name,
  469. gender: data.customerInfo
  470. .identifyIssuedCom,
  471. identifyNumber: data.customerInfo
  472. .identifyNumber,
  473. addr: data.customerInfo.addr,
  474. }
  475. this.lodingshow = false;
  476. }
  477. });
  478. }
  479. }
  480. });
  481. } else {
  482. this.lodingshow = false;
  483. }
  484. },
  485. async userbackChange() {
  486. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  487. count: 1,
  488. sizeType: ['compressed']
  489. });
  490. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  491. if (!size) {
  492. this.$refs.uToast.show({
  493. title: '上传图片大小不能超过 5MB!',
  494. type: 'error',
  495. })
  496. return false
  497. }
  498. if (chooseImageRes) {
  499. this.lodingshow = true;
  500. this.userbackImg = chooseImageRes.tempFilePaths[0];
  501. uni.uploadFile({
  502. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  503. filePath: chooseImageRes.tempFilePaths[0],
  504. name: "multipartFile",
  505. formData: {
  506. 'type': 'image',
  507. },
  508. header: {
  509. Authorization: store.state.token,
  510. },
  511. success: (imgRes) => {
  512. let data = JSON.parse(imgRes.data);
  513. if (data.code == '200') {
  514. switch (this.checkType) {
  515. case 'ownerInfo':
  516. if (this.ownerInfoPositiveList.some(v => v.imageType == 'D02')) {
  517. this.ownerInfoPositiveList.map(val => {
  518. if (val.imageType == 'D02') {
  519. val.imageId = data.data.id;
  520. }
  521. })
  522. } else {
  523. this.ownerInfoPositiveList.push({
  524. imageId: data.data.id,
  525. imageType: "D02",
  526. })
  527. }
  528. break;
  529. case 'policyHolderInfo':
  530. if (this.applicantInfoPositiveList.some(v => v.imageType ==
  531. 'D03')) {
  532. this.applicantInfoPositiveList.map(val => {
  533. if (val.imageType == 'D03') {
  534. val.imageId = data.data.id;
  535. }
  536. })
  537. } else {
  538. this.applicantInfoPositiveList.push({
  539. imageId: data.data.id,
  540. imageType: "D03",
  541. })
  542. }
  543. break;
  544. case 'insuredPersonInfo':
  545. if (this.InsuredInfoPositiveList.some(v => v.imageType == 'D04')) {
  546. this.InsuredInfoPositiveList.map(val => {
  547. if (val.imageType == 'D04') {
  548. val.imageId = data.data.id;
  549. }
  550. })
  551. } else {
  552. this.InsuredInfoPositiveList.push({
  553. imageId: data.data.id,
  554. imageType: "D04",
  555. })
  556. }
  557. break;
  558. default:
  559. break;
  560. };
  561. uni.uploadFile({
  562. url: this.$base.baseUrl + '/order/identify/idCard',
  563. filePath: chooseImageRes.tempFilePaths[0],
  564. name: "image2",
  565. header: {
  566. Authorization: store.state.token,
  567. },
  568. success: (uploadFileRes) => {
  569. let data1 = JSON.parse(uploadFileRes.data).data;
  570. let identifyValidDate = '';
  571. if (!!data1.customerInfo
  572. .identifyValidDate) {
  573. identifyValidDate = data1
  574. .customerInfo
  575. .identifyValidDate
  576. .substr(0, 4) +
  577. '-' + data1
  578. .customerInfo
  579. .identifyValidDate
  580. .substr(4, 2) + '-' +
  581. data1
  582. .customerInfo
  583. .identifyValidDate
  584. .substr(6,
  585. 2)
  586. }
  587. let identifyValidEndDate = '';
  588. if (!!data1.customerInfo
  589. .identifyValidEndDate) {
  590. identifyValidEndDate =
  591. data1.customerInfo
  592. .identifyValidEndDate
  593. .substr(0,
  594. 4) + '-' + data1
  595. .customerInfo
  596. .identifyValidEndDate
  597. .substr(4, 2) + '-' +
  598. data1
  599. .customerInfo
  600. .identifyValidEndDate
  601. .substr(6, 2)
  602. }
  603. this.userback = {
  604. identifyValidDate: identifyValidDate, //起期
  605. identifyValidEndDate: identifyValidEndDate, //止期
  606. }
  607. this.lodingshow = false;
  608. }
  609. });
  610. }
  611. }
  612. });
  613. } else {
  614. this.lodingshow = false;
  615. }
  616. },
  617. //弹框关闭
  618. close() {
  619. this.$emit('closePopup');
  620. },
  621. //tab切换
  622. tabschange(e) {
  623. this.$emit('tabschange', e);
  624. }
  625. }
  626. };
  627. </script>
  628. <!--使用scss,只在本组件生效-->
  629. <style lang="scss" scoped>
  630. .tabs {
  631. padding: 0 15px;
  632. border: 1px solid #f2f2f2;
  633. }
  634. .upload {
  635. width: 100%;
  636. height: 146px;
  637. padding: 15px 20px;
  638. background-color: white;
  639. .imgOcr-border {
  640. position: relative;
  641. width: 48%;
  642. height: 100%;
  643. padding: 2px;
  644. background-color: #fff;
  645. box-shadow: 0px 4px 10px 0px #DAE3F4;
  646. image {
  647. width: 100%;
  648. border-radius: 4px;
  649. height: 100%;
  650. }
  651. .del_btn {
  652. position: absolute;
  653. cursor: pointer;
  654. position: absolute;
  655. top: 5rpx;
  656. right: 0;
  657. width: 50rpx;
  658. height: 50rpx;
  659. border-radius: 50%;
  660. z-index: 20;
  661. }
  662. }
  663. }
  664. .operateBtn {
  665. font-weight: bold;
  666. font-size: 16px;
  667. .cancel {
  668. width: 50%;
  669. height: 46px;
  670. color: #0052FF;
  671. background-color: #EAEAEA;
  672. }
  673. .confirm {
  674. width: 50%;
  675. height: 46px;
  676. color: #fff;
  677. background-color: #0052FF;
  678. }
  679. }
  680. .mask {
  681. /* pointer-events: none; */
  682. position: fixed;
  683. z-index: 99999;
  684. top: 0;
  685. left: 0;
  686. right: 0;
  687. bottom: 0;
  688. height: 100vh;
  689. width: 100vw;
  690. display: flex;
  691. flex-direction: column;
  692. justify-content: center;
  693. align-items: center;
  694. flex-wrap: wrap;
  695. }
  696. .mask.mask-show {
  697. background: rgba(255, 255, 255, 0.3);
  698. }
  699. .title {
  700. color: #333;
  701. font-size: 28rpx;
  702. margin-top: 20rpx;
  703. }
  704. .loader {
  705. display: block;
  706. width: 120rpx;
  707. height: 120rpx;
  708. border-radius: 50%;
  709. border: 3rpx solid transparent;
  710. border-top-color: #9370db;
  711. -webkit-animation: spin 2s linear infinite;
  712. animation: spin 2s linear infinite;
  713. }
  714. .loader::before {
  715. content: "";
  716. position: absolute;
  717. top: 5rpx;
  718. left: 5rpx;
  719. right: 5rpx;
  720. bottom: 5rpx;
  721. border-radius: 50%;
  722. border: 3rpx solid transparent;
  723. border-top-color: #ba55d3;
  724. -webkit-animation: spin 3s linear infinite;
  725. animation: spin 3s linear infinite;
  726. }
  727. .loader::after {
  728. content: "";
  729. position: absolute;
  730. top: 15rpx;
  731. left: 15rpx;
  732. right: 15rpx;
  733. bottom: 15rpx;
  734. border-radius: 50%;
  735. border: 3rpx solid transparent;
  736. border-top-color: #ff00ff;
  737. -webkit-animation: spin 1.5s linear infinite;
  738. animation: spin 1.5s linear infinite;
  739. }
  740. @-webkit-keyframes spin {
  741. 0% {
  742. -webkit-transform: rotate(0deg);
  743. -ms-transform: rotate(0deg);
  744. transform: rotate(0deg);
  745. }
  746. 100% {
  747. -webkit-transform: rotate(360deg);
  748. -ms-transform: rotate(360deg);
  749. transform: rotate(360deg);
  750. }
  751. }
  752. @keyframes spin {
  753. 0% {
  754. -webkit-transform: rotate(0deg);
  755. -ms-transform: rotate(0deg);
  756. transform: rotate(0deg);
  757. }
  758. 100% {
  759. -webkit-transform: rotate(360deg);
  760. -ms-transform: rotate(360deg);
  761. transform: rotate(360deg);
  762. }
  763. }
  764. </style>