Il s'agit de Vue3, Vite3, Typescript, Pinia, Unocss, Element-plus, pnpm, 有点潮.
VSCode + Volar (et désactiver Vetur) + TypeScript Vue Plugin (Volar).
.vue
dans TS TypeScript ne peut pas gérer les informations de type pour les importations .vue
par défaut, nous remplaçons donc la CLI tsc
par vue-tsc
pour la vérification de type. Dans les éditeurs, nous avons besoin du plug-in TypeScript Vue (Volar) pour que le service de langage TypeScript soit conscient des types .vue
.
Si le plugin TypeScript autonome ne vous semble pas assez rapide, Volar a également implémenté un mode Take Over plus performant. Vous pouvez l'activer en procédant comme suit :
Extensions: Show Built-in Extensions
à partir de la palette de commandes de VSCodeTypeScript and JavaScript Language Features
, faites un clic droit et sélectionnez Disable (Workspace)
Developer: Reload Window
à partir de la palette de commandes. Voir Référence de configuration Vite.
pnpm i
pnpm dev
pnpm build
pnpm test:unit
pnpm test:e2e:dev
Cela exécute les tests de bout en bout sur le serveur de développement Vite. C’est beaucoup plus rapide que la mise en production.
Mais il est toujours recommandé de tester la version de production avec test:e2e
avant le déploiement (par exemple dans les environnements 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
Pour Docker, et pour Docker (pour Ubuntu20.04) :
sudo apt mise à jour
sudo apt install apt-transport-https ca-certificates curl gnupg-agent logiciel-propriétés-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key ajouter -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt mise à jour
sudo apt installer docker-ce docker-ce-cli conteneurd.io
sudo apt mise à jour
liste apt -un docker-ce
5:19.03.9~3-0~ubuntu-focal
)sudo apt install docker-ce= docker-ce-cli= containersd.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),且部分软件包不支持更改配置文件目录,因此,根目录庞大不是本项目文件组织的问题,此问题更应该归由IDE解决。
Version : antfu.file-nesting
Il s'agit d'un exemple de vscode :
./.vscode/extensions.json
./.vscode/settings.json
./.vscode/clover.code-snippets
./.devcontainer
./.vscode/settings.json
Il s'agit d'une vue, d'une vue-router, d'une pinia, etc. Dans ce cas, la vue est la suivante:
< 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
目录下的组件都将自动注册声明, Il s'agit d'un exemple de .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 et ESLint ont mis en place des solutions de type JS,目前前端社区没有好的解决方案。
./src/globalProperties.ts
$filters
为例 // globalProperties.ts
export function setupGlobalProperties ( app : App ) {
installGlobalProperties ( filters , "$filters" ) ;
// ...
}
Il s'agit de $filters./src/typings ./src/typings/vue.d.ts
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 >
Il s'agit d'un fichier css, d'un fichier main.ts,这导致main.ts会越来越臃肿, 因此本项目将其拆分, ./src/plugins/assets.ts
système de gestion des ressources en ligne./src/plugins/assets.ts进行导入.
vite的配置在vite.config.ts中, 但其plugins往往会有很长的配置.
直接从官网复制来的图标如下:
< el-icon > < Search /> </ el-icon >
因为项目使用了自动导入图标的方式, 所以需要改为如下格式:
< el-icon > < i-ep-Search /> </ el-icon >
element-plus E5%85%A5
本项目支持源自https://iconify.design/的所有图标, 只需简单的配置即可. element-plus
./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 " />
Astuce : 如果在使用svg-icon组件动态渲染本地图标后, 出现icon颜色不跟随父级而改变, Il s'agit d'un système de remplissage et d'un système de remplissage.
本项目使用unocss, 当需要编写css时, 可去官方查找相应的css类名 : https://uno.antfu.me/, Il s'agit de tailwindcss et WindiCSS. unocss的文档 : https://github.com/unocss/unocss Il s'agit d'un fichier 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 ="" />
Il existe une version css basée sur les éléments suivants :
// 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 >
Il s'agit d'un exemple d'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 dit:
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 signifie :
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"
(et icon-name
name de l'icône, ep de l'element-plus iconify)icon: "mdi-icon-name"
(将icon-name
替换为你的图标名, mdi为图表集的前缀)icon: "local-icon-name"
(将icon-name
替换为@/assets/svg-icon
目录下的文件名)./src/views
./src/views
ainsi queabout, etindex.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 >
Il s'agit d'un système de gestion des ressources en ./src/service
. Dans ce cas, il s'agit de : ./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
pour 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
pour handleAxiosError./src/config/service.ts
中的ERROR_STATUS const response = {
code : 200 ,
message : "成功" ,
data : "SomeObjectOrOther"
}
Il s'agit d'une demande de réponse à une demande de requête :
export const mockRequest = createRequest ( { baseURL : "/mock" } , {
codeField : "statusCode" ,
dataField : "data" ,
msgField : "msg" ,
successCode : 0 ,
} ) ;
Il s'agit d'un message mockRequest, d'un message 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 >
如你所见, 你只能拿到接口返回的data的内容, 如果你想获取其他内容, 比如响应头,你可以在声明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 : "成功"
}
Données relatives aux données :
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 : [ ]
}
Il s'agit d'une question de temps.
Nom : 具体属于哪种设计模式也取决于看待问题的角度, 比如组件,可以认为是工厂模式(传入简单的参数即可获取到一段模板),也可以认为是门面模式(组件内部会执行复杂的逻辑, 而使用者无需关心它们).
@/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
.项目使用了诸多三方库, 以下列出其文档
Lire : https://www.lfhacks.com/tech/cypress-download-failure/
网络不畅, 且此资源较大, 官方给出的解决方式 : Voici la version CYPRESS_DOWNLOAD_MIRRORhttps://download.cypress.io/desktop, qui est la suivante :
Windows et Windows
set CYPRESS_DOWNLOAD_MIRROR=https://download.cypress.io/desktop
fenêtresPowerShell
$env :CYPRESS_DOWNLOAD_MIRROR= " https://download.cypress.io/desktop "
Linux、Mac
CYPRESS_DOWNLOAD_MIRROR= " https://download.cypress.io/desktop "
设置完成后再次install即可
Il y a AutoImport qui utilise eslintrc: {enabled: true}
pour Eslint, qui est false.
Utilisez AutoImport pour ./build/plugins/unplugin.ts
AutoImport ( {
// ...
eslintrc : {
enabled : false , // 自动生成全局声明文件, 不需要eslint检查(在.eslintrc-auto-import.json生成成功之后就可以改为false)
filepath : "./.eslintrc-auto-import.json" ,
globalsPropValue : true ,
} ,
} ) ,
Il s'agit d'un bug, d'un bug, d'un bug, d'un bug.
流程如下:
pnpm patch [email protected]
Vous pouvez maintenant modifier le dossier suivant : C:UsersADMINI~1AppDataLocalTemp482a1b2c5aaad6b4abb4d39bab8ef39cuser
pnpm patch-commit C:UsersADMINI~1AppDataLocalTemp482a1b2c5aaad6b4abb4d39bab8ef39cuser
Il s'agit de patches/[email protected]
, et package.json de patches/[email protected] est utilisé :
{
"pnpm" : {
"patchedDependencies" : {
"[email protected]" : " patches/[email protected] "
}
}
}
Il s'agit d'un cas où il y a un certain nombre de personnes.
Il s'agit de pnpm cz
et de git提交, et de git提交界面.
feat(components): [ HoverLink ] 增加悬浮链接组件(使用命令式语气)
主体行和主体内容之间用空白行隔开(可以有预期时间)
通过一行或多行描述你的修改信息(大批量更改务必描述修改详情)
每一行的首字母大写
且每一行的总字符数限制在72个以内最优, 超过了将不易于他人理解
- 你也可以通过添加子项列表符号来为内容布局
主题标题的格式是:
[ type ] (scope 域): [ messages ]
通用惯例
保持 git 提交历史简洁
如果你更喜欢中文, 项目也对此作好了翻译, 点这里浏览