一个使用 Vue3,Vite3,Typescript,Pinia,Unocss,Element-plus,pnpm的中后台模板,易上手,可配置,多功能,有点潮。
VSCode + Volar(Vetur 비활성화) + TypeScript Vue 플러그인(Volar).
.vue
가져오기에 대한 유형 지원 TypeScript는 기본적으로 .vue
가져오기에 대한 유형 정보를 처리할 수 없으므로 유형 확인을 위해 tsc
CLI를 vue-tsc
로 대체합니다. 편집기에서는 TypeScript 언어 서비스가 .vue
유형을 인식하도록 하려면 TypeScript Vue 플러그인(Volar)이 필요합니다.
독립형 TypeScript 플러그인이 충분히 빠르다고 느껴지지 않는다면 Volar는 더 성능이 좋은 Take Over Mode를 구현했습니다. 다음 단계에 따라 활성화할 수 있습니다.
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为例):
sudo 적절한 업데이트
sudo apt install apt-transport-https ca 인증서 컬 gnupg-agent 소프트웨어 속성-공통
컬 -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) 안정"
sudo 적절한 업데이트
sudo apt 설치 docker-ce docker-ce-cli 컨테이너d.io
sudo 적절한 업데이트
적절한 목록 -a docker-ce
5:19.03.9~3-0~ubuntu-focal
)sudo apt install docker-ce= docker-ce-cli= 컨테이너d.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的文件嵌套功能(fileNestin g.enabled), 因为根目录中大多为配置文件, 서로 다른 방식으로 사용되는 配置文件格式不统一 (不单)单是json,软件包为提供更好的定包能力,将配置文件格式定为ts、js等),且part软件包不支持更改配置文件目录,因此,根目录庞大不是本项目文件组织的问题,此问题更应该归由IDE解决.
推荐插件: antfu.file-nesting
项目期望保持一致的开发体验, 配置了一系列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 ./src/components
router, pinia, 所有将自动注册声明,响应式语法糖可以自动脱取.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中, 但其플러그인을 사용하여 빌드를 다운로드할 수 있습니다.
直接从官网复来的图标如下 :
< el-icon > < Search /> </ el-icon >
사용 방법에 따라 다음과 같은 방법을 사용하세요.
< el-icon > < i-ep-Search /> </ el-icon >
element-plus의 웹사이트: https://element-plus.org/zh-CN/comComponent/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 " />
본넷에서는 src/ ./src/assets/svg-icon
/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颜color不跟随父级而改变,这是由于本地图标中的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 ;
icon配置支持element-plus内置icon、iconify图标(需先在vite的unplugin中引入)以及本地图标,如下:
icon: "ep-icon-name"
(将icon-name
替换为你的图标name, ep为element-plus图标에서iconify의 图标集前缀)icon: "mdi-icon-name"
(将icon-name
替换为你的图标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
中声mingapi상사적数据类型 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 : [ ]
}
项目鼓励使用设计模式解决实际问题, 下면例举一些场景.
참고: 具体属于哪种设计模式也取决于看待问题的角titude, 比如组件,可以认为是工厂模式(传入简单的参数即可获取到一段模板),也可以认为是门face模式(组件内part会执行复杂的逻辑, 而使user无需关心它们).
@/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
windowsPowerShell
$env :CYPRESS_DOWNLOAD_MIRROR= " https://download.cypress.io/desktop "
리눅스、맥
CYPRESS_DOWNLOAD_MIRROR= " https://download.cypress.io/desktop "
设置完成后再次install即可
按光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 ,
} ,
} ) ,
开发时, 遇到项目依赖的库유 버그, 等不及库작성자修复, 可以自行修改打补丁
스트림 아래:
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 提交历史简洁
如果你更喜欢中文, 项目也对此작품好了翻译, 点这里浏览