<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          分享一個(gè)自用實(shí)用骨架屏實(shí)現(xiàn)方案!

          共 18801字,需瀏覽 38分鐘

           ·

          2024-04-15 07:55

          作者:背對(duì)疾風(fēng)
          原文:https://juejin.cn/post/6945748911147450405

          在我的前前公司,接口經(jīng)常出問題,基本每個(gè)接口都會(huì)出問題,這就要求我們前端對(duì)每個(gè)接口都要做錯(cuò)誤信息的提醒,非常麻煩,當(dāng)然這也是必須的,所以就想著做一個(gè)組件能夠幫我們自動(dòng)處理和展示這些錯(cuò)誤信息,當(dāng)時(shí)開發(fā)用的是Flutter,所以應(yīng)該叫widget。后來又做vue和小程序了,想著能不能把那個(gè)解決方法帶到vue上來,于是就有了這個(gè)

          功能

          • 以骨架屏的形式展示加載中,而且可以絲滑過渡到加載完成
          • 加載失敗展示錯(cuò)誤信息

          先看效果

          test3.gif

          原理

          一般情況下,我們請(qǐng)求接口然后渲染數(shù)據(jù)時(shí)會(huì)先判斷是否有數(shù)據(jù)來來渲染不同的視圖,比如加載的展位圖,空數(shù)據(jù)的占位圖,有數(shù)據(jù)時(shí)就展示數(shù)據(jù)。

          ClamView的思路則是 數(shù)據(jù)加載未完成時(shí)也給渲染的模板一個(gè)數(shù)據(jù),然后通過給負(fù)責(zé)顯示數(shù)據(jù)span、img等標(biāo)簽設(shè)置背景色和字體顏色來達(dá)到“骨架”的效果,待數(shù)據(jù)請(qǐng)求完成后,再使用動(dòng)畫將骨架隱去,完成過渡。

          代碼

          • ClamView.tsx

            ClamView 需要傳入一個(gè) ResponseBean點(diǎn)此了解[1] 對(duì)象 res 來判斷當(dāng)前數(shù)據(jù)的狀態(tài),還需要傳入一個(gè)加載過程中用到的假數(shù)據(jù) emptyData 用來撐起你的 span 標(biāo)簽

          import { defineComponent,computed } from 'vue';
          import {ResponseBean} from "bdjf_http";
          import './clam_view.css'
          import './skeleton.css'

          /**
           * 定義 ClamView 的四種狀態(tài)
           * 1. LOADING res 為空或者res.code === -100 時(shí)狀態(tài)為LOADING,此時(shí)顯示骨架屏
           * 2. EMPTY res.code  === 0 且 res.data 為空時(shí)
           * 3. SHOW res.code  === 0 且 res.data 不為空時(shí)
           * 4. ERROR res.code  !== 0 時(shí)
           **/

          type ViewStatusType = 'LOADING'|'EMPTY'|'SHOW'|'ERROR';

          export default defineComponent({
              name:'ClamView',
              props:{
                  res: {
                      type:ResponseBean,
                      default:()=>{
                          // 默認(rèn)顯示一個(gè)loading
                          return new ResponseBean().loading();
                      }
                  },
                  showLoading :{
                      typeBoolean,
                      default:()=>{
                          return false;
                      }
                  },
                  emptyText:{
                      typeString,
                      default:()=>{
                          return '暫無數(shù)據(jù)';
                      }
                  },
                  emptyData:{
                      type:Object,
                      default:()=>{
                          return {}
                      }
                  },
                  noPackage:{
                      typeBoolean,
                      default:()=>{
                          return false;
                      }
                  }
              },
              setup(props,{  slots }) {

                  // 根據(jù) res 的狀態(tài)來判斷如何顯示
                  const viewStatusAdapter = (response: ResponseBean): ViewStatusType => {
                      // console.log('----viewStatusAdapter----',response)
                      if (props.showLoading) {
                          return "LOADING";
                      }
                      if (!response) {
                          return "LOADING";
                      }
                      switch (response.code) {
                          case 0:
                              if (!response.data || response.data.length === 0) {
                                  return "EMPTY";
                              } else {
                                  return "SHOW";
                              }
                          case -100:
                              return "LOADING";
                          default:
                              return "ERROR";
                      }
                  }

                  // 用 computed 包一下
                  const viewStatus = computed<ViewStatusType>(()=>{
                      return viewStatusAdapter(props.res)
                  })

                  const noDataView = (text:string)=>{
                      return (
                          <div class="empty_view col-center item-center">
                              {text}
                          </div>

                      )
                  }

                  const emptyView = ()=>{
                      if(viewStatus.value === 'EMPTY'){
                          return slots.empty?slots.empty():noDataView(props.emptyText);
                      }
                  }

                  const errorView = ()=>{
                      if(viewStatus.value === 'ERROR'){
                           return slots.error?slots.error():noDataView(props.res.msg);
                      }
                  }



                  return () => {
                      if(viewStatus.value === 'EMPTY'){
                          return emptyView();
                      }else if(viewStatus.value === 'ERROR'){
                          return errorView();
                      }else {
                          // noPackage 為 false 時(shí),ClamView將會(huì)在 slots 外面包一層 div ,通過給div更換樣式來實(shí)現(xiàn)狀態(tài)切換;
                          // 為 true 時(shí),將不會(huì)包裹div,會(huì)通過 vClass 屬性傳替給 需要使用的地方綁定樣式進(jìn)行切換
                          if(props.noPackage){
                              return slots.default({
                                  data:viewStatus.value==='LOADING'?props.emptyData:props.res.data,
                                  vClass:viewStatus.value==='LOADING'?'skeleton-view-empty-view':'skeleton-view-default-view'
                              })
                          }else {
                              return (
                                  <div  class={viewStatus.value==='LOADING'?'skeleton-view-empty-view':'skeleton-view-default-view'}>
                                      {slots.default({
                                          data:viewStatus.value==='LOADING'?props.emptyData:props.res.data
                                      })}
                                  </div>

                              )
                          }
                      }
                  }
              }
          })
          • skeleton.css
          /**
          正常狀態(tài)下的 樣式,
          設(shè)置 transition 來讓過渡平滑
          */

          .skeleton-view-default-view span,
          .skeleton-view-default-view a,
          .skeleton-view-default-view img
          {
              transition: all .7s ease;
              background-colorrgba(0000);
          }


          /**
          加載時(shí)的樣式,首先設(shè)置不監(jiān)聽任何事件,省的用戶亂點(diǎn)
          然后給 span、a、img標(biāo)簽設(shè)置可以動(dòng)的背景,字體顏色設(shè)成透明,
          就形成 “骨架” 了
          */

          .skeleton-view-empty-view {
              pointer-events: none;
          }
          .skeleton-view-empty-view span,
          .skeleton-view-empty-view a {
              colorrgba(0000!important;
              border-radius2px;
              backgroundlinear-gradient(
                      -45deg,
                      #F5F5F5 0%,
                      #DCDCDC 25%,
                      #F5F5F5 50%,
                      #DCDCDC 75%,
                      #F5F5F5 100%
              );
              animation: gradientBG 4s ease infinite;
              background-size400% 400%;
              background-color:#DCDCDC;
              transition: all 1s ease;
          }

          .skeleton-view-empty-view img {
              /* 這里是一個(gè)透明的小圖片 */
              contenturl(../../assets/img/no_url.png);
              border-radius2px;
              backgroundlinear-gradient(
                      -45deg,
                      #F5F5F5 0%,
                      #DCDCDC 25%,
                      #F5F5F5 50%,
                      #DCDCDC 75%,
                      #F5F5F5 100%
              );
              animation: gradientBG 4s ease infinite;
              background-size400% 400%;
              background-color:#DCDCDC;
              transition: all 1s ease;
          }
          @keyframes gradientBG {
              0% {
                  background-position100% 100%;
              }
              50% {
                  background-position0% 0%;
              }
              100% {
                  background-position100% 100%;
              }

          }
          • clam_view.css
          .clam-box{
              width100%;
              height100%;
          }
          .empty_view{
              padding-top100px;
              width100%;
              height100%;
              padding-bottom100px;
          }
          .empty_img{
              width310px;
              height218px;
          }
          .trip_text{
              font-size28px;
              color#999999;
          }

          使用

          <template>
            <div class="home col">
              <clam-view :res="response" v-slot="{data}" :empty-data="emptyData">
                <p><span>{{data.name}}</span></p>
                <p>Home</p>
                <router-link to="/about" >{{data.route}}</router-link>
              </clam-view>
            </div>
          </template>
          ts
          復(fù)制代碼
          <script lang="ts">
          import { defineComponent,reactive,toRefs,onMounted } from 'vue';
          import {ResponseBean} from 'bdjf_http'

          export default defineComponent({
            name: 'Home',
            setup(){

              const state = reactive({
                response:new ResponseBean().loading()
              })

              onMounted(()=>{
                setTimeout(()=>{
                  state.response = new ResponseBean(0,'',{
                    name:'Home',
                    route:'About'
                  })
                },2500)
              })

              const emptyData = {
                name:'站位文字',
                route:'站位文字'
              }

              return {
                ...toRefs(state),
                emptyData
              }
            }
          });
          </script>

          <style scoped>
          </
          style>

          配合bdjf_http

          如果你配合 bdjf_http 使用,就能用極少的代碼完成所需功能 點(diǎn)此了解bdjf\_http[2]

          post(API.getData())
          .then(res => state.response = res;)
          參考資料
          [1]

          https://juejin.cn/post/6945373247659573278

          [2]

          https://juejin.cn/post/6945373247659573278

          最后



          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我個(gè)小忙:

          1. 點(diǎn)個(gè)「喜歡」或「在看」,讓更多的人也能看到這篇內(nèi)容

          2. 我組建了個(gè)氛圍非常好的前端群,里面有很多前端小伙伴,歡迎加我微信「sherlocked_93」拉你加群,一起交流和學(xué)習(xí)

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。



          點(diǎn)個(gè)喜歡支持我吧,在看就更好了


          瀏覽 154
          10點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          10點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色色的视频在线观看 | 337P亚洲精品色噜噜狠狠 | 91麻豆视频日播 | 91人妻人人澡人人 | 亚洲国产永久精品成人麻豆 |