OCRComponent.vue 20 KB

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