OCRComponent.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  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. carnature: data.carInfo.useNature,
  307. vehicleUse: data.carInfo.vehicleUse, //车辆用途
  308. }
  309. this.lodingshow = false;
  310. }
  311. });
  312. }
  313. }
  314. });
  315. } else {
  316. this.lodingshow = false;
  317. }
  318. },
  319. async carbackChange() {
  320. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  321. count: 1,
  322. sizeType: ['compressed']
  323. });
  324. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  325. if (!size) {
  326. this.$refs.uToast.show({
  327. title: '上传图片大小不能超过 5MB!',
  328. type: 'error',
  329. })
  330. return false
  331. }
  332. if (chooseImageRes) {
  333. this.lodingshow = true;
  334. this.carbackImg = chooseImageRes.tempFilePaths[0];
  335. uni.uploadFile({
  336. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  337. filePath: chooseImageRes.tempFilePaths[0],
  338. name: "multipartFile",
  339. formData: {
  340. 'type': 'image',
  341. },
  342. header: {
  343. Authorization: store.state.token,
  344. },
  345. success: (imgRes) => {
  346. let data = JSON.parse(imgRes.data);
  347. if (data.code == '200') {
  348. if (this.carInfoPositiveList.some(v => v.imageType == 'D01')) {
  349. this.carInfoPositiveList.map(val => {
  350. if (val.imageType == 'D01') {
  351. val.imageId = data.data.id;
  352. }
  353. })
  354. } else {
  355. this.carInfoPositiveList.push({
  356. imageId: data.data.id,
  357. imageType: "D01",
  358. })
  359. }
  360. uni.uploadFile({
  361. url: this.$base.baseUrl + '/order/identify/drivingPermit',
  362. filePath: chooseImageRes.tempFilePaths[0],
  363. name: "image2",
  364. header: {
  365. Authorization: store.state.token,
  366. },
  367. success: (uploadFileRes) => {
  368. let data1 = JSON.parse(uploadFileRes.data).data;
  369. this.carback = {
  370. vehicleweight: data1.carInfo
  371. .grossMass, //总质量
  372. completeKerbMass: data1.carInfo
  373. .unladenMass, //整备质量
  374. seatCount: data1.carInfo
  375. .approvedPassengersCapacity, //核定载客数
  376. limitLoad: data1.carInfo
  377. .limitLoad, //核定载质量
  378. }
  379. this.lodingshow = false;
  380. }
  381. });
  382. }
  383. }
  384. });
  385. } else {
  386. this.lodingshow = false;
  387. }
  388. },
  389. async userfrontChange() {
  390. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  391. count: 1,
  392. sizeType: ['compressed']
  393. });
  394. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  395. if (!size) {
  396. this.$refs.uToast.show({
  397. title: '上传图片大小不能超过 5MB!',
  398. type: 'error',
  399. })
  400. return false
  401. }
  402. if (chooseImageRes) {
  403. this.lodingshow = true;
  404. this.userfrontImg = chooseImageRes.tempFilePaths[0];
  405. uni.uploadFile({
  406. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  407. filePath: chooseImageRes.tempFilePaths[0],
  408. name: "multipartFile",
  409. formData: {
  410. 'type': 'image',
  411. },
  412. header: {
  413. Authorization: store.state.token,
  414. },
  415. success: (imgRes) => {
  416. let data = JSON.parse(imgRes.data);
  417. if (data.code == '200') {
  418. switch (this.checkType) {
  419. case 'ownerInfo':
  420. if (this.ownerInfoPositiveList.some(v => v.imageType == 'C02')) {
  421. this.ownerInfoPositiveList.map(val => {
  422. if (val.imageType == 'C02') {
  423. val.imageId = data.data.id;
  424. }
  425. })
  426. } else {
  427. this.ownerInfoPositiveList.push({
  428. imageId: data.data.id,
  429. imageType: "C02",
  430. })
  431. }
  432. break;
  433. case 'policyHolderInfo':
  434. if (this.applicantInfoPositiveList.some(v => v.imageType ==
  435. 'C03')) {
  436. this.applicantInfoPositiveList.map(val => {
  437. if (val.imageType == 'C03') {
  438. val.imageId = data.data.id;
  439. }
  440. })
  441. } else {
  442. this.applicantInfoPositiveList.push({
  443. imageId: data.data.id,
  444. imageType: "C03",
  445. })
  446. }
  447. break;
  448. case 'insuredPersonInfo':
  449. if (this.InsuredInfoPositiveList.some(v => v.imageType == 'C04')) {
  450. this.InsuredInfoPositiveList.map(val => {
  451. if (val.imageType == 'C04') {
  452. val.imageId = data.data.id;
  453. }
  454. })
  455. } else {
  456. this.InsuredInfoPositiveList.push({
  457. imageId: data.data.id,
  458. imageType: "C04",
  459. })
  460. }
  461. break;
  462. default:
  463. break;
  464. };
  465. uni.uploadFile({
  466. url: this.$base.baseUrl + '/order/identify/idCard',
  467. filePath: chooseImageRes.tempFilePaths[0],
  468. name: "image1",
  469. header: {
  470. Authorization: store.state.token,
  471. },
  472. success: (uploadFileRes) => {
  473. let data = JSON.parse(uploadFileRes.data).data;
  474. this.userfront = {
  475. age: getAgeByIdCard(data.customerInfo
  476. .identifyNumber),
  477. name: data.customerInfo.name,
  478. gender: data.customerInfo
  479. .identifyIssuedCom,
  480. identifyNumber: data.customerInfo
  481. .identifyNumber,
  482. addr: data.customerInfo.addr,
  483. }
  484. this.lodingshow = false;
  485. }
  486. });
  487. }
  488. }
  489. });
  490. } else {
  491. this.lodingshow = false;
  492. }
  493. },
  494. async userbackChange() {
  495. let [chooseImageErr, chooseImageRes] = await uni.chooseImage({
  496. count: 1,
  497. sizeType: ['compressed']
  498. });
  499. let size = chooseImageRes.tempFiles[0].size / 1024 / 1024 < 5;
  500. if (!size) {
  501. this.$refs.uToast.show({
  502. title: '上传图片大小不能超过 5MB!',
  503. type: 'error',
  504. })
  505. return false
  506. }
  507. if (chooseImageRes) {
  508. this.lodingshow = true;
  509. this.userbackImg = chooseImageRes.tempFilePaths[0];
  510. uni.uploadFile({
  511. url: this.$base.baseUrl + '/ins/taskImage/uploadFile',
  512. filePath: chooseImageRes.tempFilePaths[0],
  513. name: "multipartFile",
  514. formData: {
  515. 'type': 'image',
  516. },
  517. header: {
  518. Authorization: store.state.token,
  519. },
  520. success: (imgRes) => {
  521. let data = JSON.parse(imgRes.data);
  522. if (data.code == '200') {
  523. switch (this.checkType) {
  524. case 'ownerInfo':
  525. if (this.ownerInfoPositiveList.some(v => v.imageType == 'D02')) {
  526. this.ownerInfoPositiveList.map(val => {
  527. if (val.imageType == 'D02') {
  528. val.imageId = data.data.id;
  529. }
  530. })
  531. } else {
  532. this.ownerInfoPositiveList.push({
  533. imageId: data.data.id,
  534. imageType: "D02",
  535. })
  536. }
  537. break;
  538. case 'policyHolderInfo':
  539. if (this.applicantInfoPositiveList.some(v => v.imageType ==
  540. 'D03')) {
  541. this.applicantInfoPositiveList.map(val => {
  542. if (val.imageType == 'D03') {
  543. val.imageId = data.data.id;
  544. }
  545. })
  546. } else {
  547. this.applicantInfoPositiveList.push({
  548. imageId: data.data.id,
  549. imageType: "D03",
  550. })
  551. }
  552. break;
  553. case 'insuredPersonInfo':
  554. if (this.InsuredInfoPositiveList.some(v => v.imageType == 'D04')) {
  555. this.InsuredInfoPositiveList.map(val => {
  556. if (val.imageType == 'D04') {
  557. val.imageId = data.data.id;
  558. }
  559. })
  560. } else {
  561. this.InsuredInfoPositiveList.push({
  562. imageId: data.data.id,
  563. imageType: "D04",
  564. })
  565. }
  566. break;
  567. default:
  568. break;
  569. };
  570. uni.uploadFile({
  571. url: this.$base.baseUrl + '/order/identify/idCard',
  572. filePath: chooseImageRes.tempFilePaths[0],
  573. name: "image2",
  574. header: {
  575. Authorization: store.state.token,
  576. },
  577. success: (uploadFileRes) => {
  578. let data1 = JSON.parse(uploadFileRes.data).data;
  579. let identifyValidDate = '';
  580. if (!!data1.customerInfo
  581. .identifyValidDate) {
  582. identifyValidDate = data1
  583. .customerInfo
  584. .identifyValidDate
  585. .substr(0, 4) +
  586. '-' + data1
  587. .customerInfo
  588. .identifyValidDate
  589. .substr(4, 2) + '-' +
  590. data1
  591. .customerInfo
  592. .identifyValidDate
  593. .substr(6,
  594. 2)
  595. }
  596. let identifyValidEndDate = '';
  597. if (!!data1.customerInfo
  598. .identifyValidEndDate) {
  599. identifyValidEndDate =
  600. data1.customerInfo
  601. .identifyValidEndDate
  602. .substr(0,
  603. 4) + '-' + data1
  604. .customerInfo
  605. .identifyValidEndDate
  606. .substr(4, 2) + '-' +
  607. data1
  608. .customerInfo
  609. .identifyValidEndDate
  610. .substr(6, 2)
  611. }
  612. this.userback = {
  613. identifyValidDate: identifyValidDate, //起期
  614. identifyValidEndDate: identifyValidEndDate ==
  615. '长期--' ? '9999-12-31' :
  616. identifyValidEndDate, //止期
  617. }
  618. this.lodingshow = false;
  619. }
  620. });
  621. }
  622. }
  623. });
  624. } else {
  625. this.lodingshow = false;
  626. }
  627. },
  628. //弹框关闭
  629. close() {
  630. this.$emit('closePopup');
  631. },
  632. //tab切换
  633. tabschange(e) {
  634. this.$emit('tabschange', e);
  635. }
  636. }
  637. };
  638. </script>
  639. <!--使用scss,只在本组件生效-->
  640. <style lang="scss" scoped>
  641. .tabs {
  642. padding: 0 15px;
  643. border: 1px solid #f2f2f2;
  644. }
  645. .upload {
  646. width: 100%;
  647. height: 146px;
  648. padding: 15px 20px;
  649. background-color: white;
  650. .imgOcr-border {
  651. position: relative;
  652. width: 48%;
  653. height: 100%;
  654. padding: 2px;
  655. background-color: #fff;
  656. box-shadow: 0px 4px 10px 0px #DAE3F4;
  657. image {
  658. width: 100%;
  659. border-radius: 4px;
  660. height: 100%;
  661. }
  662. .del_btn {
  663. position: absolute;
  664. cursor: pointer;
  665. position: absolute;
  666. top: 5rpx;
  667. right: 0;
  668. width: 50rpx;
  669. height: 50rpx;
  670. border-radius: 50%;
  671. z-index: 20;
  672. }
  673. }
  674. }
  675. .operateBtn {
  676. font-weight: bold;
  677. font-size: 16px;
  678. .cancel {
  679. width: 50%;
  680. height: 46px;
  681. color: #0052FF;
  682. background-color: #EAEAEA;
  683. }
  684. .confirm {
  685. width: 50%;
  686. height: 46px;
  687. color: #fff;
  688. background-color: #0052FF;
  689. }
  690. }
  691. .mask {
  692. /* pointer-events: none; */
  693. position: fixed;
  694. z-index: 99999;
  695. top: 0;
  696. left: 0;
  697. right: 0;
  698. bottom: 0;
  699. height: 100vh;
  700. width: 100vw;
  701. display: flex;
  702. flex-direction: column;
  703. justify-content: center;
  704. align-items: center;
  705. flex-wrap: wrap;
  706. }
  707. .mask.mask-show {
  708. background: rgba(255, 255, 255, 0.3);
  709. }
  710. .title {
  711. color: #333;
  712. font-size: 28rpx;
  713. margin-top: 20rpx;
  714. }
  715. .loader {
  716. display: block;
  717. width: 120rpx;
  718. height: 120rpx;
  719. border-radius: 50%;
  720. border: 3rpx solid transparent;
  721. border-top-color: #9370db;
  722. -webkit-animation: spin 2s linear infinite;
  723. animation: spin 2s linear infinite;
  724. }
  725. .loader::before {
  726. content: "";
  727. position: absolute;
  728. top: 5rpx;
  729. left: 5rpx;
  730. right: 5rpx;
  731. bottom: 5rpx;
  732. border-radius: 50%;
  733. border: 3rpx solid transparent;
  734. border-top-color: #ba55d3;
  735. -webkit-animation: spin 3s linear infinite;
  736. animation: spin 3s linear infinite;
  737. }
  738. .loader::after {
  739. content: "";
  740. position: absolute;
  741. top: 15rpx;
  742. left: 15rpx;
  743. right: 15rpx;
  744. bottom: 15rpx;
  745. border-radius: 50%;
  746. border: 3rpx solid transparent;
  747. border-top-color: #ff00ff;
  748. -webkit-animation: spin 1.5s linear infinite;
  749. animation: spin 1.5s linear infinite;
  750. }
  751. @-webkit-keyframes spin {
  752. 0% {
  753. -webkit-transform: rotate(0deg);
  754. -ms-transform: rotate(0deg);
  755. transform: rotate(0deg);
  756. }
  757. 100% {
  758. -webkit-transform: rotate(360deg);
  759. -ms-transform: rotate(360deg);
  760. transform: rotate(360deg);
  761. }
  762. }
  763. @keyframes spin {
  764. 0% {
  765. -webkit-transform: rotate(0deg);
  766. -ms-transform: rotate(0deg);
  767. transform: rotate(0deg);
  768. }
  769. 100% {
  770. -webkit-transform: rotate(360deg);
  771. -ms-transform: rotate(360deg);
  772. transform: rotate(360deg);
  773. }
  774. }
  775. </style>