一个使用 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 Language على دراية بأنواع .vue
.
إذا لم يكن المكون الإضافي المستقل TypeScript سريعًا بما يكفي لك ، فقد قام Volar أيضًا بتطبيق وضع الاستيلاء على الأداء. يمكنك تمكينه بالخطوات التالية:
Extensions: Show Built-in Extensions
من لوحة أوامر VSCODETypeScript and JavaScript Language Features
، انقر بزر الماوس الأيمن وحدد Disable (Workspace)
Developer: Reload Window
من لوحة الأوامر. انظر مرجع تكوين 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 为例):
تحديث Sudo APT
sudo apt تثبيت apt-transport-https ca curl curl gnupg-agent properties-common
curl -fssl https://download.docker.com/linux/ubuntu/gpg | Sudo Apt -Key Add -
sudo add-apt-repository "deb [arch = amd64] https://download.docker.com/linux/ubuntu $ (lsb_release -cs) مستقر"
تحديث Sudo APT
sudo apt تثبيت docker-ce docker-ce-cli containerd.io
تحديث Sudo APT
قائمة apt -a docker -ce
5:19.03.9~3-0~ubuntu-focal
)sudo apt تثبيت 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 lesting
项目期望保持一致的开发体验 ، 配置了一系列 vscode 配置:
./.vscode/extensions.json
./.vscode/settings.json
./.vscode/clover.code-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 很好地配合提供类型检查 ،时他们无法很好地工作 ، 目前前端社区没有好的解决方案。
./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٪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 组件动态渲染本地图标后 ، 出现 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 的无值写法 (无需再写 class):
// 一张圆形的图片
< 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 ;
أيقونة 配置支持 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
中新建目录 حول ، 在 on 目录中新建 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"
}
如果后端返回的数据结构与上述不符 ، 可以在创建 اطلب 时进行配置:
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 ، 但不同操作系统设置常量的方式不同 ، 以下列出几种:
Windows 命令行
set CYPRESS_DOWNLOAD_MIRROR=https://download.cypress.io/desktop
Windowspowershell
$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: user admini ~ 1 appdata local temp 482a1b2c5aaad6b4abb4d39bab8ef39c user
pnpm patch-commit c: user admini ~ 1 appdata local temp 482a1b2c5aaad6b4abb4d39bab8ef39c user
这会在项目目录下生成patches/[email protected]
文件 ، 且在 package.json 中自动更新如下配置:
{
"pnpm" : {
"patchedDependencies" : {
"[email protected]" : " patches/[email protected] "
}
}
}
好的提交规范可以清晰地了解到开发者试图做什么 ، 并且有助于自动生成更改日志.
推荐使用项目内置的pnpm cz
命令进行 git 提交 ، 这是一个交互式的 git 提交界面.
feat(components): [ HoverLink ] 增加悬浮链接组件(使用命令式语气)
主体行和主体内容之间用空白行隔开(可以有预期时间)
通过一行或多行描述你的修改信息(大批量更改务必描述修改详情)
每一行的首字母大写
且每一行的总字符数限制在72个以内最优, 超过了将不易于他人理解
- 你也可以通过添加子项列表符号来为内容布局
: :
[ type ] (scope 域): [ messages ]
通用惯例
保持 git 提交历史简洁
如果你更喜欢中文 ، 项目也对此作好了翻译 ، 点这里浏览