一個使用Vue3,Vite3,Typescript,Pinia,Unocss,Element-plus,pnpm的中後台模板,易上手,可配置,多功能,有點潮。
VSCode + Volar(並停用 Vetur)+ TypeScript Vue 外掛程式 (Volar)。
.vue
導入的類型支持預設情況下,TypeScript 無法處理.vue
導入的類型信息,因此我們將tsc
CLI 替換為vue-tsc
進行類型檢查。在編輯器中,我們需要 TypeScript Vue 外掛程式 (Volar) 來讓 TypeScript 語言服務識別.vue
類型。
如果您覺得獨立的 TypeScript 外掛不夠快,Volar 還實現了效能更高的接管模式。您可以透過以下步驟啟用它:
Extensions: Show Built-in Extensions
TypeScript and JavaScript Language Features
,右鍵並選擇Disable (Workspace)
Developer: Reload Window
來重新載入 VSCode 視窗。 請參閱Vite配置參考。
pnpm i
pnpm dev
pnpm build
pnpm test:unit
pnpm test:e2e:dev
這將針對 Vite 開發伺服器運行端對端測試。它比生產構建快得多。
但仍建議在部署之前使用test:e2e
測試生產建置(例如在 CI 環境中):
pnpm build
pnpm test:e2e
pnpm lint
docker build -t clover-admin-vue:v0.0.0 -f docker/Dockerfile 。
docker run --name clover -p 80:80 -d clover-admin-vue:v0.0.0
http://localhost
如果您未安裝Docker,請執行下列步驟零安裝Docker(以Ubuntu20.04為例):
須藤apt更新
sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
捲曲-fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key 添加 -
sudo add-apt-repository“deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) 穩定”
須藤apt更新
sudo apt install docker-ce docker-ce-cli containerd.io
須藤apt更新
apt list -a docker-ce
5:19.03.9~3-0~ubuntu-focal
)sudo apt install docker-ce=docker-ce-cli=containerd.io
clover-admin
|-- .devcontainer // vscode远程开发配置
| |-- devcontainer.json
| |-- Dockerfile
|-- .husky // git commit提交钩子
|-- .vscode // vscode插件、设置、代码片段
| |-- clover.code-snippets // 代码片段
| |-- extensions.json // 插件
| |-- setting.json // 设置
|-- build // 构建的相关配置和插件
| |-- config
| |-- plugins // vite插件
| | |-- compress.ts // 代码压缩
| | |-- html.ts // html(注入变量、压缩代码)
| | |-- mock.ts // mock插件
| | |-- unplugin.ts // vue宏增强、自动导入、注册注册声明、Icon封装、UI组件
| | |-- visualizer.ts // 打包依赖分析
| |-- utils
|-- cypress // e2e测试框架
|-- doc // 文档相关
|-- docker // docker配置
| |-- .dockerignore
| |-- Dockerfile
| |-- nginx.conf
|-- mock // mock服务
|-- public
|-- src
| |-- assets // 静态资源
| | |-- animation // lottie动画资源
| | |-- svg-icon // 本地svg图标
| |-- components // 全局组件(自动导入)
| | |--business // 业务相关组件
| | |--common // 公共组件(常用组件)
| | |--custom // 自定义组件
| |-- composables // 组合式函数
| |-- config // 全局静态配置
| |-- constants // 全局常量
| |-- directives // vue指令
| |-- enum // TS枚举
| |-- hooks // 组合式的函数hooks(内部状态)
| |-- layout // 布局组件
| |-- plugins // 插件
| |-- router // vue路由
| | |-- guard
| | |-- helper
| | |-- modules
| | |-- routes
| |-- sdk // 三方sdk
| |-- service // 网络请求
| | |-- api
| | |-- request
| |-- stores // pinia状态管理
| |-- styles // 全局样式
| | |-- css
| | |-- less
| | |-- scss
| |-- typings // TS类型声明
| | |-- api.d.ts // 接口相关的类型声明
| | |-- auto-import.d.ts
| | |-- business.d.ts // 业务相关的类型声明
| | |-- components.d.ts
| | |-- env.d.ts // 项目级、vite相关配置
| | |-- expose.d.ts // vue组件导出类型
| | |-- global.d.ts // 全局类型
| | |-- package.d.ts // 包类型
| | |-- route.d.ts // 路由类型
| | |-- router.d.ts // 路由类型
| | |-- system.d.ts // 系统类型
| | |-- vue.d.ts // vue相关类型
| |-- utils // 工具函数
| | |-- auth
| | |-- common
| | |-- crypto
| | |-- filters
| | |-- router
| | |-- service
| | |-- storage
| |-- views // 页面
| |-- App.vue // vue文件入口
| |-- globalProperties.ts // vue全局变量
| |-- main.ts // 项目入口文件
|-- .editorconfig // 统一编辑器配置
|-- .env // 通用环境配置
|-- .env.config.ts // 请求环境的配置
|-- .env.development // 开发环境配置
|-- .env.production // 生产环境配置
|-- .eslintignore // 配置哪些文件忽略eslint检查
|-- .eslintrc-auto-import.json // auto-import插件的eslint生成文件
|-- .eslintrc.cjs // eslint配置文件
|-- .gitattributes // git配置
|-- .gitignore // 配置git记录时哪些文件忽略
|-- .npmignore // 可忽略
|-- .npmrc // npm配置
|-- .prettierrc.json // prettier代码格式化插件配置
|-- commitlint.config.js // commitlint提交规范插件配置
|-- commitlint.config.ts // commitlint提交规范插件配置
|-- cypress.config.ts // e2e测试框架配置
|-- index.html
|-- LICENSE
|-- package.json
|-- pnpm-lock.yaml
|-- README.md
|-- tsconfig.app.json
|-- tsconfig.config.json
|-- tsconfig.json // TS配置
|-- tsconfig.vitest.json
|-- uno.config.ts // unocss配置
|-- vite.config.ts // vite配置
|-- vitest.config.ts // vitest配置
如果你覺得專案根目錄過於龐大,你可以開啟VsCode的文件嵌套功能(fileNesting.enabled),因為根目錄中主要為配置文件,不同的架構提供的配置文件格式不統一(不單單是json) ,架構為提供更好的客製化能力,將設定檔格式定為ts、js等),且部分架構不支援更改設定檔目錄本,因此,根目錄龐大不是專案文件組織的問題,此問題更應該歸由IDE解決。
插件推薦: antfu.file-nesting
專案希望保持一致的開發體驗,配置了一系列 vscode 配置:
./.vscode/extensions.json
./.vscode/settings.json
./.vscode/clover.code-snippets
-snippets./.devcontainer
./.vscode/settings.json
專案採用自動匯入的方式來使用vue、vue-router、pinia等,並且結合自動註冊元件和vue3的響應式語法糖。 以前,我們使用vue可能是這樣的:
< template >
< div >{{ count }}</ div >
< HelloComponent />
</ template >
< script lang='ts' setup>
import HelloComponent from ' @/components '
import { ref , computed } from ' vue '
const count = ref ( 0 )
const doubled = computed (() => count . value * 2 )
</ script >
現在將使用更簡潔的方式:消耗再手動導入vue、vue-router、pinia,所有在./src/components
目錄下的所有組件都會自動註冊聲明,響應式語法糖可以自動脫下.value
等。
< template >
< div >{{ count }}</ div >
< HelloComponent />
</ template >
< script lang='ts' setup>
const count = $ref ( 0 )
const doubled = computed (() => count * 2 )
</ script >
請務必使用<script lang='ts' setup>
或<script lang='ts'>
,不要再寫純JS,當使用TS時,TypeScript可以和ESLint很好地配合提供型別檢查,但當混入純JS時當他們無法很好地工作時,目前前端社群還沒有好的解決方案。
./src/globalProperties.ts
中$filters
為例 // globalProperties.ts
export function setupGlobalProperties ( app : App ) {
installGlobalProperties ( filters , "$filters" ) ;
// ...
}
也需要在./src/typings/vue.d.ts
中為$filters定義類型
export { } ;
declare module "vue" {
/** 定义在vue实例上自定义的全局属性的类型 */
export interface ComponentCustomProperties {
$filters : typeof import ( "@/utils/filters" ) ;
}
}
// 模板中
< template >
< p >{{ $filters }}</ p >
</ template >
< script lang="ts" setup>
// setup中
const vm = getCurrentInstance () ! ;
const { $filters } = vm . appContext . config . globalProperties ;
</ script >
一些三方庫在使用時需要導入其css樣式文件,一般會把它們導入在main.ts中,這導致main.ts會出現註釋錯誤,因此本項目將其拆分,所有的資源文件統一放置在./src/plugins/assets.ts
進行導入。
vite的配置在vite.config.ts中,但是其plugins配置被拆分到了build目錄下,因為plugins往往會有很長的配置。
直接從官網複製來的圖示如下:
< el-icon > < Search /> </ el-icon >
由於專案採用了自動匯入圖示的方式,需要改為如下格式:
< el-icon > < i-ep-Search /> </ el-icon >
element-plus的圖示自動導入請見官方文件:https://element-plus.org/zh-CN/component/icon.html#%E8%87%AA%E5%8A%A8%E5%AF%BC% E5%85%A5
本專案支援原本https://iconify.design/的所有圖標,不需要簡單的配置即可。 element-plus的圖示也是https://iconify.design/中的圖示集合,現在在https://iconify.design/中挑選一個新的圖示集合:
./build/plugins/unplugin.ts
中寫入此圖示集合的導出名稱(mdi): Components ( {
dts : "src/typings/components.d.ts" ,
resolvers : [
IconsResolver ( {
// 自动注册图标组件 how to use: <i-ep-location />
enabledCollections : [ "ep" , "mdi" ] , // 'ep'是element图标集在https://iconify.design/ 里的集合名, 如果你引入或使用了其他图标集, 需要在此把其集合名写上
// ...
} ) ,
]
} ) ,
< i-mdi-github class =" text-22px " />
本專案也支援使用本機圖標,首先需要下載一個 svg 圖標並放在./src/assets/svg-icon
目錄下,然後在以下專案中使用:
// 假设我们有一个名为403.svg的图标在@/src/assets/svg-icon目录下
< i-local-403 />
// 也可以使用el-icon将其包裹
< el-icon :size =" 400 " > < i-local-403 /> </ el-icon >
本專案支援動態渲染圖標集圖標和本機圖標,請參閱如下範例:
<!-- iconify图标(动态渲染) -->
< svg-icon icon =" mdi-github " />
<!-- 本地图标(动态渲染) -->
< svg-icon local-icon =" link-icon " />
提示:如果在使用svg-icon元件動態渲染本機圖示後,出現圖示顏色不跟隨父級而改變,這是由於本機圖示中的fill屬性造成了覆蓋,刪除本機圖示中的fill屬性即可。
本專案使用unocss,需要寫css時,可以去官方找到對應的css類別名稱:https://uno.antfu.me/,或也可以參考tailwindcss和WindiCSS。 unocss的文檔:https://github.com/unocss/unocss 專案預設整合了unocss的presetUno屬性包,你可以繼續整合其他屬性包,這一切配置在uno.config.ts中。
// 一张圆形的图片
< img class =" w-32 h-32 rounded-full " src ="" />
// 等价于
< img style =" width: 32rem; height: 32rem; border-radius: 9999px; " src ="" />
專案同時也開啟了unocss的無值寫法(消耗再寫類別):
// 一张圆形的图片
< img w-32 h-32 rounded-full src ="" />
如果現有的css快捷寫法無法滿足您,您可以自行設定:
// uno.config.ts
export default defineConfig ( {
// ...
shortcuts : {
"wh-full" : "w-full h-full" ,
"flex-center" : "flex justify-center items-center"
}
} )
< div class =" wh-full " > </ div >
// 等价于
< div class =" w-full h-full " > </ div >
如果上述寫法仍無法滿足需求,也可以為unocss配置解析規則:
export default defineConfig ( {
// ...
rules : [
/ ^wh-(d+)px$ / ,
( [ , d ] ) => ( {
width : ` ${ d } px` ,
height : ` ${ d } px` ,
} ) ,
]
} )
< div class =" wh-20 " > </ div >
// 等价于
< div class =" w-20px h-20px " > </ div >
unocss也支援配置主題:
export default defineConfig ( {
// ...
// see: https://tailwindcss.com/docs/theme
theme : {
colors : {
dark : "#18181c" ,
} ,
}
} )
< div class =" bg-dark " > </ div >
// 等价于
< div class =" bg-#18181c " > </ div >
// 等价于普通css
< div style =" background-color: #18181c; " > </ div >
unocss暗黑模式:
export default defineConfig ( {
// 开启
presets : [ presetUno ( { dark : "class" } ) ] ,
} )
// bg-dark仅在暗黑模式下生效
< div class =" dark:bg-dark " > </ div >
./src/router/modules
中,以关于
頁面為例./src/router/modules
新建about.ts
import Layout from "@/layout/index.vue" ;
const about = [
{
name : "about" ,
path : "/about" ,
component : Layout , // 全局框架
redirect : "/about/index" , // 重定向
meta : { title : "关于" , hidden : true /** 在侧边菜单中隐藏这一级菜单 */ , order : 7 /** 此菜单在侧边菜单中的显示顺序 */ } ,
children : [
{
path : "index" ,
name : "about_index" , // 请遵循此书写规则"about_index", about即父级的name, index即当前的path
component : ( ) => import ( "@/views/about/index.vue" ) , /** 页面组件 */
meta : {
title : "关于" , /** 侧边菜单中显示的label */
icon : "ep-warning" , /** 侧边菜单中显示的icon */
} ,
} ,
] ,
} ,
] ;
export default about ;
icon配置支援element-plus內建icon、iconify圖標(需先在vite的unplugin中導入)以及本地圖標,如下:
icon: "ep-icon-name"
(將icon-name
替換為你的圖示名稱, ep為element-plus圖示在iconify的圖示集出口)icon: "mdi-icon-name"
(將icon-name
替換為你的圖示名,mdi為圖表集的匯出)icon: "local-icon-name"
(將icon-name
替換為@/assets/svg-icon
目錄下的檔案名稱)./src/views
中,在./src/views
中新目錄about,在about目錄中新建index.vue: < template >
< dark-mode-container class = " h-full " >
< el-card class = " h-full " >关于</ el-card >
</ dark-mode-container >
</ template >
< script lang="ts" setup></ script >
< style lang="less" scoped>
.el-card {
#ep .el-card-rounded ();
}
</ style >
本項目將網路請求統一放置在./src/service
目錄下。 如果您想參考範例,請參閱: ./src/views/function/request
。 要聲明一個api,需依照以下步驟:
./src/service/request/index.ts
中建立一個Request實例 import { createRequest } from "./request" ;
export const mockRequest = createRequest ( { baseURL : "/mock" } ) ;
./src/service/api
目錄下聲明api // /api/auth.ts
import { mockRequest } from "../request" ;
export function fetchLogin ( username : string , password : string ) {
return mockRequest . post < ApiAuth . Token > ( "/login" , { username , password } ) ;
}
./src/typings/api.d.ts
中聲明api相關的資料類型 declare namespace ApiAuth {
/** token */
interface Token {
token : string ;
}
}
< script lang="ts" setup>
import { fetchLogin } from " @/service " ;
const requestLogin = async () => {
const { data } = await fetchLogin ( " lalala " , " 123456 " );
if ( data ) {
console . log ( " data " , data ); // { token: "这是token" }
}
}
</ script >
./src/service/request
中已經統一執行了處理:./src/utils/service/error.ts
中的handleAxiosError./src/config/service.ts
中的ERROR_STATUS const response = {
code : 200 ,
message : "成功" ,
data : "SomeObjectOrOther"
}
如果傳回的資料結構與上述不符,可以在建立Request時進行配置:
export const mockRequest = createRequest ( { baseURL : "/mock" } , {
codeField : "statusCode" ,
dataField : "data" ,
msgField : "msg" ,
successCode : 0 ,
} ) ;
現在,當你使用mockRequest發起請求時,當遙控器回傳的statusCode不為0時,將彈出el-message錯誤提示,提示內容就是msg欄位的內容。
< script lang="ts" setup>
import { fetchLogin } from " @/service " ;
const requestLogin = async () => {
const { data } = await fetchLogin ( " lalala " , " 123456 " );
if ( data ) {
console . log ( " data " , data ); // { token: "这是token" }
}
}
</ script >
如你所見,你只能取得介面回傳的資料的內容,如果你想取得其他內容,例如回應頭,你可以在宣告api時進行設定:
export function fetchLogin ( username : string , password : string ) {
// entries可以配置所有的AxiosResponse的key
return mockRequest . post < ApiAuth . Token > ( "/login" , { username , password } , { entries : [ "data" , "headers" ] } ) ;
}
現在,您可以獲得響應頭了
< script lang="ts" setup>
import { fetchLogin } from " @/service " ;
const requestLogin = async () => {
const { data, headers } = await fetchLogin ( " lalala " , " 123456 " );
console . log ( " data " , data ); // { token: "这是token" }
console . log ( " headers " , headers );
}
</ script >
const message = {
code : 200 ,
message : "成功"
}
本項目會自動說明補充數據:
const message = {
code : 200 ,
message : "成功" ,
data : "success"
}
// .env.config.ts
const serviceEnv : ServiceEnv = {
// 项目默认启动在5574端口
dev : [
/**
* 代理:
* - 将http://127.0.0.1:5574/my-api/xx代理到http://127.0.0.1:5976/backend-api/xx
* - 也可以不写rewritten, 默认情况下将: http://127.0.0.1:5574/my-api/xx代理到http://127.0.0.1:5976/xx
*/
{
url : "http://127.0.0.1:5976" ,
urlPattern : "/my-api" ,
rewritten : "/backend-api"
}
] ,
test : [ ] ,
prod : [ ]
}
項目鼓勵使用設計模式解決實際問題,以下舉出一些例子。
注意:具體屬於哪種設計模式也看問題的角度,例如組件,可以認為是工廠模式(建立簡單的參數就可以獲取到一段模板),也可以認為是門面模式(組件內部會執行複雜)的邏輯,而用戶則消耗它們)。
@/utils/common/pattern.ts
// 策略模式会执行所有条件为true的策略
const actions = [
[
true ,
( ) => "执行"
] ,
[
false ,
( ) => "不执行"
]
]
exeStrategyActions ( actions ) ;
@utils/common/responsibilitiesChain.ts
// 示例1: 按照添加顺序, 进行链处理
const canBuyChain = new CustomChain ( ) ;
// 按照添加顺序组成责任链
canBuyChain . append (
new ResponsibilitiesChainNode (
( ) => false , // 条件
( ) => "一号" // 条件匹配时执行的动作
)
) ;
canBuyChain . append (
new ResponsibilitiesChainNode (
( ) => true ,
( ) => "二号"
)
) ;
canBuyChain . append (
new ResponsibilitiesChainNode (
( ) => true ,
( ) => "三号"
)
) ;
// 一旦执行成功就返回
const res = canBuyChain . execute ( ) ; // 二号
// 示例2: 灵活设置链处理顺序
const canBuyChain2 = new CustomChain ( ) ;
const chain1 = new ResponsibilitiesChainNode (
( ) => true ,
( ) => "哦耶1"
) ;
const chain2 = new ResponsibilitiesChainNode (
( ) => true ,
( ) => "哦耶2"
) ;
const chain3 = new ResponsibilitiesChainNode (
( ) => true ,
( ) => "哦耶3" // 在最后一个chain设置的成功函数的返回值会返回给链外
) ;
chain1 . setNext ( chain2 ) ;
chain2 . setNext ( chain3 ) ;
canBuyChain2 . append ( chain1 ) ;
// 执行到底返回最后结果, 遇到失败则结束
const res2 = canBuyChain2 . executeAll ( ) ; // 哦耶3
// 执行到底返回全部结果, 遇到失败则结束
const res22 = canBuyChain2 . executeAllSettled ( ) ; // ["哦耶1", "哦耶2", "哦耶3"]
// 示例3: 支持自定义chainNode类
class MyChainNode1 extends ResponsibilitiesChain {
public canHandle ( ) : boolean {
return false ;
}
public doHandle ( ) {
return "一号节点执行成功" ;
}
public errHandle ( ) {
return ;
}
}
class MyChainNode2 extends ResponsibilitiesChain {
public canHandle ( ) : boolean {
return true ;
}
public doHandle ( ) {
return "二号节点执行成功" ;
}
public errHandle ( ) {
return ;
}
}
const myChainNode1 = new MyChainNode1 ( ) ;
const myChainNode2 = new MyChainNode2 ( ) ;
const myChain = new CustomChain ( ) ;
myChain . append ( myChainNode1 ) ;
myChain . append ( myChainNode2 ) ;
const myRes = myChain . execute ( ) ;
@/views/component/complex-form/components/ResponsibilityValidatorForm.vue
,這裡提供了一個小遊戲來解釋責任鏈的使用項目使用了消防三方庫,以下推出了其文檔
參考自:https://www.lfhacks.com/tech/cypress-download-failure/
網路不暢,且此資源增加,官方給出的解決方式:將CYPRESS_DOWNLOAD_MIRROR常數設定為https://download.cypress.io/desktop,但不同網路設定常數的方式不同,以下幾種:
視窗命令列
set CYPRESS_DOWNLOAD_MIRROR=https://download.cypress.io/desktop
Windows PowerShell
$env :CYPRESS_DOWNLOAD_MIRROR= " https://download.cypress.io/desktop "
Linux、Mac
CYPRESS_DOWNLOAD_MIRROR= " https://download.cypress.io/desktop "
設定完成後再次安裝即可
根據 AutoImport 外掛程式介紹,需要在第一個執行專案時設定eslintrc: {enabled: true}
產生 eslint 文件,後續可改為 false。
本項目AutoImport位置在./build/plugins/unplugin.ts
AutoImport ( {
// ...
eslintrc : {
enabled : false , // 自动生成全局声明文件, 不需要eslint检查(在.eslintrc-auto-import.json生成成功之后就可以改为false)
filepath : "./.eslintrc-auto-import.json" ,
globalsPropValue : true ,
} ,
} ) ,
開發時,遇到專案依賴的函式庫有bug,等不及函式庫作者修復,可以自行修改打補丁
流程如下:
pnpm 修補 [email protected]
現在您可以編輯以下資料夾:C:UsersADMINI~1AppDataLocalTemp482a1b2c5aaad6b4abb4d39bab8ef39cuser
pnpm 補丁提交 C:UsersADMINI~1AppDataLocalTemp482a1b2c5aaad6b4abb4d39bab8ef39cuser
這會在專案目錄下產生patches/[email protected]
文件,並在package.json中自動更新如下配置:
{
"pnpm" : {
"patchedDependencies" : {
"[email protected]" : " patches/[email protected] "
}
}
}
好的提交規範可以清楚地了解開發者嘗試做什麼,並有助於自動產生更改日誌。
這是使用推薦項目內建的pnpm cz
指令進行git提交,一個易受影響的git提交介面。
feat(components): [ HoverLink ] 增加悬浮链接组件(使用命令式语气)
主体行和主体内容之间用空白行隔开(可以有预期时间)
通过一行或多行描述你的修改信息(大批量更改务必描述修改详情)
每一行的首字母大写
且每一行的总字符数限制在72个以内最优, 超过了将不易于他人理解
- 你也可以通过添加子项列表符号来为内容布局
主題標題的格式是:
[ type ] (scope 域): [ messages ]
通用型
保持 git 提交歷史簡潔
如果你比較喜歡中文,專案也就此作好了翻譯,點這裡瀏覽