Jelajahi Sumber

feat: 保存

shuisheng 1 tahun lalu
induk
melakukan
67cd7d3bc8

+ 2 - 1
package.json

@@ -64,6 +64,7 @@
     "vue": "^3.3.4",
     "vue-demi": "^0.13.11",
     "vue-i18n": "9",
+    "vue-lazyload": "^3.0.0",
     "vue-router": "^4.0.13",
     "webrtc-adapter": "^8.2.2"
   },
@@ -135,4 +136,4 @@
     "webpackbar": "^5.0.2",
     "windicss-webpack-plugin": "^1.7.7"
   }
-}
+}

File diff ditekan karena terlalu besar
+ 7441 - 311
pnpm-lock.yaml


TEMPAT SAMPAH
src/assets/img/lazy_error.webp


TEMPAT SAMPAH
src/assets/img/lazy_loading.webp


TEMPAT SAMPAH
src/assets/img/pay-course.webp


+ 6 - 4
src/components/Avatar/index.vue

@@ -7,9 +7,9 @@
       <div
         class="avatar"
         :class="{ border }"
-        :style="{ backgroundImage: `url(${avatar})` }"
+        v-lazy:background-image="avatar"
       ></div>
-      <template v-if="living">
+      <template v-if="living && !disableLiving">
         <div class="cycle cycle-1"></div>
         <div class="cycle cycle-2"></div>
         <div class="cycle cycle-3"></div
@@ -22,16 +22,18 @@
 <script lang="ts" setup>
 withDefaults(
   defineProps<{
-    avatar: string;
+    avatar?: string;
     size: number;
     living?: boolean;
     border?: boolean;
+    disableLiving?: boolean;
   }>(),
   {
     avatar: '',
     size: 100,
     living: false,
     border: false,
+    disableLiving: false,
   }
 );
 </script>
@@ -62,7 +64,7 @@ withDefaults(
       border-radius: 50%;
       cursor: pointer;
 
-      @extend %containBg;
+      @extend %coverBg;
       &.border {
         border: 1px solid $theme-color-gold;
       }

+ 100 - 0
src/components/LongList/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <div
+    ref="longListRef"
+    class="long-list-wrap"
+  >
+    <slot></slot>
+    <div
+      class="loading"
+      v-if="loading"
+    >
+      {{ t('common.loading') }}
+    </div>
+    <div ref="bottomRef"></div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const longListRef = ref<HTMLElement>();
+const loading = ref(false);
+const bottomRef = ref<any>();
+const intersectionObserver = ref<IntersectionObserver>();
+
+const { t } = useI18n();
+
+defineExpose({
+  longListRef,
+  loading,
+});
+
+const props = withDefaults(
+  defineProps<{
+    rootMargin?: {
+      top: number;
+      right: number;
+      bottom: number;
+      left: number;
+    };
+  }>(),
+  {
+    rootMargin: () => {
+      return { top: 0, right: 0, bottom: 100, left: 0 };
+    },
+  }
+);
+
+const emits = defineEmits(['getListData']);
+
+function monitTouchBottom() {
+  // 距离底部50px就开始加载下一页
+  const rootMargin = {
+    bottom: props.rootMargin.bottom,
+    top: props.rootMargin.top,
+    left: props.rootMargin.left,
+    right: props.rootMargin.right,
+  };
+  intersectionObserver.value = new IntersectionObserver(
+    (entries) => {
+      entries.forEach((item) => {
+        if (item.isIntersecting) {
+          console.log('到底了');
+          emits('getListData');
+        } else {
+          // console.log('隐藏');
+        }
+      });
+    },
+    {
+      root: longListRef.value,
+      rootMargin: `${rootMargin.top}px ${rootMargin.right}px ${rootMargin.bottom}px ${rootMargin.left}px`,
+    }
+  );
+  intersectionObserver.value.observe(bottomRef.value);
+}
+
+onMounted(() => {
+  monitTouchBottom();
+});
+onUnmounted(() => {
+  intersectionObserver.value?.disconnect();
+});
+</script>
+
+<style lang="scss" scoped>
+.long-list-wrap {
+  overflow: scroll;
+  box-sizing: border-box;
+  padding-bottom: 10px;
+  width: 100%;
+  height: 100%;
+
+  @extend %customScrollbar;
+  .loading {
+    width: 100%;
+    text-align: center;
+  }
+}
+</style>

+ 3 - 1
src/components/Slider/index.vue

@@ -28,7 +28,9 @@
             <div
               alt=""
               class="img"
-              :style="{ backgroundImage: `url(${slide.img})` }"
+              :style="{
+                backgroundImage: `url(${slide.img})`,
+              }"
             ></div>
           </div>
 

+ 3 - 0
src/constant.ts

@@ -42,6 +42,9 @@ export const SRS_CB_URL_PARAMS = {
   userId: 'userid',
   randomId: 'randomid',
 };
+export const URL_PARAMS = {
+  goodsType: 'goodsType',
+};
 
 export const QINIU_RESOURCE = {
   domain: `resource.${prodDomain}`,

+ 14 - 2
src/hooks/use-pull.ts

@@ -234,9 +234,19 @@ export function usePull(roomId: string) {
   function handlePlay(data: ILiveRoom) {
     roomLiving.value = true;
     flvurl.value =
-      data.cdn === LiveRoomUseCDNEnum.yes ? data.cdn_flv_url! : data.flv_url!;
+      data.cdn === LiveRoomUseCDNEnum.yes &&
+      [LiveRoomTypeEnum.tencent_css, LiveRoomTypeEnum.tencent_css_pk].includes(
+        data.type!
+      )
+        ? data.cdn_flv_url!
+        : data.flv_url!;
     hlsurl.value =
-      data.cdn === LiveRoomUseCDNEnum.yes ? data.cdn_hls_url! : data.hls_url!;
+      data.cdn === LiveRoomUseCDNEnum.yes &&
+      [LiveRoomTypeEnum.tencent_css, LiveRoomTypeEnum.tencent_css_pk].includes(
+        data.type!
+      )
+        ? data.cdn_hls_url!
+        : data.hls_url!;
     function play() {
       if (appStore.liveLine === LiveLineEnum.flv) {
         handleFlvPlay();
@@ -256,6 +266,8 @@ export function usePull(roomId: string) {
         LiveRoomTypeEnum.forward_bilibili,
         LiveRoomTypeEnum.forward_huya,
         LiveRoomTypeEnum.forward_all,
+        LiveRoomTypeEnum.tencent_css,
+        LiveRoomTypeEnum.tencent_css_pk,
       ].includes(data.type!)
     ) {
       play();

+ 1 - 3
src/layout/mobile/head/index.vue

@@ -38,9 +38,7 @@
         >
           <div
             class="btn"
-            :style="{
-              backgroundImage: `url(${userStore.userInfo.avatar})`,
-            }"
+            v-lazy:background-image="userStore.userInfo.avatar"
           ></div>
         </n-dropdown>
       </div>

+ 4 - 1
src/layout/pc/head/index.vue

@@ -367,7 +367,7 @@
           <template #btn>
             <div
               class="btn"
-              :style="{ backgroundImage: `url(${userStore.userInfo.avatar})` }"
+              v-lazy:background-image="userStore.userInfo.avatar"
             ></div>
           </template>
           <template #list>
@@ -865,6 +865,9 @@ function handleWebsiteJump() {
           color: $theme-color-gold;
         }
       }
+      .github {
+        width: 82px;
+      }
 
       .videoTools,
       .privatizationDeployment,

+ 18 - 0
src/layout/pc/index.vue

@@ -7,10 +7,19 @@
     </router-view>
     <SidebarCpt></SidebarCpt>
     <LoginModal v-if="appStore.showLoginModal"></LoginModal>
+    <img
+      class="pay-course"
+      src="@/assets/img/pay-course.webp"
+      alt=""
+      @click="openToTarget(COMMON_URL.payCoursesArticle)"
+    />
   </div>
 </template>
 
 <script lang="ts" setup>
+import { openToTarget } from 'billd-utils';
+
+import { COMMON_URL } from '@/constant';
 import { useAppStore } from '@/store/app';
 
 import HeadCpt from './head/index.vue';
@@ -25,6 +34,7 @@ const appStore = useAppStore();
   box-sizing: border-box;
   padding-top: $header-height;
   min-height: 100vh;
+
   .fixed-mask {
     position: fixed;
     top: 0;
@@ -38,5 +48,13 @@ const appStore = useAppStore();
     filter: blur(70px);
     pointer-events: none;
   }
+  .pay-course {
+    position: fixed;
+    right: 10px;
+    bottom: 10px;
+    width: 200px;
+    border-radius: 10px;
+    cursor: pointer;
+  }
 }
 </style>

+ 9 - 1
src/layout/pc/sidebar/index.vue

@@ -23,7 +23,7 @@
     </div>
     <div
       class="item"
-      @click="router.push({ name: routerName.wallet })"
+      @click="handleJump"
     >
       <div class="ico wallet"></div>
       <div class="txt">{{ t('layout.myWallet') }}</div>
@@ -34,9 +34,17 @@
 <script lang="ts" setup>
 import { useI18n } from 'vue-i18n';
 
+import { loginTip } from '@/hooks/use-login';
 import router, { routerName } from '@/router';
 
 const { t } = useI18n();
+
+function handleJump() {
+  if (!loginTip()) {
+    return;
+  }
+  router.push({ name: routerName.wallet });
+}
 </script>
 
 <style lang="scss" scoped>

+ 2 - 0
src/locales/en/common.ts

@@ -1,6 +1,8 @@
 import { nameSpaceWrap } from '@/locales/util';
 
 export default nameSpaceWrap('common', {
+  loading: 'loading...',
+  allLoaded: 'all loaded',
   nonedata: 'none data',
   living: 'living',
   wallet: 'wallet',

+ 2 - 0
src/locales/zh/common.ts

@@ -1,6 +1,8 @@
 import { nameSpaceWrap } from '@/locales/util';
 
 export default nameSpaceWrap('common', {
+  loading: '加载中...',
+  allLoaded: '已全部加载',
   nonedata: '暂无数据',
   living: '直播中',
   wallet: '钱包',

+ 9 - 0
src/main.ts

@@ -4,7 +4,10 @@ import '@/utils/showBilldVersion';
 import 'webrtc-adapter';
 // import 'windi.css'; // windicss-webpack-plugin会解析windi.css这个MODULE_ID
 import { createApp } from 'vue';
+import VueLazyLoad from 'vue-lazyload';
 
+import lazyErrorWebp from '@/assets/img/lazy_error.webp';
+import lazyLoadingWebp from '@/assets/img/lazy_loading.webp';
 import Message from '@/components/Message/index.vue';
 import registerDirectives from '@/directives';
 import { i18n } from '@/hooks/use-i18n';
@@ -18,6 +21,12 @@ registerDirectives(app);
 app.use(i18n);
 app.use(store);
 app.use(router);
+app.use(VueLazyLoad, {
+  preLoad: 1,
+  error: lazyErrorWebp,
+  loading: lazyLoadingWebp,
+  attempt: 2,
+});
 
 const message = createApp(Message);
 const messageEle = document.createElement('div');

+ 3 - 0
src/types/shims-vue.d.ts

@@ -4,6 +4,9 @@ declare module '*.vue' {
   const component: DefineComponent<{}, {}, any>;
   export default component;
 }
+
+declare module '*.webp';
+
 interface Window {
   $message: import('naive-ui/es/message/src/MessageProvider').MessageApiInjection;
   TXLivePusher: any;

+ 13 - 2
src/views/about/team/index.vue

@@ -21,7 +21,7 @@
         >
           <img
             class="avatar"
-            src="https://www.github.com/galaxy-s10.png"
+            v-lazy="'https://www.github.com/galaxy-s10.png'"
             alt=""
           />
           <div class="data">
@@ -94,7 +94,15 @@
               </a>
             </div>
           </div>
-          <div class="sponsor">
+          <div
+            class="sponsor"
+            @click="
+              router.push({
+                name: routerName.shop,
+                query: { [URL_PARAMS.goodsType]: GoodsTypeEnum.sponsors },
+              })
+            "
+          >
             <n-icon
               size="18"
               class="ico"
@@ -121,6 +129,9 @@ import {
 import { openToTarget } from 'billd-utils';
 import { ref } from 'vue';
 
+import { URL_PARAMS } from '@/constant';
+import { GoodsTypeEnum } from '@/interface';
+import router, { routerName } from '@/router';
 import { prodDomain } from '@/spec-config';
 
 const list = ref([

+ 165 - 136
src/views/area/id/index.vue

@@ -1,60 +1,72 @@
 <template>
-  <div class="area-wrap">
-    <div class="wrap">
+  <div
+    class="area-wrap"
+    ref="topRef"
+    :style="{ height: height + 'px' }"
+  >
+    <LongList
+      ref="longListRef"
+      class="list"
+      @get-list-data="getListData"
+      :rootMargin="{
+        top: 0,
+        bottom: 200,
+        left: 0,
+        right: 0,
+      }"
+    >
       <div
-        v-loading="loading"
-        class="live-room-list"
+        v-for="(item, index) in liveRoomList"
+        :key="index"
+        class="item"
+        @click="goRoom(item)"
       >
         <div
-          v-for="(iten, indey) in liveRoomList"
-          :key="indey"
-          class="live-room"
-          @click="goRoom(iten)"
+          class="cover"
+          v-lazy:background-image="item?.cover_img || item?.users?.[0]?.avatar"
         >
+          <PullAuthTip
+            v-if="
+              item?.pull_is_should_auth === LiveRoomPullIsShouldAuthEnum.yes
+            "
+          ></PullAuthTip>
           <div
-            class="cover"
-            :style="{
-              backgroundImage: `url('${
-                iten?.cover_img || iten?.users?.[0]?.avatar
-              }')`,
-            }"
+            v-if="item?.live"
+            class="living-ico"
           >
-            <PullAuthTip
-              v-if="
-                iten?.pull_is_should_auth === LiveRoomPullIsShouldAuthEnum.yes
-              "
-            ></PullAuthTip>
-            <div
-              v-if="iten?.live"
-              class="living-ico"
-            >
-              直播中
-            </div>
-            <div
-              v-if="
-                iten?.cdn === LiveRoomUseCDNEnum.yes ||
-                [
-                  LiveRoomTypeEnum.tencent_css,
-                  LiveRoomTypeEnum.tencent_css_pk,
-                ].includes(iten.type!)
-              "
-              class="cdn-ico"
-            >
-              <div class="txt">CDN</div>
-            </div>
-            <div class="txt">{{ iten?.users?.[0]?.username }}</div>
+            直播中
           </div>
-          <div class="desc">{{ iten?.name }}</div>
-        </div>
-        <div
-          v-if="!liveRoomList.length"
-          class="null"
-        >
-          {{ t('common.nonedata') }}
+          <div
+            v-if="
+              item?.cdn === LiveRoomUseCDNEnum.yes ||
+              [
+                LiveRoomTypeEnum.tencent_css,
+                LiveRoomTypeEnum.tencent_css_pk,
+              ].includes(item.type!)
+            "
+            class="cdn-ico"
+          >
+            <div class="txt">CDN</div>
+          </div>
+          <div class="txt">{{ item?.users?.[0]?.username }}</div>
         </div>
+        <div class="desc">{{ item?.name }}</div>
+      </div>
+      <div v-if="loading"></div>
+      <div
+        v-else-if="!liveRoomList.length"
+        class="null"
+      >
+        {{ t('common.nonedata') }}
       </div>
-    </div>
-    <div
+      <div
+        v-else-if="!hasMore"
+        class="null"
+      >
+        {{ t('common.allLoaded') }}
+      </div>
+    </LongList>
+    <!-- <div
       class="paging-wrap"
       v-if="pageParams.total > pageParams.pageSize"
     >
@@ -67,7 +79,7 @@
         @update-page="getData"
         @update-page-size="handleUpdatePageSize"
       />
-    </div>
+    </div> -->
   </div>
 </template>
 
@@ -77,6 +89,7 @@ import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
 import { fetchLiveRoomList } from '@/api/area';
+import LongList from '@/components/LongList/index.vue';
 import router, { routerName } from '@/router';
 import {
   ILiveRoom,
@@ -90,29 +103,26 @@ const liveRoomList = ref<ILiveRoom[]>([]);
 const { t } = useI18n();
 const route = useRoute();
 
+const longListRef = ref<InstanceType<typeof LongList>>();
+const topRef = ref<HTMLDivElement>();
+const height = ref(0);
 const loading = ref(false);
+const hasMore = ref(true);
 const pageParams = reactive({
-  nowPage: 1,
-  pageSize: 30,
-  total: 0,
-  hasMore: false,
+  nowPage: 0,
+  pageSize: 50,
 });
 
 watch(
   () => route.params.id,
   (newVal) => {
     if (!newVal) return;
-    pageParams.nowPage = 1;
+    liveRoomList.value = [];
+    pageParams.nowPage = 0;
     getData();
   }
 );
 
-function handleUpdatePageSize(v) {
-  pageParams.nowPage = 1;
-  pageParams.pageSize = v;
-  getData();
-}
-
 function goRoom(item: ILiveRoom) {
   if (!item.live) {
     window.$message.info('该直播间没在直播~');
@@ -125,12 +135,27 @@ function goRoom(item: ILiveRoom) {
 }
 
 onMounted(() => {
+  if (topRef.value) {
+    height.value =
+      document.documentElement.clientHeight -
+      topRef.value.getBoundingClientRect().top;
+  }
   getData();
 });
 
+function getListData() {
+  if (!hasMore.value) return;
+  getData();
+}
+
 async function getData() {
   try {
+    if (loading.value) return;
     loading.value = true;
+    pageParams.nowPage += 1;
+    if (longListRef.value) {
+      longListRef.value.loading = true;
+    }
     const res = await fetchLiveRoomList({
       id: Number(route.params.id),
       live_room_is_show: LiveRoomIsShowEnum.yes,
@@ -138,107 +163,111 @@ async function getData() {
       pageSize: pageParams.pageSize,
     });
     if (res.code === 200) {
-      liveRoomList.value = res.data.rows;
-      pageParams.total = res.data.total;
-      pageParams.hasMore = res.data.hasMore;
+      liveRoomList.value.push(...res.data.rows);
+      hasMore.value = res.data.hasMore;
     }
   } catch (error) {
+    pageParams.nowPage -= 1;
     console.log(error);
-  } finally {
-    loading.value = false;
+  }
+  loading.value = false;
+  if (longListRef.value) {
+    longListRef.value.loading = false;
   }
 }
 </script>
 
 <style lang="scss" scoped>
 .area-wrap {
-  .wrap {
-    margin-top: 10px;
-    padding: 0 20px;
-    .live-room-list {
-      display: flex;
-      align-items: center;
-      flex-wrap: wrap;
-      .live-room {
-        display: inline-block;
-        margin-right: 25px;
-        margin-bottom: 12px;
-        width: 300px;
-        cursor: pointer;
-        .cover {
-          position: relative;
-          overflow: hidden;
-          width: 100%;
-          height: 150px;
-          border-radius: 8px;
-          background-position: center center;
-          background-size: cover;
-          .living-ico {
-            position: absolute;
-            top: 0px;
-            left: 0px;
-            z-index: 10;
-            padding: 0 10px;
-            height: 20px;
-            border-radius: 8px 0 10px;
-            background-color: $theme-color-gold;
-            color: white;
-            text-align: center;
-            font-size: 12px;
-            line-height: 20px;
-          }
-          .cdn-ico {
-            position: absolute;
-            top: -10px;
-            right: -10px;
-            z-index: 2;
-            width: 70px;
-            height: 28px;
-            background-color: #f87c48;
-            color: white;
-            transform: rotate(45deg);
-            transform-origin: bottom;
-            .txt {
-              margin-left: 18px;
-              background-image: initial !important;
-              font-size: 13px;
-            }
-          }
+  padding: 0 20px;
 
+  .list {
+    display: flex;
+    align-content: flex-start;
+    flex-wrap: wrap;
+    .item {
+      display: inline-block;
+      margin-right: 25px;
+      margin-bottom: 12px;
+      width: 300px;
+      cursor: pointer;
+      .cover {
+        position: relative;
+        overflow: hidden;
+        width: 100%;
+        height: 150px;
+        border-radius: 8px;
+        background-position: center center;
+        background-size: cover;
+        .living-ico {
+          position: absolute;
+          top: 0px;
+          left: 0px;
+          z-index: 10;
+          padding: 0 10px;
+          height: 20px;
+          border-radius: 8px 0 10px;
+          background-color: $theme-color-gold;
+          color: white;
+          text-align: center;
+          font-size: 12px;
+          line-height: 20px;
+        }
+        .cdn-ico {
+          position: absolute;
+          top: -10px;
+          right: -10px;
+          z-index: 2;
+          width: 70px;
+          height: 28px;
+          background-color: #f87c48;
+          color: white;
+          transform: rotate(45deg);
+          transform-origin: bottom;
           .txt {
-            position: absolute;
-            bottom: 0;
-            left: 0;
-            box-sizing: border-box;
-            padding: 4px 8px;
-            width: 100%;
-            border-radius: 0 0 4px 4px;
-            background-image: linear-gradient(
-              -180deg,
-              rgba(0, 0, 0, 0),
-              rgba(0, 0, 0, 0.6)
-            );
-            color: white;
-            text-align: initial;
+            margin-left: 18px;
+            background-image: initial !important;
             font-size: 13px;
-
-            @extend %singleEllipsis;
           }
         }
-        .desc {
-          margin-top: 4px;
-          font-size: 14px;
+
+        .txt {
+          position: absolute;
+          bottom: 0;
+          left: 0;
+          box-sizing: border-box;
+          padding: 4px 8px;
+          width: 100%;
+          border-radius: 0 0 4px 4px;
+          background-image: linear-gradient(
+            -180deg,
+            rgba(0, 0, 0, 0),
+            rgba(0, 0, 0, 0.6)
+          );
+          color: white;
+          text-align: initial;
+          font-size: 13px;
 
           @extend %singleEllipsis;
         }
       }
+      .desc {
+        margin-top: 4px;
+        font-size: 14px;
+
+        @extend %singleEllipsis;
+      }
+    }
+    .null {
+      width: 100%;
+      text-align: center;
     }
   }
   .paging-wrap {
-    margin-bottom: 20px;
     display: flex;
     align-items: center;
     justify-content: center;
+    margin-bottom: 20px;
   }
 }
 </style>

+ 1 - 1
src/views/area/index.vue

@@ -47,7 +47,7 @@ onMounted(() => {
 .tab-list {
   display: flex;
   align-items: center;
-  margin-bottom: 20px;
+  margin-bottom: 10px;
   height: 40px;
   font-size: 14px;
   padding: 0 30px;

+ 1 - 1
src/views/doc/ad/index.vue

@@ -15,7 +15,7 @@
         <div>
           <img
             class="cover"
-            :src="item.cover"
+            v-lazy="item.cover"
             alt=""
           />
         </div>

+ 3 - 2
src/views/doc/privatizationDeployment/index.vue

@@ -24,8 +24,9 @@
           <ol>
             <li>服务器自建SRS直播。</li>
             <li>服务器自建WebRTC会议。</li>
-            <li>接入第三方腾讯云直播。</li>
-            <li>PK互动。</li>
+            <li>接入第三方腾讯云云直播。</li>
+            <li>直播PK互动。</li>
+            <li>转推到第三方直播(b站、虎牙)。</li>
           </ol>
         </div>
         <div class="hr"></div>

+ 3 - 3
src/views/doc/sponsors/index.vue

@@ -45,7 +45,7 @@
               @click.prevent="openToTarget(item.url)"
             >
               <img
-                :src="item.logo"
+                v-lazy="item.logo"
                 alt=""
               />
             </a>
@@ -61,7 +61,7 @@
               @click.prevent="openToTarget(item.url)"
             >
               <img
-                :src="item.logo"
+                v-lazy="item.logo"
                 alt=""
               />
             </a>
@@ -77,7 +77,7 @@
               @click.prevent="openToTarget(item.url)"
             >
               <img
-                :src="item.logo"
+                v-lazy="item.logo"
                 alt=""
               />
             </a>

+ 108 - 57
src/views/h5/area/index.vue

@@ -1,64 +1,76 @@
 <template>
   <div class="h5-area-wrap">
-    <div class="title">{{ route.query.name }}</div>
-    <div class="live-room-list">
-      <div
-        v-for="(iten, indey) in liveRoomList"
-        :key="indey"
-        class="live-room"
-        @click="goRoom(iten)"
+    <div ref="topRef"></div>
+    <div :style="{ height: height + 'px' }">
+      <LongList
+        :class="{
+          list: 1,
+        }"
+        ref="longListRef"
+        @get-list-data="getListData"
       >
         <div
-          class="cover"
-          :style="{
-            backgroundImage: `url('${
-              iten?.cover_img || iten?.users?.[0].avatar
-            }')`,
-          }"
+          v-for="(iten, indey) in liveRoomList"
+          :key="indey"
+          class="item"
+          @click="goRoom(iten)"
         >
-          <PullAuthTip
-            v-if="
-              iten?.pull_is_should_auth === LiveRoomPullIsShouldAuthEnum.yes
-            "
-          ></PullAuthTip>
           <div
-            v-if="iten?.live"
-            class="living-ico"
+            class="cover"
+            v-lazy:background-image="iten?.cover_img || iten?.users?.[0].avatar"
           >
-            <div class="live-txt">直播中</div>
+            <PullAuthTip
+              v-if="
+                iten?.pull_is_should_auth === LiveRoomPullIsShouldAuthEnum.yes
+              "
+            ></PullAuthTip>
+            <div
+              v-if="iten?.live"
+              class="living-ico"
+            >
+              <div class="live-txt">直播中</div>
+            </div>
+            <div
+              v-if="
+                iten?.cdn === LiveRoomUseCDNEnum.yes ||
+                [
+                  LiveRoomTypeEnum.tencent_css,
+                  LiveRoomTypeEnum.tencent_css_pk,
+                ].includes(iten.type!)
+              "
+              class="cdn-ico"
+            >
+              <div class="txt">CDN</div>
+            </div>
+            <div class="txt">{{ iten?.users?.[0]?.username }}</div>
           </div>
-          <div
-            v-if="
-              iten?.cdn === LiveRoomUseCDNEnum.yes ||
-              [
-                LiveRoomTypeEnum.tencent_css,
-                LiveRoomTypeEnum.tencent_css_pk,
-              ].includes(iten.type!)
-            "
-            class="cdn-ico"
-          >
-            <div class="txt">CDN</div>
-          </div>
-          <div class="txt">{{ iten?.users?.[0]?.username }}</div>
+          <div class="desc">{{ iten?.name }}</div>
         </div>
-        <div class="desc">{{ iten?.name }}</div>
-      </div>
-      <div
-        v-if="!liveRoomList.length"
-        class="null"
-      >
-        {{ t('common.nonedata') }}
-      </div>
+        <div v-if="loading"></div>
+        <div
+          v-else-if="!liveRoomList.length"
+          class="null"
+        >
+          {{ t('common.nonedata') }}
+        </div>
+        <div
+          v-else-if="!hasMore"
+          class="null"
+        >
+          {{ t('common.allLoaded') }}
+        </div>
+      </LongList>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref } from 'vue';
+import { onMounted, reactive, ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
 import { fetchLiveRoomList } from '@/api/area';
+import LongList from '@/components/LongList/index.vue';
 import router, { routerName } from '@/router';
 import {
   ILiveRoom,
@@ -68,10 +80,21 @@ import {
   LiveRoomUseCDNEnum,
 } from '@/types/ILiveRoom';
 
-const liveRoomList = ref<ILiveRoom[]>([]);
 const { t } = useI18n();
-
 const route = useRoute();
+
+const liveRoomList = ref<ILiveRoom[]>([]);
+const topRef = ref<HTMLDivElement>();
+
+const pageParams = reactive({
+  nowPage: 0,
+  pageSize: 50,
+});
+const height = ref(0);
+const loading = ref(false);
+const hasMore = ref(true);
+const longListRef = ref<InstanceType<typeof LongList>>();
+
 function goRoom(item: ILiveRoom) {
   if (!item.live) {
     window.$message.info('该直播间没在直播~');
@@ -84,32 +107,56 @@ function goRoom(item: ILiveRoom) {
 }
 
 onMounted(() => {
+  if (topRef.value) {
+    height.value =
+      document.documentElement.clientHeight -
+      topRef.value.getBoundingClientRect().top;
+  }
   getData();
 });
 
 async function getData() {
-  const res = await fetchLiveRoomList({
-    id: Number(route.params.id),
-    live_room_is_show: LiveRoomIsShowEnum.yes,
-  });
-  if (res.code === 200) {
-    liveRoomList.value = res.data.rows;
+  try {
+    if (loading.value) return;
+    loading.value = true;
+    pageParams.nowPage += 1;
+    if (longListRef.value) {
+      longListRef.value.loading = true;
+    }
+    const res = await fetchLiveRoomList({
+      id: Number(route.params.id),
+      live_room_is_show: LiveRoomIsShowEnum.yes,
+      ...pageParams,
+    });
+    if (res.code === 200) {
+      liveRoomList.value.push(...res.data.rows);
+      hasMore.value = res.data.hasMore;
+    }
+  } catch (error) {
+    pageParams.nowPage -= 1;
+    console.log(error);
   }
+  loading.value = false;
+  if (longListRef.value) {
+    longListRef.value.loading = false;
+  }
+}
+function getListData() {
+  if (!hasMore.value) return;
+  getData();
 }
 </script>
 
 <style lang="scss" scoped>
 .h5-area-wrap {
   padding: 0 20px;
-  .title {
-    margin-bottom: 10px;
-  }
-  .live-room-list {
+
+  .list {
     display: flex;
-    align-items: center;
     flex-wrap: wrap;
+    align-content: flex-start;
     justify-content: space-between;
-    .live-room {
+    .item {
       display: inline-block;
       margin-bottom: 10px;
       width: 48%;
@@ -185,6 +232,10 @@ async function getData() {
         @extend %singleEllipsis;
       }
     }
+    .null {
+      width: 100%;
+      text-align: center;
+    }
   }
 }
 </style>

+ 21 - 23
src/views/h5/index.vue

@@ -2,7 +2,10 @@
   <div class="h5-wrap">
     <div
       class="swiper"
-      :style="{ backgroundImage: `url(${currentSwiper.bgi})` }"
+      :style="{
+        backgroundImage: `url(${currentSwiper.bgi})`,
+      }"
+      @click="openToTarget(currentSwiper.url)"
     >
       <!-- {{ currentSwiper.txt }} -->
     </div>
@@ -34,12 +37,9 @@
           >
             <div
               class="cover"
-              :style="{
-                backgroundImage: `url('${
-                  iten.live_room?.cover_img ||
-                  iten.live_room?.users?.[0]?.avatar
-                }')`,
-              }"
+              v-lazy:background-image="
+                iten.live_room?.cover_img || iten.live_room?.users?.[0]?.avatar
+              "
             >
               <PullAuthTip
                 v-if="
@@ -82,11 +82,12 @@
 </template>
 
 <script lang="ts" setup>
+import { openToTarget } from 'billd-utils';
 import { onMounted, onUnmounted, ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 
 import { fetchAreaLiveRoomList } from '@/api/area';
-import { QINIU_RESOURCE } from '@/constant';
+import { COMMON_URL } from '@/constant';
 import { IArea, IAreaLiveRoom } from '@/interface';
 import router, { mobileRouterName, routerName } from '@/router';
 import { useAppStore } from '@/store/app';
@@ -105,20 +106,14 @@ const swiperList = ref([
   {
     id: 1,
     txt: '广告位1',
-    bgi: `${QINIU_RESOURCE.url}/billd-live/image/ecdece08eb3eda2f37433cb7c748766f.webp`,
-    url: '',
+    bgi: `https://resource.hsslive.cn/billd-live/image/aa51fe9093c4c6887931d5e9224f0f07.webp`,
+    url: COMMON_URL.payCoursesArticle,
   },
   {
-    id: 2,
+    id: 1,
     txt: '广告位2',
-    bgi: `${QINIU_RESOURCE.url}/billd-live/image/b2e3459e7d4a70463cd201ee468491a1.webp`,
-    url: '',
-  },
-  {
-    id: 3,
-    txt: '广告位3',
-    bgi: `${QINIU_RESOURCE.url}/billd-live/image/71d01ff0bd34c57586500e425e21938f.webp`,
-    url: '',
+    bgi: `https://resource.hsslive.cn/billd-live/image/1d62827adb3f0575cf3138811aeed4f2.png`,
+    url: 'https://github.com/galaxy-s10/billd-live',
   },
 ]);
 const swiperTimer = ref();
@@ -218,9 +213,11 @@ onUnmounted(() => {
   .swiper {
     width: 100%;
     height: 180px;
-    background-size: cover;
-    background-repeat: no-repeat;
-    background-position: center center;
+    // background-size: cover;
+    // background-repeat: no-repeat;
+    // background-position: center center;
+
+    @extend %coverBg;
   }
   .type-list {
     .item {
@@ -314,8 +311,9 @@ onUnmounted(() => {
             }
           }
           .desc {
-            font-size: 14px;
             margin-top: 4px;
+            font-size: 14px;
+
             @extend %singleEllipsis;
           }
         }

+ 4 - 10
src/views/h5/room/index.vue

@@ -4,9 +4,7 @@
       <div class="left">
         <div
           class="avatar"
-          :style="{
-            backgroundImage: `url(${anchorInfo?.avatar})`,
-          }"
+          v-lazy:background-image="anchorInfo?.avatar"
         ></div>
         <div class="username">
           {{ anchorInfo?.username }}
@@ -31,9 +29,7 @@
     >
       <div
         class="cover"
-        :style="{
-          backgroundImage: `url(${appStore.liveRoomInfo?.cover_img})`,
-        }"
+        v-lazy:background-image="appStore.liveRoomInfo?.cover_img"
       ></div>
       <div
         v-if="!roomLiving"
@@ -115,7 +111,7 @@
                     v-else
                   >
                     <img
-                      :src="item.msg"
+                      v-lazy="item.msg"
                       alt=""
                       @load="handleScrollTop"
                     />
@@ -175,9 +171,7 @@
               >
                 <div
                   class="avatar"
-                  :style="{
-                    backgroundImage: `url(${item.value.userInfo.avatar})`,
-                  }"
+                  v-lazy:background-image="item.value.userInfo.avatar"
                 ></div>
                 <div class="username">
                   {{ item.value.userInfo.username }}

+ 51 - 42
src/views/home/index.vue

@@ -1,21 +1,20 @@
 <template>
   <div class="home-wrap">
-    <!-- <SystemModal></SystemModal> -->
     <div class="play-container">
       <div
         v-if="configBg && configBg !== ''"
         class="bg-img"
-        :style="{ backgroundImage: `url(${configBg})` }"
+        v-lazy:background-image="configBg"
       ></div>
       <video
         v-if="configVideo && configVideo !== ''"
         class="bg-video"
-        :src="configVideo"
+        v-lazy="configVideo"
         muted
         autoplay
         loop
       ></video>
-      <div class="slider-wrap">
+      <!-- <div class="slider-wrap">
         <div
           v-for="(item, index) in interactionList"
           :key="index"
@@ -29,7 +28,7 @@
             :customStyle="{ margin: '0 auto' }"
           ></Slider>
         </div>
-      </div>
+      </div> -->
 
       <div class="container">
         <div
@@ -40,7 +39,7 @@
         >
           <div
             v-if="
-              currentLiveRoom?.live_room?.cdn === LiveRoomUseCDNEnum.yes ||
+              currentLiveRoom?.live_room?.cdn === LiveRoomUseCDNEnum.yes &&
               [
                 LiveRoomTypeEnum.tencent_css,
                 LiveRoomTypeEnum.tencent_css_pk,
@@ -50,28 +49,29 @@
           >
             <div class="txt">CDN</div>
           </div>
+          <div class="billd-logo">Billd直播</div>
           <div
             class="cover"
-            :style="{
-              backgroundImage: `url(${
-                currentLiveRoom?.live_room?.cover_img ||
-                currentLiveRoom?.user?.avatar
-              })`,
-            }"
+            v-lazy:background-image="
+              currentLiveRoom?.live_room?.cover_img ||
+              currentLiveRoom?.user?.avatar
+            "
           ></div>
           <div
             v-if="currentLiveRoom?.live_room?.flv_url"
             ref="remoteVideoRef"
           ></div>
           <template v-if="currentLiveRoom">
-            <VideoControls
-              @click.stop
-              :resolution="videoResolution"
-              @refresh="handleRefresh"
-              :control="{
-                line: true,
-              }"
-            ></VideoControls>
+            <div class="video-controls">
+              <VideoControls
+                :resolution="videoResolution"
+                @refresh="handleRefresh"
+                :control="{
+                  line: true,
+                }"
+              ></VideoControls>
+            </div>
+
             <div
               class="join-btn"
               :class="{
@@ -99,11 +99,9 @@
                 item: 1,
                 active: item.live_room_id === currentLiveRoom?.live_room_id,
               }"
-              :style="{
-                backgroundImage: `url(${
-                  item.live_room?.cover_img || item?.user?.avatar
-                })`,
-              }"
+              v-lazy:background-image="
+                item.live_room?.cover_img || item?.user?.avatar
+              "
               @click="changeLiveRoom(item)"
             >
               <PullAuthTip
@@ -116,7 +114,7 @@
                 <div
                   class="cdn-ico"
                   v-if="
-                    item?.live_room?.cdn === LiveRoomUseCDNEnum.yes ||
+                    item?.live_room?.cdn === LiveRoomUseCDNEnum.yes &&
                     [
                       LiveRoomTypeEnum.tencent_css,
                       LiveRoomTypeEnum.tencent_css_pk,
@@ -165,11 +163,9 @@
           >
             <div
               class="cover"
-              :style="{
-                backgroundImage: `url('${
-                  iten?.live_room?.cover_img || iten?.user?.avatar
-                }')`,
-              }"
+              v-lazy:background-image="
+                iten?.live_room?.cover_img || iten?.user?.avatar
+              "
             >
               <PullAuthTip
                 v-if="
@@ -179,7 +175,7 @@
               ></PullAuthTip>
               <div
                 v-if="
-                  iten?.live_room?.cdn === LiveRoomUseCDNEnum.yes ||
+                  iten?.live_room?.cdn === LiveRoomUseCDNEnum.yes &&
                   [
                     LiveRoomTypeEnum.tencent_css,
                     LiveRoomTypeEnum.tencent_css_pk,
@@ -374,8 +370,6 @@ function joinRoom(data: { roomId: number }) {
       z-index: -1;
       width: 100%;
       height: 100%;
-
-      // object-fit: fill;
     }
     .slider-wrap {
       padding: 4px 0;
@@ -385,6 +379,7 @@ function joinRoom(data: { roomId: number }) {
       justify-content: center;
       box-sizing: border-box;
       margin: 0 auto;
+      padding-top: 20px;
       height: calc($w-1100 / $video-ratio);
 
       .left {
@@ -419,12 +414,21 @@ function joinRoom(data: { roomId: number }) {
             font-size: 14px;
           }
         }
+        .billd-logo {
+          position: absolute;
+          top: 10px;
+          left: 10px;
+          z-index: 2;
+          color: rgba($color: #fff, $alpha: 0.4);
+          font-weight: bold;
+          font-size: 30px;
+        }
 
         .cover {
           position: absolute;
           background-position: center center;
           background-size: cover;
-          filter: blur(10px);
+          filter: blur(5px);
 
           inset: 0;
         }
@@ -452,12 +456,6 @@ function joinRoom(data: { roomId: number }) {
 
           user-select: none;
         }
-
-        &:hover {
-          .join-btn {
-            display: inline-flex !important;
-          }
-        }
         .join-btn {
           position: absolute;
           top: 50%;
@@ -465,7 +463,6 @@ function joinRoom(data: { roomId: number }) {
           z-index: 20;
           display: none;
           align-items: center;
-          align-items: center;
           justify-content: center;
           box-sizing: border-box;
           // width: 80%;
@@ -488,6 +485,18 @@ function joinRoom(data: { roomId: number }) {
             }
           }
         }
+        .video-controls {
+          display: none;
+        }
+
+        &:hover {
+          .join-btn {
+            display: block;
+          }
+          .video-controls {
+            display: block;
+          }
+        }
       }
       .right {
         display: inline-block;

+ 1 - 1
src/views/order/index.vue

@@ -20,7 +20,7 @@
         <div class="user">
           <template v-if="item.user">
             <img
-              :src="item.user.avatar"
+              v-lazy="item.user.avatar"
               class="avatar"
               alt=""
             />

+ 10 - 16
src/views/pull/index.vue

@@ -7,14 +7,14 @@
       <video
         v-if="configVideo && configVideo !== ''"
         class="bg-video"
-        :src="configVideo"
+        v-lazy="configVideo"
         muted
         autoplay
         loop
       ></video>
       <div
         v-if="configBg && configBg !== ''"
-        :style="{ backgroundImage: `url(${configBg})` }"
+        v-lazy:background-image="configBg"
       ></div>
     </div>
     <div class="left">
@@ -25,9 +25,7 @@
         <div class="info">
           <div
             class="avatar"
-            :style="{
-              backgroundImage: `url(${anchorInfo?.avatar})`,
-            }"
+            v-lazy:background-image="anchorInfo?.avatar"
             @click="
               router.push({
                 name: routerName.my,
@@ -96,7 +94,7 @@
                   >
                     <div
                       class="ico"
-                      :style="{ backgroundImage: `url(${item.goods?.cover})` }"
+                      v-lazy:background-image="item.goods?.cover"
                     ></div>
                     <div class="nums">x{{ item.nums }}</div>
                   </div>
@@ -120,11 +118,9 @@
         </div>
         <div
           class="cover"
-          :style="{
-            backgroundImage: `url(${
-              appStore.liveRoomInfo?.cover_img || anchorInfo?.avatar
-            })`,
-          }"
+          v-lazy:background-image="
+            appStore.liveRoomInfo?.cover_img || anchorInfo?.avatar
+          "
         ></div>
         <div
           class="remote-video"
@@ -152,7 +148,7 @@
         >
           <div
             class="ico"
-            :style="{ backgroundImage: `url(${item.cover})` }"
+            v-lazy:background-image="item.cover"
           >
             <div
               v-if="item.badge"
@@ -197,9 +193,7 @@
             >
               <div
                 class="avatar"
-                :style="{
-                  backgroundImage: `url(${item.value.userInfo.avatar})`,
-                }"
+                v-lazy:background-image="item.value.userInfo.avatar"
               ></div>
               <div class="username">
                 {{ item.value.userInfo.username }}
@@ -310,7 +304,7 @@
               v-else
             >
               <img
-                :src="item.msg"
+                v-lazy="item.msg"
                 alt=""
                 @load="handleScrollTop"
               />

+ 2 - 3
src/views/push/index.vue

@@ -83,7 +83,7 @@
         <div class="info">
           <div
             class="avatar"
-            :style="{ backgroundImage: `url(${userStore.userInfo?.avatar})` }"
+            v-lazy:background-image="userStore.userInfo?.avatar"
           ></div>
           <div class="detail">
             <div class="top">
@@ -334,7 +334,7 @@
                   v-else
                 >
                   <img
-                    :src="item.msg"
+                    v-lazy="item.msg"
                     alt=""
                     @load="handleScrollTop"
                   />
@@ -1881,7 +1881,6 @@ function setScaleInfo({ track, canvasDom, scale = 1 }) {
 }
 
 async function addMediaOk(val: AppRootState['allTrack'][0]) {
-  console.log('addMediaOk');
   showMediaModalCpt.value = false;
   if (val.type === MediaTypeEnum.screen) {
     const event = await handleDisplayMedia({

+ 79 - 70
src/views/rank/index.vue

@@ -77,11 +77,13 @@
               currRankType !== RankTypeEnum.blog && handleJump(item.users[0])
             "
           >
-            <img
-              :src="item.users[0]?.avatar"
-              class="avatar"
-              alt=""
-            />
+            <Avatar
+              :size="28"
+              :avatar="item.users[0]?.avatar"
+              :living="!!item.live?.live"
+              :border="!item.users[0]?.avatar?.length"
+              disableLiving
+            ></Avatar>
             <div class="username">{{ item.users[0]?.username }}</div>
             <div
               class="wallet"
@@ -116,7 +118,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref } from 'vue';
+import { onMounted, reactive, ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 
 import { fetchLiveRoomList } from '@/api/liveRoom';
@@ -161,6 +163,11 @@ const rankTypeList = ref<IRankType[]>([
 
 const mockDataNums = 4;
 
+const pageParams = reactive({
+  nowPage: 1,
+  pageSize: 100,
+});
+
 const currRankType = ref(RankTypeEnum.liveRoom);
 const { t } = useI18n();
 const mockRank: {
@@ -172,66 +179,66 @@ const mockRank: {
   signin_nums: number;
   live?: ILiveRoom;
 }[] = [
-  {
-    users: [
-      {
-        id: -1,
-        username: '待上榜',
-        avatar: '',
-      },
-    ],
-    rank: 1,
-    level: 0,
-    score: 0,
-    balance: 0,
-    signin_nums: 0,
-    live: undefined,
-  },
-  {
-    users: [
-      {
-        id: -1,
-        username: '待上榜',
-        avatar: '',
-      },
-    ],
-    rank: 2,
-    level: 0,
-    score: 0,
-    balance: 0,
-    signin_nums: 0,
-    live: undefined,
-  },
-  {
-    users: [
-      {
-        id: -1,
-        username: '待上榜',
-        avatar: '',
-      },
-    ],
-    rank: 3,
-    level: 0,
-    score: 0,
-    balance: 0,
-    signin_nums: 0,
-    live: undefined,
-  },
-  {
-    users: [
-      {
-        id: -1,
-        username: '待上榜',
-        avatar: '',
-      },
-    ],
-    rank: 4,
-    level: 0,
-    score: 0,
-    balance: 0,
-    signin_nums: 0,
-    live: undefined,
-  },
+  // {
+  //   users: [
+  //     {
+  //       id: -1,
+  //       username: '待上榜',
+  //       avatar: '',
+  //     },
+  //   ],
+  //   rank: 1,
+  //   level: 0,
+  //   score: 0,
+  //   balance: 0,
+  //   signin_nums: 0,
+  //   live: undefined,
+  // },
+  // {
+  //   users: [
+  //     {
+  //       id: -1,
+  //       username: '待上榜',
+  //       avatar: '',
+  //     },
+  //   ],
+  //   rank: 2,
+  //   level: 0,
+  //   score: 0,
+  //   balance: 0,
+  //   signin_nums: 0,
+  //   live: undefined,
+  // },
+  // {
+  //   users: [
+  //     {
+  //       id: -1,
+  //       username: '待上榜',
+  //       avatar: '',
+  //     },
+  //   ],
+  //   rank: 3,
+  //   level: 0,
+  //   score: 0,
+  //   balance: 0,
+  //   signin_nums: 0,
+  //   live: undefined,
+  // },
+  // {
+  //   users: [
+  //     {
+  //       id: -1,
+  //       username: '待上榜',
+  //       avatar: '',
+  //     },
+  //   ],
+  //   rank: 4,
+  //   level: 0,
+  //   score: 0,
+  //   balance: 0,
+  //   signin_nums: 0,
+  //   live: undefined,
+  // },
 ];
 const rankList = ref(mockRank);
 
@@ -258,7 +265,7 @@ function handleJoin(item) {
 async function getWalletList() {
   try {
     fullLoading({ loading: true });
-    const res = await fetchWalletList({});
+    const res = await fetchWalletList({ ...pageParams });
     if (res.code === 200) {
       const length = res.data.rows.length;
       rankList.value = res.data.rows.map((item, index) => {
@@ -292,10 +299,10 @@ async function getLiveRoomList() {
   try {
     fullLoading({ loading: true });
     const res = await fetchLiveRoomList({
-      hidden_cover_img: true,
       is_show: LiveRoomIsShowEnum.yes,
       orderName: 'updated_at',
       orderBy: 'desc',
+      ...pageParams,
     });
     if (res.code === 200) {
       const length = res.data.rows.length;
@@ -333,6 +340,7 @@ async function getUserList() {
     const res = await fetchUserList({
       orderName: 'updated_at',
       orderBy: 'desc',
+      ...pageParams,
     });
     if (res.code === 200) {
       const length = res.data.rows.length;
@@ -369,6 +377,7 @@ async function getSigninList() {
     const res = await fetchSigninList({
       orderName: 'sum_nums',
       orderBy: 'desc',
+      ...pageParams,
     });
     if (res.code === 200) {
       const length = res.data.rows.length;
@@ -519,8 +528,8 @@ onMounted(() => {
         }
 
         .avatar {
-          margin-top: -50px;
           display: inline-block;
+          margin-top: -50px;
           cursor: pointer;
         }
 
@@ -580,12 +589,12 @@ onMounted(() => {
           font-size: 12px;
           cursor: pointer;
           .avatar {
-            margin-right: 10px;
             width: 28px;
             height: 28px;
             border-radius: 50%;
           }
           .username {
+            margin-left: 10px;
             width: 100px;
 
             @extend %singleEllipsis;

+ 3 - 2
src/views/shop/index.vue

@@ -23,7 +23,7 @@
       >
         <div
           class="left"
-          :style="{ backgroundImage: `url(${item.cover})` }"
+          v-lazy:background-image="item.cover"
         >
           <div
             v-if="item.badge"
@@ -64,6 +64,7 @@ import { useRoute } from 'vue-router';
 
 import { fetchGoodsList } from '@/api/goods';
 import QrPayCpt from '@/components/QrPay/index.vue';
+import { URL_PARAMS } from '@/constant';
 import { GoodsTypeEnum, IGoods } from '@/interface';
 import router from '@/router';
 import { formatMoney } from '@/utils';
@@ -105,7 +106,7 @@ async function getGoodsList(type: GoodsTypeEnum) {
 }
 
 onMounted(() => {
-  const key = route.query.goodsType as GoodsTypeEnum;
+  const key = route.query[URL_PARAMS.goodsType] as GoodsTypeEnum;
   if (GoodsTypeEnum[key]) {
     currTab.value = key;
   } else {

+ 4 - 0
src/views/wallet/index.vue

@@ -44,6 +44,7 @@ import { useI18n } from 'vue-i18n';
 import { fetchMyWallet } from '@/api/wallet';
 import { fetchWalletRecordMyList } from '@/api/walletRecord';
 import { fullLoading } from '@/components/FullLoading';
+import { loginTip } from '@/hooks/use-login';
 import {
   IWalletRecord,
   WalletRecordAmountStatusEnum,
@@ -86,6 +87,9 @@ const typeMap = {
 };
 
 onMounted(() => {
+  if (!loginTip()) {
+    return;
+  }
   updateMyWallet();
   getPayList();
 });

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini