跳到主要内容

· 阅读需 6 分钟
JaguarJack

为什么需要组合开发

夏至冬来的一次久违的分享!从之前 catchadmin 开发情况来看,使用该项目的多是后端人员,极大可能还是前后端都有参与,所以将两者组合到一起会很方便开发者。当然如果需要单独开发也没有问题,将该篇 blog 的操作逆向看也行。而且 LaravelVue 集合也是相当好,生态也很丰富,反观 thinkphp, 简直一塌糊涂,每每小版本升级都导致很多问题。这种事情在 v3 下会很少发生了。

迁移

首先 v3 也是有前端的,但是 v3 前端的仓库将会是单独的纯前端仓库,不会再跟后台耦合。所以在开发前第一步就是要将前端迁移到 Laravel。当然这篇 blog 只是记录过程,实际上项目上已经完全迁移好了

项目文件

前端项目一般都是根目录存有依赖,config 配置等文件,src 则是开发目录,首先是将根目录的依赖 config 复制到 Laravel 项目根目录下

  • package.json
  • .env
  • .env.production
  • postcss.config.js
  • tailwind.config.js
  • tsconfig.json
  • tsconfig.node.json
  • vite.config.js

等等,上面列出主要文件

开发目录迁移

Laravel 一般规定前端开发的文件都存储在 resources 目录,所以先在该目录下创建 admin 目录, 然后将前端的 src 目前复制到 Laravel 的 resources/admin 目录。迁移进去之后,先不要着急运行 yarn dev,首先安装 Laravel Vite 开发插件

yarn add -D laravel-vite-plugin

在 resources/views 目录下建立 admin.blade.php 文件,将前端的 index.html 内容复制到该文件中,目前 v3 内容如下

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Laravel</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100%;
}
</style>
<!-- 这里引入前端的入口文件 -->
@vite('resources/admin/app.ts')
</head>
<body id="app">
</body>
</html>

配置 vite.config.js

这个配置是最重要的一环,首先引入

import laravel from 'laravel-vite-plugin'

return {
plugins: [
laravel({
input: ['resources/admin/app.ts'],
// 监控目录刷新
refresh: ['resources/admin'],
}),
// 前端都是使用 @ 作为项目根目录,但是这里不能这么使用
// 即使使用 @ => resources/admin 也不会生效
// 因为 laravel-vite-plugin 在插件写死 @ => resources/js
// 虽然可以覆盖,但由于在写下 blog 的时候,vite 3.2 的配置和 3.0 的配置有所区别,导致无法覆盖
// 所以这里使用 /admin 前缀表示
// @ 留给自主开发应用
alias({
entries: [
{
find: '/admin',
replacement: resolve(rootPath, 'resources/admin'),
},
],
}),
// 自动生成全局组件
// 不需要在 import 组件
Components({
dirs: ['resources/admin/components/', 'resources/admin/layout/'],

extensions: ['vue'],

deep: true,

dts: true,

include: [/\.vue$/, /\.vue\?vue/],

exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
// resolvers: [ ElementPlusResolver({ importStyle: 'sass'}) ]
}),
]
}

目前就需要这么多的配置,后期改变继续添加

配置 tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
mode: 'jit',
darkMode: 'class',
// 主要
content: ['./resources/admin/**/*.{vue,js,ts,jsx.tsx}'],
theme: {
extend: {
transitionProperty: {
width: 'width',
spacing: 'margin, padding'
},
colors: {
'regal-dark': '#283046'
}
}
},
plugins: []
}

配置 ts

{
"compilerOptions": {
"baseUrl": "./",
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"allowJs": true,
"lib": [
"esnext",
"dom"
],
"skipLibCheck": true,
// "types": ["element-plus/golbal"],
"paths": {
"@/*": [
"resources/js/*"
],
// 主要
"/admin/*": [
"resources/admin/*"
]
}
},
"include": [
// 主要配置
"resources/admin/js/**/*.ts",
"resources/admin/js/**/*.d.ts",
"resources/admin/js/**/*.tsx",
"resources/admin/js/**/*.vue",
"resources/admin/js/**/*.js",
"resources/admin/js/env.d.ts"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

env

因为 Laravel 目录也是有 .env 文件,所以将 vue 的项目 .envLaravel 的 .env 合并,主要就是添加一个配置项

VITE_BASE_URL=http://127.0.0.1:9091/api/

代码格式化

PHPStorm 安装 prettier 插件,然后在 preferences 中选择 Languages & Frameworks, 选中 JavascriptPrettier, 会看到 prettier package, 因为项目已经安装 prettier 插件,所以选择 node_modules/prettier,再勾选 On Save 选项。

另外项目的根目录下有一个 .prettierrc 文件,内容如下

{
// 句末加分号
"semi": false,
"printWidth": 200,
"tabWidth": 2,
"useTabs": false,
// 用单引号
"singleQuote": true,
// 箭头函数参数括号 (x) => {} always
// x => {} avoid
"arrowParens": "avoid",
"trailingComma": "all",
// 对象&数组是否追加空格
"bracketSpacing": true
}

最后

找到 routes/web.php 文件,添加路由

Route::get('/', function () {
return view('admin');
});

使用

 yarn dev

启动前端项目

使用

php artisan serve

启动后端项目,通过后端的链接访问项目,大功完成✅

· 阅读需 3 分钟
JaguarJack

为什么使用 mock

在日常开发中,后端接口和前端开发的进度一般会不一致,有时候前端因为一个接口没有对接完成,就会卡住,无法进行下去,所以这个时候需要进行数据 mock,使得业务能正常开发下去,当然首要的前提是,需要前后端对接口的数据定义要统一。

安装

yarn add mockjs

yarn add @types/mockjs -D

yarn add vite-plugin-mock -D

使用

找到 vite.config.js

import { viteMockServe } from 'vite-plugin-mock'


// 然后在 plugins 中添加 viteMockServe
return {
plugins: [
viteMockServe({
// mock 数据的文件夹
mockPath: './src/mock',
// 根据实际情况,是否需要开启 mock
localEnabled: command === 'serve',
watchFiles: true // 监视文件夹中的文件更改。 并实时同步到请求结果
}),
}

创建 mock 数据

mkdir ./src/mock

touch login.ts

添加一个 loginmock 数据

import { MockMethod } from 'vite-plugin-mock'

export default [
{
url: '/api/login',
method: 'post',
response: ({ query }) => {
return {
code: 10000,
data: {
token: 'adminislogined'
}
}
}
},

{
url: '/api/logout',
method: 'post',
response: ({ query }) => {
return {
code: 10000,
data: {
}
}
}
}
] as MockMethod[]

注意,这里的 mock 实际是会通过浏览器发送真实请求的,所以在业务开发中,后端可以先定义好接口返回的数据格式,前端按照接口数据格式模拟数据。一旦后端开发完成之后,通过关闭 mock-serve,可以迅速完成业务接口交接。 目前 catchadmin vue3 的后台,正是使用这样的方式开发,所以可以通过查看 mock 的数据接口,就可以了解接口的数据定义了

· 阅读需 4 分钟
JaguarJack

Piniavue3 新推的状态管理,当然你也可以使用 vuex, 但是不是很建议,因为这个 plugin 就相当于 vuex5 了。

为什么使用它

引用官方文档的内容 PiniaVue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:

  • dev-tools 支持
    • 跟踪动作、突变的时间线
    • Store 出现在使用它们的组件中
    • time travel 和 更容易的调试
  • 热模块更换
    • 在不重新加载页面的情况下修改您的 Store
    • 在开发时保持任何现有状态
  • 插件:使用插件扩展 Pinia 功能
  • JS 用户提供适当的 TypeScript 支持或 autocompletion
  • 服务器端渲染支持

安装

yarn add pinia
# 或者使用 npm
npm install pinia

使用

找到 utils/catchadmin.ts, 全局引入

import { createPinia } from 'pinia'

protected usePinia () : CatchAdmin {
this.app.use(createPinia())

return this
}

创建 store 文件夹

mkdir src/store

// then

touch index.ts

// 保留主入口

创建模块

cd src/store && mkdir modules

创建 app Store

cd modules && mkdir app

// then

touch index.ts

内容如下

import { defineStore } from 'pinia'

const initSize = 'small'

const initLocale = 'zh'

/**
* app
*/
type app = {
size: 'small' | 'medium' | 'large',

isExpand: boolean,

locale: 'zh' | 'en',

isMobile: boolean
}

export const useAppStore = defineStore('app', {
state: () : app => ({
size: initSize,
isExpand: true,
locale: initLocale,
isMobile: false
}),

actions: {
changeSize (size: 'small' | 'medium' | 'large') {
this.size = size
},

changeLocale (locale: 'zh' | 'en') {
this.locale = locale
},

changeExpaned () {
this.isExpand = !this.isExpand
}
}
})

这里还是用的 optional api,本人体验了下 composition api 写法,似乎很麻烦,然后参考了下相关的项目的写法,都是使用的 optional api 写法。

为什么要使用

在项目中有一些状态需要在组件之间流转,例如,目前在写这个 blog 文章的时候,项目遇到的菜单「展开关闭」这个状态值,这个值需要在 header 组件,Slide 组件及其子组件之间不停的流转,就很麻烦,不仅需要 props 还需要 emit 这样的操作无疑增加了代码的复杂度以及维护的难度,使用状态管理之后,因为状态管理是响应式的,所以可以通过一个接口,就可以对状态进行操作,很方便。

找到 layout/componetns/header/index.vue

import { useAppStore } from '@/stores/modules/app'

const store = useAppStore()

const { changeExpaned } = store

就可以直接在组件中使用了 changeExpaned 方法,对应的状态也可以改变

· 阅读需 2 分钟
JaguarJack

更新依赖版本

查看依赖版本

npm outdated

// 可以看到项目依赖的版本情况
Package Current Wanted Latest Location Depended by
@element-plus/icons-vue 1.1.4 1.1.4 2.0.6 node_modules/@element-plus/icons-vue catchadmin-pro
@types/node 17.0.45 17.0.45 18.0.4 node_modules/@types/node catchadmin-pro
@vitejs/plugin-vue 2.3.3 2.3.3 3.0.0 node_modules/@vitejs/plugin-vue catchadmin-pro
@vitejs/plugin-vue-jsx 1.3.10 1.3.10 2.0.0 node_modules/@vitejs/plugin-vue-jsx catchadmin-pro
unplugin-auto-import 0.7.2 0.7.2 0.9.3 node_modules/unplugin-auto-import catchadmin-pro
unplugin-vue-components 0.19.9 0.19.9 0.21.1 node_modules/unplugin-vue-components catchadmin-pro
vite 2.9.14 2.9.14 3.0.0 node_modules/vite catchadmin-pro
vue-tsc 0.34.17 0.34.17 0.38.5 node_modules/vue-tsc catchadmin-pro

更新依赖

yarn upgrade

通过该命令可以升级依赖,然后通过上面的命令继续查看下,如果发现devDependencies没有更新的话,可以根据latest手动修改版本之后,再执行命令

yarn install

就可以完成项目的依赖升级了

代码格式化

新版前端开发使用的代码格式化的两个vscode插件,一个是 eslint,另外一个是 prettier。在 vscode 配置如下

// prettier 配置
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.formatOnSave": true
},
"[html]": {
"editor.formatOnSave": true
},
"[css]": {
"editor.formatOnSave": true
},
"[vue]": {
"editor.formatOnSave": true
},
"prettier.semi": false,
"prettier.bracketSameLine": true,
"prettier.htmlWhitespaceSensitivity": "ignore",
"prettier.singleQuote": true,
"prettier.singleAttributePerLine": true,
"prettier.printWidth": 200,
"prettier.jsxSingleQuote": true,
"prettier.trailingComma": "none"
}

// eslint
```json
{
"editor.formatOnSave": false,
//代码保存时,自动执行ESlint格式化代码
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
}

· 阅读需 2 分钟
JaguarJack

原因

为什么要使用第三方的图标?因为 ElementPlusicon 实在是太少了,不够用。hreIcons 感觉还可以,并且可以很好的和 tailwindcss 集合,图标数量也有 230+ ,感觉能满足后台需求。所以整个项目为了 icon 统一,就去除了 ElementPlus Icon 了,统一使用 heroIcons

安装

yarn add @heroicons/vue  

使用

为了统一使用,并且为了之后的动态 icon 做铺垫,所以这里需要二次封装下。创建 src/icon/index.vue 组件。

<template>
<component :is="icon" class="ct-w-5 ct-h-5"/>
</template>

<script>
import { defineComponent } from 'vue'
import * as heroIcons from '@heroicons/vue/outline'
export default defineComponent({
name: 'icon',
props: {
name: {
type: String,
required: true
}
},
setup (props, ctx) {
let name = ''

props.name.split('-').forEach(v => {
name += v[0].toUpperCase() + v.substr(1)
})

const icon = heroIcons[name + 'Icon']

return {
icon
}
}
})
</script>

创建完成之后,为了可以全局使用,需要注册在 vue 中注册下,还是找到后台的入口 utils/catchadmin.ts

import icon from '@/components/icon/index.vue'


// 找到 registerComponents 方法,注册
protected registerComponent () : CatchAdmin {
this.app.component('icon', icon)

return this
}

注册完成之后,就可以全局使用了,例如这样

<Icon name="logout"/>

当然这个使用是有一个小小弊端,修改之后无法立即刷新图标,但是这个应该不是什么大问题,因为图标是静态的。 当然如果你不喜欢这样,也可以直接在页面中引入 heroIcons

<template>
<div>
<BeakerIcon class="h-5 w-5 text-blue-500"/>
<p>...</p>
</div>
</template>

<script>
import { BeakerIcon } from '@heroicons/vue/solid'

export default {
components: { BeakerIcon }
}
</script>

· 阅读需 3 分钟
JaguarJack

介绍

本篇文章主要介绍在catchadmin V3 中如何实现国际化多语言这个功能。由于后台是基于ElementPlus进行开发,所以国际化也分为两个部分。

  • vue-i18n国际化,用于项目中自定义
  • ElementPlus国际化,用于 ElementPlus UI 组件语言切换

安装 i18n

首先需要安装 i18n 组件。

yarn add vue-i18n@9
提示

vue3 需要安装 9.0+ 版本

ElementPlus 多语言

找到 src/utils/CatchAdmin.ts, 引入多语言

import zh from 'element-plus/es/locale/lang/zh-cn'

在使用 ElementPlus 的地方加入下面的代码

this.app.use(ElementPlus, {
// 本地语言切换
locale: LocalStorage.get('language') === 'zh' && zh
})

i18n 多语言

src 目录下建立 i18n 文件夹,创建 index.ts 之后引入 i18n

import LocalStorage from '@/utils/LocalStorage'
import { createI18n } from 'vue-i18n'
import en from './languages/en'
import zh from './languages/zh'

const messages = {
en,
zh
}

const i18n = createI18n({
locale: LocalStorage.get('language') || 'zh',
messages,
// 全局可以使用 $t 函数
globalInjection: true
})

export default i18n

然后在 i18n 文件夹下建立 languages 文件夹,分别是

  • en.ts
  • zh.ts 以 zh 为例,内容如下
const zh = {
system: {
login: '登录',
register: '注册',
chinese: '中文',
english: '英文'
}
}

export default zh

在项目使用 i18n

还是找到 src/utils/CatchAdmin.ts, 引入 i18n

import i18n from '@/i18n'

this.app.use(i18n) // 即可

多语言切换组件

找到 layout/components/header/lang.vue 组件,内容如下

<template>
<div class="ct-flex hover:ct-cursor-pointer ct-pl-1 ct-pr-1">
<el-dropdown size="large" class="ct-flex ct-items-center ct-justify-center hover:ct-cursor-pointer ct-w-full" @command="selectLanguage">
<TranslateIcon class="ct-h-5 ct-w-5"/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="lang in langs"
:key="lang.value"
:command="lang.value"
:disabled="lang.value == defaultLang">
{{ $t('system.' + lang.label) }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>

<script lang="ts" setup>
import LocalStorage from '@/utils/LocalStorage'
import { computed } from '@vue/reactivity'
import { reactive } from 'vue'

const langs = reactive([
{ label: 'chinese', value: 'zh' },
{ label: 'english', value: 'en' }
])

const defaultLang = computed(() => {
return LocalStorage.get('language') || 'zh'
})

const selectLanguage = (value: string | number | object) => { LocalStorage.set('language', value);
location.reload()
}
</script>

在项目的任何位置引入该组件即可实现中英文切换,当然如果你需要其他语言的,可以根据自行在 i18n 文件夹下添加