index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. <template>
  2. <div class="pull-wrap">
  3. <div class="bg-img-wrap">
  4. <div
  5. v-if="configBg !== ''"
  6. class="bg-img"
  7. :style="{ backgroundImage: `url(${configBg})` }"
  8. ></div>
  9. <video
  10. v-if="configVideo !== ''"
  11. class="bg-video"
  12. :src="configVideo"
  13. muted
  14. autoplay
  15. loop
  16. ></video>
  17. <div
  18. v-else
  19. class="bg-img"
  20. ></div>
  21. </div>
  22. <div class="left">
  23. <div
  24. ref="topRef"
  25. class="head"
  26. >
  27. <div class="info">
  28. <div
  29. class="avatar"
  30. :style="{
  31. backgroundImage: `url(${anchorInfo?.avatar})`,
  32. }"
  33. @click="
  34. router.push({
  35. name: routerName.profile,
  36. params: { userId: anchorInfo?.id },
  37. })
  38. "
  39. ></div>
  40. <div class="detail">
  41. <div class="top">{{ anchorInfo?.username }}</div>
  42. <div class="bottom">
  43. <span>{{ appStore.liveRoomInfo?.desc }}</span>
  44. <span v-if="NODE_ENV === 'development'">
  45. socketId:{{ mySocketId }}
  46. </span>
  47. <span
  48. class="area"
  49. @click="
  50. router.push({
  51. name: routerName.area,
  52. query: { id: appStore.liveRoomInfo?.areas?.[0].id },
  53. })
  54. "
  55. >{{ appStore.liveRoomInfo?.areas?.[0].name }}</span
  56. >
  57. </div>
  58. </div>
  59. </div>
  60. <div class="other">在线人数:{{ liveUserList.length }}</div>
  61. </div>
  62. <div
  63. ref="containerRef"
  64. class="container"
  65. >
  66. <div
  67. class="no-live"
  68. v-if="!roomLiving"
  69. >
  70. 主播还没开播~
  71. </div>
  72. <div
  73. v-else
  74. v-loading="videoLoading"
  75. class="video-wrap"
  76. >
  77. <div
  78. class="cover"
  79. :style="{
  80. backgroundImage: `url(${
  81. appStore.liveRoomInfo?.cover_img || anchorInfo?.avatar
  82. })`,
  83. }"
  84. ></div>
  85. <div
  86. ref="remoteVideoRef"
  87. class="media-list"
  88. :class="{ item: appStore.allTrack.length > 1 }"
  89. ></div>
  90. <VideoControls
  91. :resolution="videoHeight"
  92. @refresh="handleRefresh"
  93. ></VideoControls>
  94. </div>
  95. </div>
  96. <div
  97. ref="bottomRef"
  98. v-loading="giftLoading"
  99. class="gift-list"
  100. >
  101. <div
  102. v-for="(item, index) in giftGoodsList"
  103. :key="index"
  104. class="item"
  105. @click="handlePay()"
  106. >
  107. <div
  108. class="ico"
  109. :style="{ backgroundImage: `url(${item.cover})` }"
  110. >
  111. <div
  112. v-if="item.badge"
  113. class="badge"
  114. :style="{ backgroundColor: item.badge_bg }"
  115. >
  116. <span class="txt">{{ item.badge }}</span>
  117. </div>
  118. </div>
  119. <div class="name">{{ item.name }}</div>
  120. <div class="price">¥{{ item.price }}</div>
  121. </div>
  122. <div
  123. class="item"
  124. @click="handleRecharge"
  125. >
  126. <div class="ico wallet"></div>
  127. <div class="name">余额:{{ userStore.userInfo?.wallet?.balance }}</div>
  128. <div class="price">立即充值</div>
  129. </div>
  130. </div>
  131. </div>
  132. <div class="right">
  133. <div class="tab">
  134. <span>在线用户</span>
  135. <span> | </span>
  136. <span>排行榜</span>
  137. </div>
  138. <div class="user-list">
  139. <div
  140. v-for="(item, index) in liveUserList.filter(
  141. (item) => item.id !== mySocketId
  142. )"
  143. :key="index"
  144. class="item"
  145. >
  146. <div class="info">
  147. <div
  148. class="avatar"
  149. :style="{ backgroundImage: `url(${item.userInfo?.avatar})` }"
  150. ></div>
  151. <div class="username">
  152. {{ item.userInfo?.username || item.id }}
  153. </div>
  154. </div>
  155. </div>
  156. <div
  157. v-if="userStore.userInfo"
  158. class="item"
  159. >
  160. <div class="info">
  161. <img
  162. :src="userStore.userInfo.avatar"
  163. class="avatar"
  164. alt=""
  165. />
  166. <div class="username">{{ userStore.userInfo.username }}</div>
  167. </div>
  168. </div>
  169. </div>
  170. <div
  171. ref="danmuListRef"
  172. class="danmu-list"
  173. >
  174. <div
  175. v-for="(item, index) in damuList"
  176. :key="index"
  177. class="item"
  178. >
  179. <template v-if="item.msgType === DanmuMsgTypeEnum.danmu">
  180. <span class="name">
  181. <span v-if="item.userInfo">
  182. {{ item.userInfo.username }}[{{
  183. item.userInfo.roles?.map((v) => v.role_name).join()
  184. }}]
  185. </span>
  186. <span v-else>{{ item.socket_id }}[游客]</span>
  187. </span>
  188. <span>:</span>
  189. <span
  190. class="msg"
  191. v-if="!item.msgIsFile"
  192. >
  193. {{ item.msg }}
  194. </span>
  195. <div
  196. class="msg img"
  197. v-else
  198. >
  199. <img
  200. :src="item.msg"
  201. alt=""
  202. @load="handleScrollTop"
  203. />
  204. </div>
  205. </template>
  206. <template v-else-if="item.msgType === DanmuMsgTypeEnum.otherJoin">
  207. <span class="name system">系统通知:</span>
  208. <span class="msg">
  209. {{ item.userInfo?.username || item.socket_id }}进入直播!
  210. </span>
  211. </template>
  212. <template v-else-if="item.msgType === DanmuMsgTypeEnum.userLeaved">
  213. <span class="name system">系统通知:</span>
  214. <span class="msg">
  215. {{ item.userInfo?.username || item.socket_id }}离开直播!
  216. </span>
  217. </template>
  218. </div>
  219. </div>
  220. <div
  221. class="send-msg"
  222. v-loading="msgLoading"
  223. >
  224. <div class="control">
  225. <div
  226. class="ico face"
  227. title="表情"
  228. @click="handleWait"
  229. ></div>
  230. <div
  231. class="ico img"
  232. title="图片"
  233. @click="mockClick"
  234. >
  235. <input
  236. placeholder="发个弹幕吧~"
  237. ref="uploadRef"
  238. type="file"
  239. class="input-upload"
  240. accept=".webp,.png,.jpg,.jpeg,.gif"
  241. @change="uploadChange"
  242. />
  243. </div>
  244. </div>
  245. <textarea
  246. v-model="danmuStr"
  247. class="ipt"
  248. @keydown="keydownDanmu"
  249. ></textarea>
  250. <div
  251. class="btn"
  252. @click="sendDanmu"
  253. >
  254. 发送
  255. </div>
  256. </div>
  257. </div>
  258. <RechargeCpt
  259. :show="showRecharge"
  260. @close="(v) => (showRecharge = v)"
  261. ></RechargeCpt>
  262. </div>
  263. </template>
  264. <script lang="ts" setup>
  265. import { onMounted, onUnmounted, ref, watch } from 'vue';
  266. import { fetchGoodsList } from '@/api/goods';
  267. import { MODULE_CONFIG_SWITCH, QINIU_LIVE } from '@/constant';
  268. import { loginTip } from '@/hooks/use-login';
  269. import { usePull } from '@/hooks/use-pull';
  270. import { useUpload } from '@/hooks/use-upload';
  271. import { DanmuMsgTypeEnum, GoodsTypeEnum, IGoods } from '@/interface';
  272. import router, { routerName } from '@/router';
  273. import { useAppStore } from '@/store/app';
  274. import { useUserStore } from '@/store/user';
  275. import { NODE_ENV } from 'script/constant';
  276. import RechargeCpt from './recharge/index.vue';
  277. const userStore = useUserStore();
  278. const appStore = useAppStore();
  279. const configBg = ref();
  280. const configVideo = ref();
  281. const giftGoodsList = ref<IGoods[]>([]);
  282. const height = ref(0);
  283. const giftLoading = ref(false);
  284. const showRecharge = ref(false);
  285. const msgLoading = ref(false);
  286. const topRef = ref<HTMLDivElement>();
  287. const bottomRef = ref<HTMLDivElement>();
  288. const danmuListRef = ref<HTMLDivElement>();
  289. const remoteVideoRef = ref<HTMLDivElement>();
  290. const containerRef = ref<HTMLDivElement>();
  291. const uploadRef = ref<HTMLInputElement>();
  292. const {
  293. initPull,
  294. closeWs,
  295. closeRtc,
  296. keydownDanmu,
  297. sendDanmu,
  298. handlePlay,
  299. msgIsFile,
  300. mySocketId,
  301. videoHeight,
  302. videoLoading,
  303. remoteVideo,
  304. roomLiving,
  305. damuList,
  306. liveUserList,
  307. danmuStr,
  308. anchorInfo,
  309. } = usePull();
  310. onMounted(() => {
  311. setTimeout(() => {
  312. scrollTo(0, 0);
  313. }, 100);
  314. appStore.setPlay(true);
  315. getGoodsList();
  316. if (topRef.value && bottomRef.value && containerRef.value) {
  317. const res =
  318. bottomRef.value.getBoundingClientRect().top -
  319. (topRef.value.getBoundingClientRect().top +
  320. topRef.value.getBoundingClientRect().height);
  321. height.value = res;
  322. }
  323. getBg();
  324. initPull();
  325. });
  326. onUnmounted(() => {
  327. closeWs();
  328. closeRtc();
  329. });
  330. watch(
  331. () => remoteVideo.value,
  332. (newVal) => {
  333. newVal.forEach((item) => {
  334. remoteVideoRef.value?.appendChild(item);
  335. });
  336. },
  337. {
  338. deep: true,
  339. immediate: true,
  340. }
  341. );
  342. watch(
  343. () => damuList.value.length,
  344. () => {
  345. setTimeout(() => {
  346. handleScrollTop();
  347. }, 0);
  348. }
  349. );
  350. watch(
  351. () => appStore.liveRoomInfo,
  352. () => {
  353. getBg();
  354. },
  355. {
  356. deep: true,
  357. }
  358. );
  359. function getBg() {
  360. try {
  361. const reg = /.+\.mp4$/g;
  362. const url = appStore.liveRoomInfo?.bg_img;
  363. if (url) {
  364. if (reg.exec(url)) {
  365. configVideo.value = url;
  366. } else {
  367. configBg.value = url;
  368. }
  369. }
  370. } catch (error) {
  371. console.log(error);
  372. }
  373. }
  374. function handleWait() {
  375. window.$message.warning('敬请期待!');
  376. }
  377. function mockClick() {
  378. uploadRef.value?.click();
  379. }
  380. async function uploadChange() {
  381. const fileList = uploadRef.value?.files;
  382. if (fileList?.length) {
  383. try {
  384. msgLoading.value = true;
  385. msgIsFile.value = true;
  386. const res = await useUpload({
  387. prefix: QINIU_LIVE.prefix['billd-live/msg-image/'],
  388. file: fileList[0],
  389. });
  390. if (res?.resultUrl) {
  391. danmuStr.value = res.resultUrl || '错误图片';
  392. sendDanmu();
  393. }
  394. } catch (error) {
  395. console.log(error);
  396. } finally {
  397. msgIsFile.value = false;
  398. msgLoading.value = false;
  399. if (uploadRef.value) {
  400. uploadRef.value.value = '';
  401. }
  402. }
  403. }
  404. }
  405. function handlePay() {
  406. if (!MODULE_CONFIG_SWITCH.pay) {
  407. window.$message.info('敬请期待!');
  408. return;
  409. }
  410. window.$message.info('敬请期待!');
  411. }
  412. function handleRefresh() {
  413. if (appStore.liveRoomInfo) {
  414. handlePlay(appStore.liveRoomInfo);
  415. }
  416. }
  417. async function getGoodsList() {
  418. try {
  419. giftLoading.value = true;
  420. const res = await fetchGoodsList({
  421. type: GoodsTypeEnum.gift,
  422. orderName: 'created_at',
  423. orderBy: 'desc',
  424. });
  425. if (res.code === 200) {
  426. giftGoodsList.value = res.data.rows;
  427. }
  428. } catch (error) {
  429. console.log(error);
  430. } finally {
  431. giftLoading.value = false;
  432. }
  433. }
  434. function handleRecharge() {
  435. if (!MODULE_CONFIG_SWITCH.pay) {
  436. window.$message.info('敬请期待!');
  437. return;
  438. }
  439. if (!loginTip()) return;
  440. showRecharge.value = true;
  441. }
  442. function handleScrollTop() {
  443. if (danmuListRef.value) {
  444. danmuListRef.value.scrollTop = danmuListRef.value.scrollHeight + 10000;
  445. }
  446. }
  447. </script>
  448. <style lang="scss" scoped>
  449. .pull-wrap {
  450. display: flex;
  451. justify-content: space-around;
  452. margin: 15px auto 0;
  453. width: $w-1275;
  454. .bg-img-wrap {
  455. position: absolute;
  456. top: $layout-head-h;
  457. left: 50%;
  458. max-width: 1920px;
  459. max-height: 890px;
  460. width: 100%;
  461. height: 100%;
  462. background-position: center center;
  463. background-size: cover;
  464. background-repeat: no-repeat;
  465. transform: translateX(-50%);
  466. .bg-img {
  467. position: absolute;
  468. top: 0;
  469. right: 0;
  470. left: 0;
  471. z-index: -1;
  472. width: 100%;
  473. height: 100%;
  474. background-position: center;
  475. background-size: cover;
  476. background-repeat: no-repeat;
  477. }
  478. .bg-video {
  479. position: absolute;
  480. top: 0;
  481. right: 0;
  482. left: 0;
  483. z-index: -1;
  484. width: 100%;
  485. height: 100%;
  486. }
  487. }
  488. .left {
  489. position: relative;
  490. display: inline-block;
  491. overflow: hidden;
  492. box-sizing: border-box;
  493. width: $w-1000;
  494. height: 100%;
  495. border-radius: 6px;
  496. background-color: papayawhip;
  497. color: #61666d;
  498. vertical-align: top;
  499. .head {
  500. display: flex;
  501. justify-content: space-between;
  502. padding: 10px 20px;
  503. .info {
  504. display: flex;
  505. align-items: center;
  506. text-align: initial;
  507. .avatar {
  508. margin-right: 20px;
  509. width: 50px;
  510. height: 50px;
  511. border-radius: 50%;
  512. cursor: pointer;
  513. @extend %containBg;
  514. }
  515. .detail {
  516. .top {
  517. margin-bottom: 10px;
  518. color: #18191c;
  519. }
  520. .bottom {
  521. font-size: 14px;
  522. .area {
  523. margin-left: 10px;
  524. color: #9499a0;
  525. cursor: pointer;
  526. }
  527. }
  528. }
  529. }
  530. .other {
  531. display: flex;
  532. flex-direction: column;
  533. justify-content: center;
  534. font-size: 14px;
  535. }
  536. }
  537. .container {
  538. display: flex;
  539. align-items: center;
  540. justify-content: space-between;
  541. height: 562px;
  542. background-color: rgba($color: #000000, $alpha: 0.5);
  543. .no-live {
  544. position: absolute;
  545. top: 50%;
  546. left: 50%;
  547. z-index: 20;
  548. color: white;
  549. font-size: 28px;
  550. transform: translate(-50%, -50%);
  551. }
  552. .video-wrap {
  553. position: relative;
  554. overflow: hidden;
  555. flex: 1;
  556. height: 100%;
  557. .cover {
  558. position: absolute;
  559. background-position: center center;
  560. background-size: cover;
  561. filter: blur(10px);
  562. inset: 0;
  563. }
  564. .videoControls {
  565. position: relative;
  566. z-index: 20;
  567. }
  568. .media-list {
  569. position: relative;
  570. height: 562px;
  571. :deep(video) {
  572. position: absolute;
  573. top: 50%;
  574. left: 50%;
  575. display: block;
  576. margin: 0 auto;
  577. // min-width: 100%;
  578. // min-height: 100%;
  579. max-width: $w-1000;
  580. max-height: 562px;
  581. transform: translate(-50%, -50%);
  582. }
  583. :deep(canvas) {
  584. position: absolute;
  585. top: 50%;
  586. left: 50%;
  587. display: block;
  588. margin: 0 auto;
  589. // min-width: 100%;
  590. // min-height: 100%;
  591. max-width: $w-1000;
  592. max-height: 562px;
  593. transform: translate(-50%, -50%);
  594. }
  595. // &.item {
  596. // :deep(video) {
  597. // width: 50%;
  598. // height: initial !important;
  599. // }
  600. // :deep(canvas) {
  601. // width: 50%;
  602. // height: initial !important;
  603. // }
  604. // }
  605. }
  606. .controls {
  607. display: none;
  608. }
  609. .tip-btn {
  610. position: absolute;
  611. top: 50%;
  612. left: 50%;
  613. z-index: 1;
  614. align-items: center;
  615. padding: 12px 26px;
  616. border: 2px solid rgba($color: $theme-color-gold, $alpha: 0.5);
  617. border-radius: 6px;
  618. background-color: rgba(0, 0, 0, 0.3);
  619. color: $theme-color-gold;
  620. cursor: pointer;
  621. transform: translate(-50%, -50%);
  622. &:hover {
  623. background-color: rgba($color: $theme-color-gold, $alpha: 0.5);
  624. color: white;
  625. }
  626. }
  627. }
  628. }
  629. .gift-list {
  630. position: relative;
  631. display: flex;
  632. align-items: center;
  633. justify-content: space-around;
  634. box-sizing: border-box;
  635. margin: 5px 0;
  636. height: 100px;
  637. > :last-child {
  638. position: absolute;
  639. }
  640. .item {
  641. display: flex;
  642. align-items: center;
  643. flex-direction: column;
  644. justify-content: center;
  645. box-sizing: border-box;
  646. width: 100px;
  647. height: 100px;
  648. text-align: center;
  649. cursor: pointer;
  650. &:hover {
  651. background-color: #ebe0ce;
  652. }
  653. .ico {
  654. position: relative;
  655. width: 45px;
  656. height: 45px;
  657. background-position: center center;
  658. background-size: cover;
  659. background-repeat: no-repeat;
  660. &.wallet {
  661. background-image: url('@/assets/img/wallet.webp');
  662. }
  663. .badge {
  664. position: absolute;
  665. top: -8px;
  666. right: -10px;
  667. display: flex;
  668. align-items: center;
  669. justify-content: center;
  670. padding: 2px;
  671. border-radius: 2px;
  672. color: white;
  673. .txt {
  674. display: inline-block;
  675. line-height: 1;
  676. transform-origin: center !important;
  677. @include minFont(10);
  678. }
  679. }
  680. }
  681. .name {
  682. color: #18191c;
  683. font-size: 12px;
  684. }
  685. .price {
  686. color: #9499a0;
  687. font-size: 12px;
  688. }
  689. }
  690. }
  691. }
  692. .right {
  693. position: relative;
  694. display: inline-block;
  695. box-sizing: border-box;
  696. width: $w-250;
  697. border-radius: 6px;
  698. background-color: papayawhip;
  699. color: #9499a0;
  700. .tab {
  701. display: flex;
  702. align-items: center;
  703. justify-content: space-evenly;
  704. padding: 5px 0;
  705. font-size: 12px;
  706. }
  707. .user-list {
  708. overflow-y: scroll;
  709. padding: 0 15px;
  710. height: 100px;
  711. background-color: papayawhip;
  712. @extend %customScrollbar;
  713. .item {
  714. display: flex;
  715. align-items: center;
  716. justify-content: space-between;
  717. margin-bottom: 10px;
  718. font-size: 12px;
  719. .info {
  720. display: flex;
  721. align-items: center;
  722. cursor: pointer;
  723. .avatar {
  724. margin-right: 5px;
  725. width: 25px;
  726. height: 25px;
  727. border-radius: 50%;
  728. @extend %containBg;
  729. }
  730. .username {
  731. color: black;
  732. }
  733. }
  734. }
  735. }
  736. .danmu-list {
  737. overflow-y: scroll;
  738. box-sizing: border-box;
  739. padding-top: 4px;
  740. height: 480px;
  741. background-color: #f6f7f8;
  742. text-align: initial;
  743. @extend %customScrollbar;
  744. .item {
  745. box-sizing: border-box;
  746. margin-bottom: 4px;
  747. padding: 2px 10px;
  748. white-space: normal;
  749. word-wrap: break-word;
  750. font-size: 13px;
  751. .name {
  752. color: #9499a0;
  753. cursor: pointer;
  754. &.system {
  755. color: red;
  756. }
  757. }
  758. .msg {
  759. margin-top: 4px;
  760. color: #61666d;
  761. &.img {
  762. img {
  763. width: 80%;
  764. }
  765. }
  766. }
  767. }
  768. }
  769. .send-msg {
  770. position: relative;
  771. box-sizing: border-box;
  772. padding: 4px 10px;
  773. width: 100%;
  774. .control {
  775. display: flex;
  776. margin: 4px 0;
  777. .ico {
  778. margin-right: 6px;
  779. width: 24px;
  780. height: 24px;
  781. cursor: pointer;
  782. .input-upload {
  783. width: 0;
  784. height: 0;
  785. opacity: 0;
  786. }
  787. &.face {
  788. @include setBackground('@/assets/img/msg-face.webp');
  789. }
  790. &.img {
  791. @include setBackground('@/assets/img/msg-img.webp');
  792. }
  793. }
  794. }
  795. .ipt {
  796. display: block;
  797. box-sizing: border-box;
  798. margin: 0 auto;
  799. padding: 10px;
  800. width: 100%;
  801. height: 60px;
  802. outline: none;
  803. border: 1px solid hsla(0, 0%, 60%, 0.2);
  804. border-radius: 4px;
  805. background-color: #f1f2f3;
  806. font-size: 14px;
  807. }
  808. .btn {
  809. box-sizing: border-box;
  810. margin-top: 10px;
  811. margin-left: auto;
  812. padding: 4px;
  813. width: 70px;
  814. border-radius: 4px;
  815. background-color: $theme-color-gold;
  816. color: white;
  817. text-align: center;
  818. font-size: 12px;
  819. cursor: pointer;
  820. }
  821. }
  822. }
  823. }
  824. // 屏幕宽度大于1500的时候
  825. @media screen and (min-width: $w-1500) {
  826. .pull-wrap {
  827. width: $w-1350;
  828. .left {
  829. width: $w-1000;
  830. }
  831. .right {
  832. width: $w-300;
  833. }
  834. }
  835. }
  836. </style>