owned this note
owned this note
Published
Linked with GitHub
---
tags: 工作上的點點滴滴
---
# 前端初期架構規劃
> 這邊想來紀錄一下自己第一次規劃架構的經驗當作一個里程碑。在專案規劃的前期有很多需要考量的地方,但礙於自己沒有真的規劃過一個大型專案,所以只能先參考大神們的文章來思考哪些東西是有加入的必要。
### 專案構思
思路流程:
1. 是否需要 SEO
2. 打包效率
3. 編碼檢查工具、自動編排工具
4. CSS 框架與 UI 框架的選擇
5. 有沒有需要將 Sass 模組化
### 專案所需功能
- i18 多國語系切換 => [後續優化](https://hackmd.io/kC-5K27NSrWTu00SY427rQ)
- 提升打包效率 - Vite
- coding style 檢查工具(<span class="red">Eslint</span>)、自動編排工具(<span class="red">prettier</span>)
- 路由控制器
- Vuex 作為狀態管理及應用程序中所有組件的集中存儲區 => 這邊後續優化更改成使用 Pinia 套件,因為 Vuex 在使用變數內容時要打的 code 太多了(<span class="red">store.state.isLoading</span>, 很長一串XD)
- API 路徑 module
- UI 框架 - <span class="red">Element^+^</span>
- CSS 框架 - <span class="red">Tailwind</span>
- 單元測試套件 - Jest ([踩坑心得分享](https://hackmd.io/uHyz0jaMTXaJT19LwOBrxw))
- Sass 模組化
- mock API
- 全域的錯誤處理([middleware 異常狀態處理](https://hackmd.io/UCtXtiRfQJOfjA0Sx1K6TA))
### Vite + Vue3
[Vite 官方中文文檔](https://cn.vitejs.dev/guide/#scaffolding-your-first-vite-project)
安裝指令:
```
npm create vite@latest my-vue-app -- --template vue
```
### eslint + prettier 配置
[一篇文章搞定vite+vue3+eslint+prettier](https://juejin.cn/post/7020653715363217445)
[eslint 和 prettier 配置文件不能是 .js 文件,必须是.cjs 文件怎么回事 ?](https://segmentfault.com/q/1010000042298464)
:::warning
上述的配置問題因為 vite 新版在 package.json 裡面會配置 type: "module", 於是 .js 被默認為使用了 ES module 規範,如果自動生成的配置文件使用了 CommonJS,就會報錯。.cjs 的檔案會告诉 node.js 它使用了 CommonJS 規範,所以就不會報錯。
:::
:::danger
解決方法:
1. 檔案名稱更改為 *.cjs
2. 必須使用 module.exports 來匯出設定檔
3. 必須至擴充設置那邊將檔案名稱一併調整為 *.cjs, 不然會一直報「找不到檔案」的錯誤
:::
```javascript=
// .prettierrc 檔案
{
/*
* 這邊將 semi 改為 true,eslint 會跳 warning。
* 後續將 Vscode 的 Vetur 關掉之後,改用 Volar 擴充元件就沒問題
*/
"semi": true,
"singleQuote": true,
"endOfLine": "auto",
"printWidth": 100
}
```
### Router
[使用Vite构建Vue3项目,对路由Vue Router 4.x的设置](https://blog.csdn.net/xjtarzan/article/details/119736309)
:::warning
在這篇文章中,讓我覺得有趣的地方是**步驟4的 alias 設置**,讓我了解了以往我們為什麼用 @ 這個符號,它會直接編譯為 src 資料夾的路徑。
原來是因為有特別針對這個符號去做一些路徑的處理
:::
[Router 集成設置參考](https://www.cnblogs.com/liuqin-always/p/14690044.html)
透過這篇文章了解到如何將路由模組化,範例如下:
:::spoiler 路由模組化代碼
```javascript=
// modules/home.js
export default {
path: '/',
name: 'index',
title: '首頁',
component: () => import('@/views/HomeView.vue'),
};
```
```javascript=
// routes.js
import home from './modules/home';
const routes = [home];
export default routes;
```
```javascript=
// index.js
import { createRouter, createWebHashHistory } from 'vue-router';
import routes from './routes';
const router = createRouter({
history: createWebHashHistory(),
routes,
});
// 這邊的想法是:
// 1. 有 token 的情況下,只要路由想要到 login 都會被導回首頁。
// 2. 沒有 token 的情況下,只要不是 login 路由,都會自動導回到 login
router.beforeEach((to, from) => {
const access = Cookies.get('token');
if (access && to.path === '/login') {
return { name: 'HomeView' };
} else if (!access && to.path !== '/login') {
return { name: 'login' };
}
});
export default router;
```
:::
#### 步驟流程:
1. 在 modules 裡面定義各頁面路由
2. 接著透過 routes.js 導入
3. 最後由 index.js 去建立一個 Router 實體,並把它匯出
4. 方便於 main.js 去引用。
![](https://i.imgur.com/L9rrm7b.png)
[路由的後續優化](https://hackmd.io/XGnLB8CfTwi8UAMRvJOPsg)
### Tailwind CSS
[Install Tailwind CSS with Vue 3 and Vite](https://tailwindcss.com/docs/guides/vite)
### Vuex
[Vue3 的資料狀態管理,provide / inject、vuex、props](https://penueling.com/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/vue3-%E7%9A%84%E8%B3%87%E6%96%99%E7%8B%80%E6%85%8B%E7%AE%A1%E7%90%86%EF%BC%8Cprovide-inject%E3%80%81vuex/) => 參考 Vuex 集成設置
:::spoiler Vuex 相關代碼
```javascript=
// modules/auth.js
export const state = {
user: JSON.parse(sessionStorage.getItem('user')),
password: '123',
};
export const actions = {};
export const mutations = {
setUser(state, payload) {
sessionStorage.setItem('user', JSON.stringify(payload));
state.user = payload;
},
};
export const getters = {
isAuthenticated: (state) => !!state.user || !!sessionStorage.getItem('user'),
};
export default {
state,
actions,
mutations,
getters,
namespaced: true,
};
```
```javascript=
import { createStore } from 'vuex';
import auth from './modules/auth';
// Create a new store instance.
export default createStore({
modules: { auth },
});
```
:::
![](https://i.imgur.com/9CLprIF.png)
### axios 集成設置
- [從 0 開始手把手帶你搭建一套規範的 Vue3 項目工程環境](https://www.readfog.com/a/1635089631515086848)
==這裡只有先寫一些註解讓大家清楚每個區塊所負責的功能是什麼,這樣未來如果要擴充或是新增功能的話,比較知道在哪個地方添加。==
```javascript=
// api/axios.js
import Axios from 'axios';
import { ElMessage, ElLoading } from 'element-plus';
const baseURL = 'https://desolate-gorge-38580.herokuapp.com';
const service = Axios.create({
baseURL,
timeout: 20000, // 請求超時 20s
});
service.interceptors.request.use(
config => {
// 發送請求前會做的事情
// 這邊目前想到2件事可以先做:
// 1. 加入全域的 loading 效果在每次發請求前
// 2. 將 token 帶入 config.headers.Authorization 裏面
const loadingInstance = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const token = Cookies.get('token');
if (token) {
config.headers.common.Authorization = `Bearer ${token}`;
loadingInstance.close();
}
return config;
},
(error) => {
// 請求錯誤要做的事情
return Promise.reject(error);
}
);
// 後置攔截器(獲取到響應時的攔截)
service.interceptors.response.use(
// 狀態碼為 2xx 時觸發的 function
(response) => {
return response;
},
// 狀態碼非 2xx 時觸發的 function
(error) => {
if (error.response && error.response.data) {
const code = error.response.status;
const msg = error.response.data.message;
ElMessage.error(`Code: ${code}, Message: ${msg}`);
console.error(`[Axios Error]`, error.response);
} else {
ElMessage.error(`${error}`);
}
return Promise.reject(error);
}
);
export default service;
```
- [Vue3.x中集成axios的方法](https://shawnzhou.world/2021/07/28/vue3-axios/)
```javascript=
// api/home.js
import service from './axios';
export function successTest() {
return service({
url: '/posts',
method: 'get',
});
}
export function errorTest() {
return service({
url: '/rooms',
method: 'get',
});
}
```
```javascript=
// 這邊在 Vite 的文件配置失效,所以後續在各個 .vue 檔案引入 api 時
// 就直接沿用前面 Vite.config 裡面所設置的 alias
proxy: {
"/desktop": {
target: "http://你的ip:你的端口/",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
```
```javascript=
// views/HomeView.vue
import { successTest, errorTest } from '@/api/home';
```
![](https://i.imgur.com/YY3DPSp.png)
### i18n
[i18n什麼的交給前端來處理吧](https://ithelp.ithome.com.tw/articles/10262689)
[將 i18n Global](https://blog.flycode.com/step-by-step-how-to-create-a-vue-multi-language-app-with-vue-i18n)
```javascript=
export default createI18n({
legacy: false,
globalInjection: true,
locale: process.env.VUE_APP_I18N_LOCALE || "en",
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en",
messages: loadLocaleMessages(),
});
```
> 需要添加 globalInjection: true,然後系統會自動將其全域化,透過 $t 就可以獲得內容。
```htmlmixed=
<!-- template 獲取 i18n value 方式 -->
<template>
<h1>{{ $t('title') }}</h1>
<p>{{ $t('description') }}</p>
</template>
```
==這邊紀錄一下,後續可能會需要處理關於切換語系後,畫面需要 reload 的部分==
### 最終資料夾內容呈現樣貌
![](https://i.imgur.com/fuS7z6Q.png)
<style>
.red{
color: red
}
</style>