博客網(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 版本磕蛇,這里面有幾個考量:
- Django 版本對 Python 版本的支持程度
- 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)會驗證跨域cookie
和header
辑奈,因此我們需要做 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
自帶的方法authenticate
和login
完成賬號認(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)。
Django
的ORM
提供了兩個非常有用的字段類屬性auto_now_add
和auto_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_update
和perform_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 等蕉拢。
- 代理地址配置
有幾個點需要注意:
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' : '',
})
- BASE 地址配置
在配置服務(wù)器地址的時候,Vite 提供的是base
屬性蒸其。這里有兩種配置方式base: '/'
和base: './'
敏释,我們配置的是第一種,這里取決于我們使用的路由模式
- 如果使用的是
History
模式摸袁,則使用第一種 - 如果使用的是
Hash
模式钥顽,則使用第二種
base: '/',
- Vue 插件配置
需要 Vite
支持 Vue,通過引入Vue插件方式實現(xiàn)靠汁,核心代碼:
plugins: [
vue(),
],
3.1.2 package.json
如果要使用 Vite耳鸯,需要在這個文件里面配置scripts
。然后才能在命令行中使用yarn dev
和yarn 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.json
中build
命令财喳。
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è)置這個屬性值的true
和false
標(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 頁面按需加載
前后端分離情況下锨阿,首屏加載速度一直是一個熱點問題,通常的方案有很多種:
- 頁面懶加載
- 網(wǎng)絡(luò)傳輸壓縮
- 后端渲染
我們這里采用了前兩種方案记罚。
頁面懶加載主要在路由設(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.userId
和userId
獲取該屬性值,這里的中劃線會被 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
獲取慢显。
-
store
定義
export const store = createStore<State>({
state() {
return {
navIndex: '1',
}
},
mutations: {
setNavIndex(state: object | any, navIndex: string) {
state.navIndex = navIndex
},
},
}
- 需要獲取數(shù)據(jù)的組件中通過
computed
獲取
computed: {
navIndex() {
const store = useStore(StateKey);
return store.state.navIndex;
},
}
- 其他組件通過
mutations
中定義的方法改變store
中的值
store.commit(SET_NAV_INDEX, "-1");
一旦這個值發(fā)生變化,通過computed
定義的屬性放祟,就會同步發(fā)生變化鳍怨,從而實現(xiàn)組件間的通信,在博客網(wǎng)站中跪妥,我們的用戶信息也是這么使用的鞋喇。
- 需要獲取數(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)整
- 在需要調(diào)整組件樣式的地方枚冗,將組件包裹在一個
div
中 - 給這個div定義一個
class
- 在
style
中通過global
穿透
.parent-div{
:global(.el-card__body){
margin: 0;
padding: 0;
}
}
也可以設(shè)置<style lang="less">
,去掉scoped
蛇损,然后使用:deep
方式穿透赁温。我這里的語法是基于vue 3
和less
坛怪,如果使用其他的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é)束了,恭喜你自己哦贫堰!