Vue3+TypeScript+Django Rest Framework 搭建個人博客(六):踩坑匯總

博客網(wǎng)站已經(jīng)部署完成流码,在過程中我們解決了很多問題,曾子曰:吾日三省延刘,這一篇就是總結(jié)我們遇到的問題漫试,俗稱:踩坑

大家好纤怒,我是落霞孤鶩痘煤,經(jīng)過幾個星期的努力对室,我們已經(jīng)把開發(fā)的博客部署上線了甚颂,這一章我們主要講講過程中解決的比較麻煩的問題和可以作為經(jīng)驗的地方。

一今膊、軟件版本

關(guān)于軟件版本的選擇铅忿,實際上沒有特定的標(biāo)準(zhǔn)猜煮,整理而言撼班,我個人的開發(fā)是后端盡可能選擇穩(wěn)定的版本歧匈,前端盡可能跟隨時代潮流。因為前端的變化很快砰嘁,每個月都在推出新的版本和新的解決方案件炉,而后端更注重的是業(yè)務(wù)邏輯的完備度和系統(tǒng)的穩(wěn)定性勘究,因此在技術(shù)選型的時候,盡可能選擇穩(wěn)定版本斟冕。

1.1 Python 技術(shù)棧選型

  • Python

目前 Python的版本已經(jīng)到 3.10 版本口糕,但我沒有選擇最高的版本,而是選擇了 3.7 版本磕蛇,這里面有幾個考量:

  1. Django 版本對 Python 版本的支持程度
  2. Django 生態(tài)中對 Python 和 Django 版本的支持程度景描,比如Django Rest Framework、Django-Filter秀撇、Django-MPTT等超棺。
  • Django

在選型中,基于核心框架進(jìn)行選擇捌袜,Django 的版本我沒有選擇最新的 3.0说搅,而是選擇了 2.2 版本中最新版本炸枣。在這樣的選擇下虏等,我這邊在使用第三方包時,就不用太擔(dān)心第三方包對 Python 和 Django 版本的兼容問題适肠。

可能有人會疑惑霍衫,為什么沒有選擇 Python 2,我的觀點是侯养,畢竟 Python 3才是后面的發(fā)展方向敦跌。

1.2 Vue 技術(shù)棧選型

  • Vue

目前 Vue 也存在兩個版本,我們這里選擇了 Vue 3逛揩,雖然 Vue 2 更成熟柠傍,但考慮到前端是一個發(fā)展非常快的領(lǐng)域辩稽,因此盡可能選擇跟隨時代潮流的軟件和工具惧笛。

  • 構(gòu)建工具

構(gòu)建工具選擇了 Vite,確實熱編譯的速度太快了逞泄,我們的博客網(wǎng)站由于代碼量較少患整,幾乎感覺不到有延遲,修改完立馬生效喷众。

  • TypeScript

TypeScript 的選擇也是前端領(lǐng)域里面的一種趨勢各谚,它更適合多人協(xié)作的中大型項目,由于 JavaScript 是弱類型的動態(tài)語言到千,在類型判斷時無法及時有效的發(fā)現(xiàn)潛在的問題昌渤,而通過 TypeScript的語法,可以很好的約束和規(guī)范代碼憔四,在編寫階段就能發(fā)現(xiàn)盡可能多的問題膀息。

  • CSS

我們使用了 Less 作為 CSS 的預(yù)編譯器望抽,從而支持更靈活的 CSS 定義和管理。

  • UI 組件 Element-Plus

是國內(nèi)比較成熟的UI組件庫履婉,是Element-UI 對 Vue 3 版本的支持版本煤篙。

二、后端踩坑記

2.1 Django跨域問題

我們的博客網(wǎng)站使用的是Session認(rèn)證機(jī)制毁腿,而Django默認(rèn)會驗證跨域cookieheader辑奈,因此我們需要做 3 件事情

2.1.1 去掉中間件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

2.1.2 編寫不驗證CSRF header 中間件

from django.utils.deprecation import MiddlewareMixin


class DisableCSRF(MiddlewareMixin):
    def process_request(self, request):
        setattr(request, '_dont_enforce_csrf_checks', True)

2.1.3 加入自定義的中間件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'project.middleware.request.DisableCSRF',
]

2.2 擴(kuò)充Django用戶表

Django中默認(rèn)的用戶表,屬性一般不能完全滿足業(yè)務(wù)需求已烤,因此會考慮擴(kuò)充表屬性鸠窗,這里需要做幾個點:

2.2.1 繼承AbstractUser

class User(AbstractUser, AbstractBaseModel):
    avatar = models.CharField('頭像', max_length=1000, blank=True)
    nickname = models.CharField('昵稱', null=True, blank=True, max_length=200)

    class Meta(AbstractUser.Meta):
        db_table = 'blog_user'
        swappable = 'AUTH_USER_MODEL'

2.2.2 修改認(rèn)證用戶表名稱

settings.py 中增加一條配置

AUTH_USER_MODEL = 'common.User'

2.2.3 自定義登錄和登出接口

這里通過調(diào)用Django自帶的方法authenticatelogin完成賬號認(rèn)證和登錄,這里面實現(xiàn)了session管理的機(jī)制

登錄代碼:

def post(self, request, *args, **kwargs):
  username = request.data.get('username', '')
  password = request.data.get('password', '')

  user = authenticate(username=username, password=password)
  if user is not None and user.is_active:
    login(request, user)
    serializer = UserSerializer(user)
    return Response(serializer.data, status=200)
  else:
    ret = {'detail': 'Username or password is wrong'}
    return Response(ret, status=403)

通過django自帶的方法auth_logout實現(xiàn)登出胯究,這里面實現(xiàn)了session失效機(jī)制稍计。代碼如下:

def get(self, request, *args, **kwargs):
  auth_logout(request)
  return Response({'detail': 'logout successful !'})

2.3 Filter 外鍵查詢

在列表查詢中,一般都會提供各種入?yún)l件來完成查詢裕循,這里我們使用的是Django-Filter第三方包實現(xiàn)的臣嚣。具體教程見:django-filter — django-filter 2.4.0 documentation

如果希望能通過外鍵ID作為過濾條件,比如通過分類ID查詢文章列表剥哑,同時也希望在返回的列表中能展示分類名稱硅则,此時就需要做如下操作。

serializer中株婴,對外鍵字段不做什么調(diào)整怎虫,采用默認(rèn)方式,對返回結(jié)果中的分類信息困介,通過SerializerMethodField定一個只讀字段實現(xiàn)大审。

如下代碼,catalog_info通過方法定義座哩,而原始的catalog字段徒扶,不做任何定義,默認(rèn)即可八回,然后在field中列出酷愧。這樣就可以在查詢條件中,通過分類ID來過濾文章列表缠诅。

class ArticleListSerializer(serializers.ModelSerializer):
    tags_info = serializers.SerializerMethodField(read_only=True)
    catalog_info = serializers.SerializerMethodField(read_only=True)
    status = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'excerpt', 'cover', 'created_at', 'modified_at', 'tags',
                  'tags_info', 'catalog', 'catalog_info', 'views', 'comments', 'words', 'likes', 'status', ]

        extra_kwargs = {
            'tags': {'write_only': True},
            'catalog': {'write_only': True},
            'views': {'read_only': True},
            'comments': {'read_only': True},
            'words': {'read_only': True},
            'likes': {'read_only': True},
            'created_at': {'read_only': True},
            'modified_at': {'read_only': True},
        }

2.4 路由定義

通過Rest Framework中的routers.DefaultRouter()定義路由后溶浴,如果在project/urls.py中通過include方式引入,則需要在app中的urls.py中定義app

blog/urls.py的代碼:

from django.urls import include, path
from rest_framework import routers

from blog import views

router = routers.DefaultRouter()
router.register('article', views.ArticleViewSet)

app_name = 'blog'

urlpatterns = [
    path('', include(router.urls)),
]

project/urls.py的代碼

path('', include('blog.urls', namespace='blog')),

2.5 前后端路由區(qū)分

在部署的時候管引,為了方便Nginx的路由區(qū)分代理士败,我們在后端的所有接口都增加api前綴,這也符合open api規(guī)范要求。

所以在project/urls.py 中谅将,所有的接口前都增加api前綴漾狼。

path('api/', include('blog.urls', namespace='blog')),
path('api/', include('common.urls', namespace='common')),

2.6 Windows 和 類 Unix 系統(tǒng)路徑兼容

在我們開發(fā)的時候, 我們使用的是windows系統(tǒng)饥臂,其路徑表達(dá)方式是\逊躁,而部署的時候是放在類unix 系統(tǒng)中,其路徑表達(dá)方式是 /隅熙,為了兼容兩種表達(dá)方式稽煤,Python 默認(rèn)使用 / 方式,因此在上傳文件的時候囚戚,統(tǒng)一成一種方式酵熙,將 \ 轉(zhuǎn)換成 /。

def get_upload_file_path(upload_name):
    # Generate date based path to put uploaded file.
    date_path = datetime.now().strftime('%Y/%m/%d')

    # Complete upload path (upload_path + date_path).
    upload_path = os.path.join(settings.UPLOAD_URL, date_path)
    full_path = os.path.join(settings.BASE_DIR, upload_path)
    make_sure_path_exist(full_path)
    file_name = slugify_filename(upload_name)
    return os.path.join(full_path, file_name).replace('\\', '/'), os.path.join('/', upload_path, file_name).replace('\\', '/')

2.7 表中自動添加創(chuàng)建時間和修改時間

在業(yè)務(wù)表中驰坊,我們一般都會填寫時間戳字段匾二,用來記錄創(chuàng)建時間和最后一次修改時間,如果每一個表都要單獨定義和維護(hù)拳芙,是一件非常麻煩的事情察藐,我們通過定義抽象類實現(xiàn)。

DjangoORM提供了兩個非常有用的字段類屬性auto_now_addauto_now

  • auto_now_add 表示新增時自動寫入當(dāng)前時間
  • auto_now表示修改時自動寫入當(dāng)前時間
class AbstractBaseModel(models.Model):
    creator = models.IntegerField('creator', null=True)
    created_at = models.DateTimeField(verbose_name='Created at', auto_now_add=True)

    modifier = models.IntegerField('modifier', null=True)
    modified_at = models.DateTimeField(verbose_name='Modified at', auto_now=True)

    class Meta:
        abstract = True

然后所有的模型類繼承這個抽象類态鳖,就可以自動完成創(chuàng)建時間和修改時間的維護(hù)转培。

2.8 自動記錄創(chuàng)建人和修改人

在模型中我們自動完成了創(chuàng)建時間和修改時間的維護(hù),那么創(chuàng)建人和修改人可以通過定義公共的ViewSet類方法完成浆竭。

通過重寫perform_updateperform_create方法,然后所有業(yè)務(wù)類通過混入( Python 可以多繼承)繼承的方式惨寿,實現(xiàn)自動維護(hù)這兩個字段的信息邦泄。

class BaseViewSetMixin(object):
    def perform_update(self, serializer):
        user = self.fill_user(serializer, 'update')
        return serializer.save(**user)

    def perform_create(self, serializer):
        user = self.fill_user(serializer, 'create')
        return serializer.save(**user)

    @staticmethod
    def fill_user(serializer, mode):
        """
        before save, fill user info into para from session
        :param serializer: Model's serializer
        :param mode: create or update
        :return: None
        """
        request = serializer.context['request']

        user_id = request.user.id
        ret = {'modifier': user_id}

        if mode == 'create':
            ret['creator'] = user_id
        return ret


class BaseModelViewSet(BaseViewSetMixin, viewsets.ModelViewSet):
    pass

  
class ArticleViewSet(BaseViewSetMixin,
                     mixins.CreateModelMixin,
                     mixins.RetrieveModelMixin,
                     mixins.UpdateModelMixin,
                     mixins.DestroyModelMixin,
                     GenericViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def perform_create(self, serializer):
        extra_infos = self.fill_user(serializer, 'create')
        extra_infos['author'] = self.request.user
        serializer.save(**extra_infos)

    def filter_queryset(self, queryset):
        queryset = super(ArticleViewSet, self).filter_queryset(queryset)
        if self.is_reader():
            queryset = queryset.exclude(status=Constant.ARTICLE_STATUS_DRAFT).exclude(
                status=Constant.ARTICLE_STATUS_DELETED)
        return queryset

    def perform_destroy(self, instance: Article):
        instance.status = Constant.ARTICLE_STATUS_DELETED
        instance.save()

    def retrieve(self, request, *args, **kwargs):
        instance: Article = self.get_object()
        serializer = self.get_serializer(instance)
        if self.is_reader():
            instance.views += 1
            instance.save()
        return Response(serializer.data)

三、前端踩坑記

3.1 構(gòu)建工具 Vite

3.1.1 vite.config.ts

這個文件的配置點不多裂垦,但卻非常重要顺囊,包括后端接口代理地址,BASE URL 等蕉拢。

  1. 代理地址配置

有幾個點需要注意:

proxy 下出現(xiàn)的 key 是我們在調(diào)試時特碳,前端頁面訪問后端接口的時候,通過 src/api/index.ts 中配置的 URL 前綴晕换,增加了api 前綴午乓,然后通過 rewrite 規(guī)則,決定對該前綴是否做替換闸准,這里自動增加 api 前綴的原因是因為Vite自己本身也是一個靜態(tài)資源服務(wù)器益愈,為了區(qū)分請求是通過Vite代理的靜態(tài)資源還是后端接口而做的處理。

vite.config.ts 核心代碼如下:

server: {
        host: "localhost",
        port: 3000,
        proxy: {
            '/api': {
                target: 'http://localhost:8000/',
                changeOrigin: true,
                ws: false,
                rewrite: (pathStr) => pathStr.replace('/api', '/api'),
                timeout: 5000,
            },
            '/upload': {
                target: 'http://localhost:8000/',
                changeOrigin: true,
                ws: false,
                rewrite: (pathStr) => pathStr.replace('/api', ''),
                timeout: 5000,
            },
        },
    }

src/api/index.ts核心代碼:

const request = axios.create({
    baseURL: import.meta.env.MODE !== 'production' ? '/api' : '',
})
  1. BASE 地址配置

在配置服務(wù)器地址的時候,Vite 提供的是base 屬性蒸其。這里有兩種配置方式base: '/'base: './'敏释,我們配置的是第一種,這里取決于我們使用的路由模式

  • 如果使用的是 History模式摸袁,則使用第一種
  • 如果使用的是 Hash 模式钥顽,則使用第二種
 base: '/',
  1. Vue 插件配置

需要 Vite 支持 Vue,通過引入Vue插件方式實現(xiàn)靠汁,核心代碼:

plugins: [
    vue(),
],

3.1.2 package.json

如果要使用 Vite耳鸯,需要在這個文件里面配置scripts。然后才能在命令行中使用yarn devyarn build命令膀曾。核心代碼如下:

"scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "serve": "vite preview"
},

3.1.3 vite-evn.d.ts

文件位置:src/vite-evn.d.ts县爬,核心代碼:

/// <reference types="vite/client" />

3.2 TypeScript 配置

啟用 TypeScript 需要配置幾個地方,一個是配置文件添谊,一個是package.jsonbuild命令财喳。

3.2.1 tsconfig.json

核心代碼:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

3.2.2 package.json

scripts部分的build命令中,主要是在編譯前先基于TypeScript語法進(jìn)行檢測斩狱,如果有語法不正確的地方耳高,則編譯不通過,用這種方式可以保證在運(yùn)行之前發(fā)現(xiàn)盡可能多的問題所踊。

"scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "serve": "vite preview"
},

3.3 懶加載

3.3.1 圖片懶加載

博客都有封面泌枪,在博客列表界面,會有較多的圖片同時加載秕岛,因此可以通過限流實現(xiàn)圖片的按需延遲加載碌燕,減少網(wǎng)絡(luò)流量和壓力。

實現(xiàn)的方式主要綁定瀏覽器的滾動實踐继薛,滾動時實現(xiàn)分類展示修壕,同時計算文章列表界面上的滾動位置距離屏幕底部的距離,當(dāng)圖片馬上要進(jìn)入到可視區(qū)域是遏考,開始加載圖片慈鸠。

我們實現(xiàn)的方式:

1、就是創(chuàng)建一個自定義屬性data-src存放真正需要顯示的圖片路徑灌具,而img自帶的src放一張大小為通用的圖片路徑青团。
2、當(dāng)頁面滾動直至此圖片出現(xiàn)在可視區(qū)域時咖楣,用取到該圖片的data-src的值并賦值各src督笆,然后將data-has-lazy-src設(shè)置為true,表示已經(jīng)加載過圖片截歉。

TypeScript核心代碼:

const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const lazyload = throttle(() => {
  const imgs = document.querySelectorAll("#list .item img");
  let num = 0;
  for (let i = num; i < imgs.length; i++) {
    let distance = viewHeight - imgs[i].getBoundingClientRect().top;
    let imgItem: any = imgs[i];
    if (distance >= 100) {
      let hasLaySrc = imgItem.getAttribute("data-has-lazy-src");
      if (hasLaySrc === "false") {
        imgItem.src = imgItem.getAttribute("data-src");
        imgItem.setAttribute("data-has-lazy-src", "true");
      }
      num = i + 1;
    }
  }
}, 1000);

onMounted(() => {
      window.onscroll = () => {
        if (getScrollTop() + getWindowHeight() > getDocumentHeight() - 100) {
          if (state.isLoadEnd === false && state.isLoading === false) {
            console.info("222");
            handleSearch();
          }
        }
      };
      document.addEventListener("scroll", lazyload);
      handleSearch();
});

template 中圖片的設(shè)置核心代碼data-has-lazy-src="false"胖腾,通過設(shè)置這個屬性值的truefalse標(biāo)記圖片是否已經(jīng)加載。data-src存放真正的圖片url,這個屬性名可以自定義咸作。

<img :data-src="article.cover" alt="文章封面" class="wrap-img img-blur-done" data-has-lazy-src="false" src="/src/assets/cover.jpg"/>

3.3.2 頁面按需加載

前后端分離情況下锨阿,首屏加載速度一直是一個熱點問題,通常的方案有很多種:

  1. 頁面懶加載
  2. 網(wǎng)絡(luò)傳輸壓縮
  3. 后端渲染

我們這里采用了前兩種方案记罚。

頁面懶加載主要在路由設(shè)置中墅诡,核心代碼如下:

{
    path: "/article/",
    name: "ArticleDetail",
    component: () =>
            import("../views/client/ArticleDetail.vue")
},

這里的組件不是通過文件頭部的import方式導(dǎo)入,而是在具體需要的地方通過匿名函數(shù)導(dǎo)入桐智,從而在需要展示該頁面的時候開始加載末早,從而第一次網(wǎng)絡(luò)請求的流量。

3.4 組件通信

在單頁應(yīng)用中说庭,組件通信是一個非常重要的功能然磷,也很復(fù)雜,有各種解決方案刊驴。我這里主要介紹一下在博客開發(fā)中用到的幾種方案姿搜。

3.4.1 父子通信

在博客的開發(fā)過程中,有很多地方都出現(xiàn)了父組件的數(shù)據(jù)傳遞給子組件中捆憎,也是單頁開發(fā)中最常見的場景舅柜,實現(xiàn)方案有兩種:

  • 屬性傳值

通過:user-id的方式,將父組件中的值賦值給屬性躲惰,在組件中致份,通過this.$props.userIduserId獲取該屬性值,這里的中劃線會被 Vue 自動轉(zhuǎn)換成駱駝命名的屬性础拨。

<UserDetail
      :user-id="state.userId"
      :visible="state.showDialog"
      @close="state.showDialog = false"
  />
  • slot傳值

通過在子組件中標(biāo)記slot和名稱氮块,占位,在父組件中引用子組件時太伊,在子組件的<></>中雇锡,通過<template v-slot:slot_name>的方式定義slot內(nèi)容,此時需要傳遞到子組件slot的數(shù)據(jù)僚焦,可以直接在父組件中直接賦值。比如下面代碼中的loading屬性等

<template v-slot:footer>
    <div class="dialog-footer">
    <el-button
             v-if="isLogin"
             :loading="state.btnLoading"
             type="primary"
             @click="handleOk"
             >
    登 錄
    </el-button>
    <el-button
             v-if="isRegister"
             :loading="state.btnLoading"
             type="primary"
             @click="handleOk"
             >注 冊
    </el-button>
  </div>
</template>

3.4.2 子父通信

  • 回調(diào)函數(shù)

子組件向父組件通信曙痘,有兩種芳悲,一種是通過父組件提供回調(diào)函數(shù),在屬性中傳入函數(shù)边坤,在子組件中調(diào)用回調(diào)函數(shù)名扛,完成子組件向組件通信。

在我們的博客網(wǎng)站中茧痒,沒有使用這種方式肮韧。

  • $emit事件觸發(fā)

父組件中,通過@close定義一個事件,并傳入一個定義好的函數(shù)handleCloseDrawer

<EditArticle
      :article-id="state.articleId"
      :visible="state.showDrawer"
      @close="handleCloseDrawer"
  />

子組件中弄企,定義emits

emits: ["close",],

在需要觸發(fā)事件的時候超燃,調(diào)用context.emit('close', param)或者this.$emit('close', param),這里的param是需要傳遞的數(shù)據(jù)拘领。

const saveArticle = async () => {
      try {
        state.loading = true
        if (state.catalogs.length) {
          state.article.catalog = state.catalogs[state.catalogs.length - 1]
        }
        if (props.articleId) {
          await remoteSaveArticle('put', state.article)
        } else {
          await remoteSaveArticle('post', state.article)
        }
        state.loading = false
        context.emit('close', true)
      } catch (e) {
        state.loading = false
      }
}

3.4.3 跨級及兄弟組件通信

這種場景下的組件通信比較復(fù)雜意乓,如果通過屬性通信,則需要層層傳遞约素,回調(diào)函數(shù)也需要層層回調(diào)届良,所以有了vuex,通過全局共享數(shù)據(jù)圣猎,實現(xiàn)跨組件通信士葫。

將需要共享的數(shù)據(jù)放入到store中,在需要的組件送悔,通過computed獲取慢显。

  1. store定義
export const store = createStore<State>({
    state() {
        return {
            navIndex: '1',
        }
    },
  mutations: {
        setNavIndex(state: object | any, navIndex: string) {
            state.navIndex = navIndex
        },
    },
}
  1. 需要獲取數(shù)據(jù)的組件中通過computed獲取
computed: {
        navIndex() {
      const store = useStore(StateKey);
      return store.state.navIndex;
    },
}
  1. 其他組件通過mutations中定義的方法改變store中的值
store.commit(SET_NAV_INDEX, "-1");

一旦這個值發(fā)生變化,通過computed定義的屬性放祟,就會同步發(fā)生變化鳍怨,從而實現(xiàn)組件間的通信,在博客網(wǎng)站中跪妥,我們的用戶信息也是這么使用的鞋喇。

  1. 需要獲取數(shù)據(jù)的組件也可以通過watch監(jiān)控
watch: {
    "$store.state.articleParams": {
      handler(val: any, oldVal: any) {
        this.state.params.tags = val.tags;
        this.state.params.catalog = val.catalog;
        this.state.articlesList = [];
        this.state.params.page = 1;
        this.handleSearch();
      },
    },
  },

這種是當(dāng)state發(fā)生變化時,可以獲取到變換前和變化后的值眉撵,通過handler做更多的處理侦香,完成該組件處理的邏輯。博客網(wǎng)站中我們在文章列表中點擊標(biāo)簽篩選文章列表纽疟,就是通過這種方式實現(xiàn)的

watch中可以監(jiān)控的對象有很多罐韩,可以參考 Vue 官網(wǎng)介紹:計算屬性和偵聽器 | Vue.js (vuejs.org)

watch: {
    '$props.visible': {
      handler(val, oldVal) {
        if (val != oldVal) {
          this.state.visible = val
        }
      }
    }
  },
watch: {
    $route: {
      handler(val: any, oldVal: any) {
        this.routeChange(val, oldVal);
      },
      immediate: true,
    },
  },
watch: {
    "$route.path": {
      handler(val, oldVal) {
        if (val !== oldVal && ["/admin/tag"].includes(val)) this.handleSearch();
      },
      deep: true,
    },
  },

3.5 CSS樣式穿透

在使用UI組件的時候,會遇到需要修改組件樣式的場景污朽,但是由于組件本身并沒有開發(fā)樣式屬性的定義能力散吵,此時就需要通過穿透機(jī)制實現(xiàn)。

//抽屜//去掉element-ui的drawer標(biāo)題選中狀態(tài)
:deep(:focus){
  outline: 0;

}

這里需要注意幾點蟆肆,一般我們在定義一個組件的樣式時矾睦,會設(shè)置<style lang="less" scoped>,這樣會導(dǎo)致該樣式穿透無效果炎功,因此還需要做如下調(diào)整

  1. 在需要調(diào)整組件樣式的地方枚冗,將組件包裹在一個div
  2. 給這個div定義一個class
  3. style中通過global穿透
.parent-div{
  :global(.el-card__body){
     margin: 0;
     padding: 0;
   }
}

也可以設(shè)置<style lang="less">,去掉scoped蛇损,然后使用:deep方式穿透赁温。我這里的語法是基于vue 3less坛怪,如果使用其他的css編譯器,請自行搜索驗證股囊。

3.6 刷新 404 問題

這個問題我們在部署篇已經(jīng)介紹袜匿,這里貼一下Nginx的配置,關(guān)鍵的是if判斷毁涉。

location / {
            root ~/blog/frontend/dist;
            index index.html index.htm;
            if (!-e $request_filename) {
                rewrite ^/(.*) /index.html last;
                break;
            }
        }

至此沉帮,這個博客搭建的內(nèi)容已經(jīng)結(jié)束了,恭喜你自己哦贫堰!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末穆壕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子其屏,更是在濱河造成了極大的恐慌喇勋,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偎行,死亡現(xiàn)場離奇詭異川背,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛤袒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門熄云,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妙真,你說我怎么就攤上這事缴允。” “怎么了珍德?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵练般,是天一觀的道長。 經(jīng)常有香客問我锈候,道長薄料,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任泵琳,我火速辦了婚禮摄职,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘获列。我一直安慰自己琳钉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布蛛倦。 她就那樣靜靜地躺著,像睡著了一般啦桌。 火紅的嫁衣襯著肌膚如雪溯壶。 梳的紋絲不亂的頭發(fā)上及皂,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音且改,去河邊找鬼验烧。 笑死,一個胖子當(dāng)著我的面吹牛又跛,可吹牛的內(nèi)容都是我干的碍拆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼慨蓝,長吁一口氣:“原來是場噩夢啊……” “哼感混!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起礼烈,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤弧满,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后此熬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庭呜,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年犀忱,在試婚紗的時候發(fā)現(xiàn)自己被綠了募谎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡阴汇,死狀恐怖数冬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鲫寄,我是刑警寧澤吉执,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站地来,受9級特大地震影響戳玫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜未斑,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一咕宿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜡秽,春花似錦府阀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寞蚌,卻和暖如春田巴,著一層夾襖步出監(jiān)牢的瞬間钠糊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工壹哺, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留抄伍,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓管宵,卻偏偏與公主長得像截珍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子箩朴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容