App.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. <template>
  2. <div
  3. v-if="!showSisView"
  4. id="screen"
  5. :style="{
  6. width: `${style.width}px`,
  7. height: `${style.height}px`,
  8. transform: `${style.transform}`,
  9. }"
  10. >
  11. <div v-if="isLogined" class="main">
  12. <div class="header-body" v-if="hideHeard === '0'">
  13. <div class="header-title" @click="handleClickJump()">
  14. <!-- <img v-if="$store.state.themeName === 'dark'" src="./assets/projectLogo.png" alt="" />
  15. <img v-if="$store.state.themeName === 'light'" src="./assets/light-projectLogo.png" alt="" /> -->
  16. <span
  17. :style="
  18. $store.state.themeName === 'dark'
  19. ? 'color:#fff; font-size:18px;font-family: SimHei'
  20. : 'color:#000'
  21. "
  22. >
  23. >>发电场站生产实时运营管理平台</span
  24. >
  25. </div>
  26. <div class="header-menu-body">
  27. <Header @onMenuClick="HeaderMenuClick" />
  28. </div>
  29. </div>
  30. <div
  31. class="menu-body"
  32. :class="{ hover: isFixed ? true : isShowMenu }"
  33. @mouseenter="showMenu"
  34. @mouseleave="hideMenu"
  35. v-show="
  36. $store.state.themeName === 'dark' && $store.state.menuData.length
  37. "
  38. v-if="hideMenus === '0'"
  39. >
  40. <Menu :root="root" />
  41. </div>
  42. <div>
  43. <el-menu
  44. class="lightMenu"
  45. :class="$store.state.themeName === 'light' ? 'show' : 'hidden'"
  46. :collapse="true"
  47. text-color="#ffffff"
  48. active-text-color="#6262a2"
  49. background-color="#36348e"
  50. @select="selectMenu"
  51. v-if="hideMenus === '0' && $store.state.menuData.length"
  52. >
  53. <el-sub-menu
  54. :index="index"
  55. :title="item.text"
  56. v-for="(item, index) in menuData"
  57. :key="index"
  58. >
  59. <template #title>
  60. <router-link :to="item.path">
  61. <el-icon>
  62. <SvgIcon :svgid="item.icon" />
  63. </el-icon>
  64. </router-link>
  65. </template>
  66. <el-menu-item-group
  67. v-for="(menu, idx) in item.children"
  68. :index="idx"
  69. :key="idx"
  70. >
  71. <router-link :to="menu.path">
  72. <el-menu-item :index="index + '-' + idx">{{
  73. menu.text
  74. }}</el-menu-item>
  75. </router-link>
  76. </el-menu-item-group>
  77. </el-sub-menu>
  78. </el-menu>
  79. </div>
  80. <div
  81. class="main-body"
  82. :style="{ paddingLeft: isFixed && menuLength > 0 ? '52px' : 0 }"
  83. >
  84. <router-view />
  85. </div>
  86. </div>
  87. <div v-else class="login"><login-page @onLogin="login" /></div>
  88. </div>
  89. <!-- <div v-else-if="!isLogined" class="login">
  90. <login-page />
  91. </div> -->
  92. <!-- <login-page v-if="!showSisView && !isLogined" @onLogin="login" /> -->
  93. <!-- <div v-else-if="token == ''">
  94. {{token}}
  95. <login-page @onLogin="login" />
  96. </div> -->
  97. <!-- <sisView v-else /> -->
  98. </template>
  99. <script>
  100. // 导入header.vue文件
  101. import Menu from "@/views/layout/Menu.vue";
  102. import Header from "@/views/layout/Header.vue";
  103. import LoginPage from "./views/layout/login-page.vue";
  104. import sisView from "./views/sisView/index.vue";
  105. import { GetBoosterlist } from "@/api/factoryMonitor/index.js";
  106. import { getApiWeatherstation } from "@/api/monthlyPerformanceAnalysis";
  107. import SvgIcon from "@com/coms/icon/svg-icon.vue";
  108. import $ from "jquery";
  109. export default {
  110. components: {
  111. Menu,
  112. Header,
  113. LoginPage,
  114. sisView,
  115. SvgIcon,
  116. },
  117. data() {
  118. return {
  119. isShowMenu: false,
  120. // 当前子系统
  121. root: "",
  122. // isLogined: localStorage.getItem("loginState") || false,
  123. showSisView: false,
  124. memuCloseTimeout: null,
  125. menuData: [],
  126. hideMenus: "0",
  127. hideHeard: "0",
  128. style: {
  129. width: "1920",
  130. height: "1080",
  131. fontsize: "16px",
  132. transform: "scaleY(1) scaleX(1) translate(-50%, -50%)",
  133. },
  134. // websocket相关
  135. socketObj: "", // websocket实例对象
  136. //心跳检测
  137. heartCheck: {
  138. vueThis: this, // vue实例
  139. timeout: 30000, // 超时时间
  140. timeoutObj: null, // 计时器对象——向后端发送心跳检测
  141. serverTimeoutObj: null, // 计时器对象——等待后端心跳检测的回复
  142. // 心跳检测重置
  143. reset: function () {
  144. clearTimeout(this.timeoutObj);
  145. clearTimeout(this.serverTimeoutObj);
  146. return this;
  147. },
  148. // 心跳检测启动
  149. start: function () {
  150. this.timeoutObj && clearTimeout(this.timeoutObj);
  151. this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
  152. this.timeoutObj = setTimeout(() => {
  153. // 这里向后端发送一个心跳检测,后端收到后,会返回一个心跳回复
  154. this.vueThis.socketObj.send("HeartBeat");
  155. console.log("发送心跳检测");
  156. this.serverTimeoutObj = setTimeout(() => {
  157. // 如果超过一定时间还没重置计时器,说明websocket与后端断开了
  158. console.log("未收到心跳检测回复");
  159. // 关闭WebSocket
  160. this.vueThis.socketObj.close();
  161. }, this.timeout);
  162. }, this.timeout);
  163. },
  164. },
  165. socketReconnectTimer: null, // 计时器对象——重连
  166. socketReconnectLock: false, // WebSocket重连的锁
  167. socketLeaveFlag: false, // 离开标记(解决 退出登录再登录 时出现的 多次相同推送 问题,出现的本质是多次建立了WebSocket连接)
  168. alarmList: {},
  169. };
  170. },
  171. computed: {
  172. isLogined() {
  173. return this.$store.state.user?.loginState;
  174. },
  175. isFixed() {
  176. return this.$store.state.isFixed;
  177. },
  178. menuLength() {
  179. return this.$store.state.menuData.length;
  180. },
  181. },
  182. created() {
  183. let that = this;
  184. const themeName = that.$store.state.themeName;
  185. $("#appBody").attr(
  186. "class",
  187. themeName === "dark" || themeName === "light" ? themeName : "dark"
  188. );
  189. },
  190. mounted() {
  191. let that = this;
  192. that.setScale();
  193. /*窗口改变事件*/
  194. $(window).resize(() => {
  195. that.setScale();
  196. });
  197. if (!this.socketLeaveFlag) {
  198. // 没有离开——重连
  199. // websocket重连
  200. this.socketReconnect();
  201. }
  202. },
  203. unmounted() {
  204. console.log("离开标记", this.socketLeaveFlag);
  205. },
  206. methods: {
  207. getScale() {
  208. const w = window.innerWidth / this.style.width;
  209. const h = window.innerHeight / this.style.height;
  210. const d = window.devicePixelRatio;
  211. // let f = 16;
  212. // if (d > 1) {
  213. // f = 18;
  214. // }
  215. return { x: w, y: h };
  216. },
  217. setScale() {
  218. let scale = this.getScale();
  219. this.style.transform =
  220. "scaleY(" + scale.y + ") scaleX(" + scale.x + ") translate(-50%, -50%)";
  221. // this.style.fontsize = scale.f;
  222. },
  223. // 切换子系统事件
  224. HeaderMenuClick(data) {
  225. this.root = data.id;
  226. },
  227. showMenu() {
  228. if (!this.isFixed) {
  229. this.isShowMenu = true;
  230. this.memuCloseTimeout && clearTimeout(this.memuCloseTimeout);
  231. }
  232. },
  233. hideMenu() {
  234. if (!this.isFixed) {
  235. const that = this;
  236. this.memuCloseTimeout = setTimeout(function () {
  237. that.isShowMenu = false;
  238. }, 500);
  239. }
  240. },
  241. getBooster() {
  242. GetBoosterlist().then((res) => {
  243. if (res.data && res.data.code == 200) {
  244. this.$store.commit("changeBooster", res.data.data);
  245. }
  246. });
  247. },
  248. // 获取测风塔
  249. async getCftlist() {
  250. const { data: datas } = await getApiWeatherstation();
  251. console.log(datas);
  252. this.$store.commit("changeCft", datas.data);
  253. },
  254. login() {
  255. this.$store.commit("user/SET_LOGINSTATE", true);
  256. this.getBooster();
  257. this.getCftlist();
  258. // websocket启动
  259. this.createWebSocket();
  260. },
  261. selectMenu(menuIndex) {
  262. this.menuIndex = menuIndex;
  263. },
  264. // websocket启动
  265. createWebSocket() {
  266. // let webSocketLink = `ws://192.168.1.102:6014/websocketBt/${this.$store.state.user.userId}_${this.$store.state.user.authToken}`;
  267. let webSocketLink = `ws://10.81.3.154:6014/websocketBt/${this.$store.state.user.userId}_${this.$store.state.user.authToken}`; // webSocket地址
  268. try {
  269. if ("WebSocket" in window) {
  270. this.socketObj = new WebSocket(webSocketLink);
  271. }
  272. // websocket事件绑定
  273. this.socketEventBind();
  274. } catch (e) {
  275. console.log("catch" + e);
  276. // websocket重连
  277. this.socketReconnect();
  278. }
  279. },
  280. // websocket事件绑定
  281. socketEventBind() {
  282. // 连接成功建立的回调
  283. this.socketObj.onopen = this.onopenCallback;
  284. // 连接发生错误的回调
  285. this.socketObj.onerror = this.onerrorCallback;
  286. // 连接关闭的回调
  287. this.socketObj.onclose = this.oncloseCallback;
  288. // 向后端发送数据的回调
  289. this.socketObj.onsend = this.onsendCallback;
  290. // 接收到消息的回调
  291. this.socketObj.onmessage = this.getMessageCallback;
  292. //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
  293. window.onbeforeunload = () => {
  294. this.socketObj.close();
  295. };
  296. },
  297. // websocket重连
  298. socketReconnect() {
  299. if (this.socketReconnectLock) {
  300. return;
  301. }
  302. this.socketReconnectLock = true;
  303. this.socketReconnectTimer && clearTimeout(this.socketReconnectTimer);
  304. this.socketReconnectTimer = setTimeout(() => {
  305. console.log("WebSocket:重连中...");
  306. this.socketReconnectLock = false;
  307. // websocket启动
  308. this.createWebSocket();
  309. }, 4000);
  310. },
  311. // 连接成功建立的回调
  312. onopenCallback: function (event) {
  313. console.log("WebSocket:已连接");
  314. // 心跳检测重置
  315. this.heartCheck.reset().start();
  316. },
  317. // 连接发生错误的回调
  318. onerrorCallback: function (event) {
  319. console.log("WebSocket:发生错误");
  320. // websocket重连
  321. this.socketReconnect();
  322. },
  323. // 连接关闭的回调
  324. oncloseCallback: function (event) {
  325. console.log("WebSocket:已关闭");
  326. // 心跳检测重置
  327. this.heartCheck.reset();
  328. if (!this.socketLeaveFlag) {
  329. // 没有离开——重连
  330. // websocket重连
  331. this.socketReconnect();
  332. }
  333. },
  334. // 向后端发送数据的回调
  335. onsendCallback: function () {
  336. console.log("WebSocket:发送信息给后端");
  337. },
  338. // 接收到消息的回调
  339. getMessageCallback: function (msg) {
  340. console.log(msg);
  341. if (Object.keys(msg) && msg.data == "ok") {
  342. // 心跳回复——心跳检测重置
  343. // 收到心跳检测回复就说明连接正常
  344. console.log("收到心跳检测回复");
  345. // 心跳检测重置
  346. this.heartCheck.reset().start();
  347. } else {
  348. // 普通推送——正常处理
  349. console.log("收到推送消息");
  350. let data = JSON.parse(msg.data);
  351. // 相关处理
  352. console.log(data);
  353. this.alarmList = data;
  354. this.$store.commit("changeAlarmlist", data);
  355. }
  356. },
  357. },
  358. watch: {
  359. $route(res) {
  360. this.showSisView = res.fullPath === "/sisView";
  361. this.hideHeard = res.query.hideheard || "0";
  362. this.hideMenus = res.query.hidemenus || "0";
  363. if (res.query.theme) {
  364. const theme = res.query.theme === "dark" ? "dark" : "light";
  365. this.$store.dispatch("changeTheme", theme);
  366. $("#appBody").attr("class", theme);
  367. }
  368. if (res.query.fn) {
  369. this[res.query.fn] && this[res.query.fn]();
  370. }
  371. let ActiveModule = null;
  372. this.menuData.forEach((pEle) => {
  373. if (pEle.path === res.fullPath) {
  374. ActiveModule = pEle;
  375. }
  376. pEle?.children?.forEach((cEle) => {
  377. if (cEle.path === res.fullPath) {
  378. ActiveModule = cEle;
  379. }
  380. });
  381. });
  382. if (ActiveModule) {
  383. this.$store.dispatch("changeModuleName", ActiveModule.text);
  384. }
  385. },
  386. "$store.state.menuData"(res) {
  387. this.menuData = res;
  388. },
  389. isLogined: {
  390. handler(res) {
  391. if (!res && this.socketObj) {
  392. // 离开标记
  393. this.socketLeaveFlag = true;
  394. // 关闭WebSocket
  395. this.socketObj.close();
  396. }
  397. },
  398. immediate: true,
  399. },
  400. "$store.state.moudleName"(msg) {
  401. if (window.__MODE__.showModuleName && msg) {
  402. this.BASE.showMsg({
  403. type: this.$store.state.themeName === "dark" ? "success" : "warning",
  404. showClose: true,
  405. msg,
  406. });
  407. }
  408. },
  409. },
  410. };
  411. </script>
  412. <style lang="less">
  413. @import "~@/assets/styles/main.less";
  414. #screen {
  415. z-index: 100;
  416. transform-origin: 0 0;
  417. position: fixed;
  418. left: 50%;
  419. top: 50%;
  420. transition: 0.3s;
  421. }
  422. * {
  423. box-sizing: border-box;
  424. &::-webkit-scrollbar {
  425. width: 6px;
  426. height: 8px;
  427. }
  428. &::-webkit-scrollbar-track-piece {
  429. background-color: rgba(255, 255, 255, 0.05);
  430. border-radius: 4px;
  431. }
  432. &::-webkit-scrollbar-thumb {
  433. background-color: fade(@gray, 75);
  434. border-radius: 4px;
  435. outline: none;
  436. outline-offset: 0px;
  437. border: none;
  438. }
  439. }
  440. body {
  441. margin: 0;
  442. background: #fff;
  443. color: #fff;
  444. // background-image: url("./assets/background.png");
  445. background: rgb(4, 12, 11);
  446. background-size: cover;
  447. font-size: @fontsize;
  448. font-family: @defalut-font-family;
  449. }
  450. @menuWidth: 51.28px;
  451. @headerHeight: 59px;
  452. .main {
  453. width: 100%;
  454. height: 100%;
  455. display: flex;
  456. flex-wrap: wrap;
  457. overflow: hidden;
  458. .header-body {
  459. z-index: 2;
  460. // background: radial-gradient(closest-corner at 22% 40%, #2d5a47, #040d0a, #040d0a);
  461. // flex: 0 0 100%;
  462. width: 100%;
  463. display: flex;
  464. flex-direction: row;
  465. height: @headerHeight;
  466. border-bottom: 1px solid #142b29;
  467. .header-title {
  468. padding-left: 20px;
  469. margin: auto;
  470. color: #fff;
  471. }
  472. .header-menu-body {
  473. flex-grow: 1;
  474. }
  475. }
  476. .menu-body {
  477. position: absolute;
  478. display: flex;
  479. flex-direction: column;
  480. align-items: center;
  481. justify-content: space-between;
  482. flex: 0 0 @menuWidth;
  483. width: @menuWidth;
  484. height: calc(100% - @headerHeight);
  485. // height: calc(100vh - 59px);
  486. top: @headerHeight;
  487. // top: 59px;
  488. background-color: fade(#192a26, 75%);
  489. z-index: 2002;
  490. opacity: 0;
  491. transition: opacity 0.2s;
  492. transition-timing-function: ease-in;
  493. // transform: translate(-@menuWidth);
  494. &:hover,
  495. &.hover {
  496. opacity: 1;
  497. transition: opacity 0.2s;
  498. transition-timing-function: ease-out;
  499. transform: translate(0);
  500. }
  501. }
  502. .main-body {
  503. flex: 0 0 100%;
  504. max-width: 100%;
  505. height: calc(100% - @headerHeight);
  506. // padding: 1.481vh;
  507. // transition: flex 0.1s, margin-left 0.1s;
  508. // transition-timing-function: ease-in-out;
  509. // &.show-menu {
  510. // flex: 0 0 calc(100vw - @menuWidth);
  511. // margin-left: @menuWidth;
  512. // transition: flex 0.1s, margin-left 0.1s;
  513. // transition-timing-function: ease-in-out;
  514. // }
  515. }
  516. .el-table__body tr.current-row > td {
  517. color: #fff;
  518. background: rgba(66, 66, 66, 0.66) !important;
  519. }
  520. .el-transfer-panel {
  521. width: 450px !important;
  522. height: 73vh;
  523. background-color: #111d1c !important;
  524. border: 1px solid #999999 !important;
  525. .el-transfer-panel__body {
  526. height: 100% !important;
  527. .el-transfer-panel__list {
  528. height: 100% !important;
  529. }
  530. }
  531. }
  532. .el-transfer-panel .el-transfer-panel__header {
  533. background-color: #111d1c !important;
  534. color: #05bb4c !important;
  535. .el-checkbox .el-checkbox__label {
  536. color: #05bb4c !important;
  537. }
  538. }
  539. .el-button--primary.is-disabled,
  540. .el-button--primary.is-disabled:active,
  541. .el-button--primary.is-disabled:focus,
  542. .el-button--primary.is-disabled:hover {
  543. background-color: #05bb4c;
  544. border-color: #05bb4c;
  545. }
  546. }
  547. .login {
  548. width: 100%;
  549. height: 100%;
  550. background: url("~@/assets/login-bg.png") no-repeat;
  551. background-size: cover;
  552. position: relative;
  553. }
  554. .el-tree-node__content {
  555. height: 40px !important;
  556. }
  557. .el-tree-node__label {
  558. font-size: 14px !important;
  559. }
  560. .el-pagination.is-background .el-pager li:not(.disabled).active {
  561. background-color: #05bb4c !important;
  562. }
  563. .el-table__body tr.hover-row > td.el-table__cell {
  564. background-color: rgba(2, 2, 2) !important;
  565. }
  566. #appBody.light .el-table th.el-table__cell > .cell {
  567. height: 8.2vh !important;
  568. }
  569. .power-benchmarking-page
  570. .top
  571. .top-left
  572. .table.el-table
  573. thead
  574. tr:last-child
  575. th
  576. .cell {
  577. height: 116px !important;
  578. }
  579. * {
  580. -webkit-touch-callout: none; /*系统默认菜单被禁用*/
  581. -webkit-user-select: none; /*webkit浏览器*/
  582. -khtml-user-select: none; /*早期浏览器*/
  583. -moz-user-select: none; /*火狐*/
  584. -ms-user-select: none; /*IE10*/
  585. user-select: none;
  586. }
  587. input {
  588. -webkit-user-select: auto; /*webkit浏览器*/
  589. }
  590. textarea {
  591. -webkit-user-select: auto; /*webkit浏览器*/
  592. }
  593. .gfSelect .el-input__suffix {
  594. display: none !important;
  595. }
  596. body .gfSelect .el-input__inner {
  597. background: rgba(83, 98, 104, 0);
  598. color: #ffffff;
  599. font-size: 16px;
  600. }
  601. .main-body {
  602. .query {
  603. display: flex;
  604. // justify-content: space-between;
  605. &.left {
  606. justify-content: flex-start;
  607. }
  608. .query-items {
  609. flex: 0 0 auto;
  610. display: flex;
  611. .query-item {
  612. flex: 0 0 auto;
  613. display: flex;
  614. margin: 0 1.4815vh;
  615. .el-input {
  616. &.placeholder-left {
  617. input {
  618. &::placeholder {
  619. text-align: left;
  620. }
  621. }
  622. }
  623. }
  624. .placeholder-left {
  625. .el-input {
  626. input {
  627. height: 33px !important;
  628. &::placeholder {
  629. text-align: left;
  630. }
  631. }
  632. }
  633. }
  634. .lable {
  635. flex: 0 0 auto;
  636. margin-right: 1.4815vh;
  637. line-height: 33px;
  638. color: @gray-l;
  639. }
  640. .search-input {
  641. position: relative;
  642. // input {
  643. // box-sizing: border-box;
  644. // flex: 0 0 200px;
  645. // border: 0px solid @darkgray;
  646. // color: @white;
  647. // outline: unset;
  648. // border-radius: 0%;
  649. // padding-right: 40px;
  650. // background: fade(#536268, 20);
  651. // height: 33px;
  652. // line-height: 33px;
  653. // &::placeholder {
  654. // font-size: 12px;
  655. // text-align: right;
  656. // color: @darkgray;
  657. // }
  658. // }
  659. // .unit {
  660. // position: absolute;
  661. // right: 12px;
  662. // top: 6px;
  663. // line-height: 33px;
  664. // margin: auto;
  665. // }
  666. }
  667. }
  668. }
  669. .query-actions {
  670. flex: 0 0 auto;
  671. margin-left: 50px;
  672. display: flex;
  673. justify-content: flex-start;
  674. align-items: center;
  675. .btn {
  676. cursor: pointer;
  677. }
  678. }
  679. }
  680. input {
  681. box-sizing: border-box;
  682. flex: 0 0 200px;
  683. border: 0px solid @darkgray;
  684. color: @white;
  685. outline: unset;
  686. border-radius: 0%;
  687. padding-right: 40px;
  688. background: fade(#536268, 20);
  689. height: 33px;
  690. line-height: 33px;
  691. &::placeholder {
  692. font-size: 12px;
  693. text-align: right;
  694. color: @darkgray;
  695. }
  696. }
  697. input[type="checkbox"] {
  698. position: relative;
  699. display: inline-block;
  700. appearance: none;
  701. width: 14px;
  702. height: 14px;
  703. outline: none;
  704. border: 1px solid @gray;
  705. background-color: #000;
  706. border-radius: 20%;
  707. margin: 0;
  708. padding: 0;
  709. &:checked {
  710. border-color: @green;
  711. background: @green;
  712. }
  713. &::after {
  714. display: inline-block;
  715. content: " ";
  716. position: absolute;
  717. left: 30%;
  718. top: 5%;
  719. width: 3px;
  720. height: 7px;
  721. border-color: #fff;
  722. border-style: solid;
  723. border-width: 0px 2px 2px 0px;
  724. transform: rotate(45deg);
  725. opacity: 0;
  726. }
  727. &:checked::after {
  728. content: "";
  729. opacity: 1;
  730. transition: opacity 0.3s ease-out;
  731. }
  732. }
  733. }
  734. </style>