跳到主要内容

· 阅读需 3 分钟
JaguarJack

为什么会有这么个需求呢

最近在开发CMS,所以想直接分发 cms 的打包后的代码。但是这里面就有个问题,如果我将打包后的代码直接让用户使用,那么不得不面临一个问题,就是打包后的接口地址也会直接打包给了用户。所以这里就需要用户自己去设置接口地址。但是前端是打包后的代码,没有办法去修改,所以就需要这么一个动态修改配置的功能

挂载

如何进行挂载呢?首先需要在入口文件index.html引入

<div id="app"></div>
<script type="module" src="../resources/admin/app.ts"></script>

// 就在这里引入
<script src="./static/config.js"></script>

千万不要纠结这个路径,因为这是打包后的路径,所以开发的时候也不需要管它。

第二步,如果没有public目录,在项目的任意目录创建 public 目录,在public目录下创建 static/config.js, 内容如下

window.admin_config = {
title: '',
BASE_URL: '',
}

然后找到 axios 请求,CatchAdmin进行了封装,所以使用的是 support\http.ts。 在这之前 CatchAdmin 还封装了两个助手方法

// 从 window 对象获取配置
// 因为动态配置挂载在 window 的 admin.config 属性
export function _window(key: string) {
if (window.hasOwnProperty('admin_config')) {
//@ts-ignore
return window.admin_config[key]
}

return null
}

// 获取 base_url
export function getBaseUrl() {
// 如果动态配置存在并且 BASE_URL 有值,则从动态配置获取
// 否则从 env 配置获取
return _window('BASE_URL') ? _window('BASE_URL') : env('VITE_BASE_URL')
}

再到 http.ts 获取 baseURL

  protected getConfig(): AxiosRequestConfig {
// set base url
this.config.baseURL = getBaseUrl()
}

这样就可以了。那会有人有疑问?./static/config.js 怎么引入的呢? 在 build 打包后,public 会被打包到 index.html 平级目录,所以可以直接引入。对了,忘了一个很重要的,就是配置 public 目录,找到 vite.config.js 文件

export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
publicDir: 'public 目录相对路径',
}
}

· 阅读需 3 分钟
JaguarJack

问题由来

在发布CatchAdmin 3.0 版本,有用户提到,在安装权限管理模块之后,刷新管理模块的页面的路由时,页面会直接跳转到404页面,而不是当前页面。其实这个问题在开发期间就已经发现了,但是当时因为赶着进度,所以就没有管。

查找原因

如何查找呢?因为catchadmin 的路由是由动态和静态路由组成,所以首先确定静态路由是否存在该问题。通过刷新用户管理模块,发现并没有跳转到 404,说明静态路由是没有问题的。在通过代码 debug 发现路由 Guard 时

router.beforeEach(async (to, from, next) => {
// 发现 to.path 已经是 404 了
}

说明在挂载动态路由前,就已经被拦截了。说明在 beforeEach 之前就已经进行了路由匹配。

解决

找到 resource/admin/route/index.ts 文件

const defaultRoutes: RouteRecordRaw[] = [
{
path: '/',
name: '/',
component: () => import('/admin/layout/index.vue'),
redirect: '/dashboard',
children: [
{
path: '/404',
name: '404',
meta: { title: '404' },
component: () => import('/admin/components/404/index.vue'),
},
],
},
// 未定义路有重定向到 404
{
path: '/:pathMatch(.*)*',
redirect: '/404',
},
]

会发现在 createRouter 的时候就已经将匹配路由添加了,问题原因就出现在这里。 因为动态路由还没有加载,那么此时肯定无法跟动态路由匹配的,自然跳转到了 404 页面。

既然是在动态路由前进行匹配,那么解决起来就很简单了。只需要在动态路由挂载之后匹配不就可以了吗? 所以找到 resource/admin/router/guard/index.ts

 // 阻塞获取用户信息
await userStore.getUserInfo()
// 如果后端没有返回 permissions,前台则只使用静态路由
if (userStore.getPermissions !== undefined) {
// 挂载路由(实际是从后端获取用户的权限)
const permissionStore = usePermissionsStore()
// 动态路由挂载
const asyncRoutes = permissionStore.getAsyncMenusFrom(toRaw(userStore.getPermissions))
asyncRoutes.forEach((route: Menu) => {
router.addRoute(route as unknown as RouteRecordRaw)
})

// 在动态路由之后挂载匹配 404 路由
router.addRoute({
path: '/:pathMatch(.*)*',
redirect: '/404',
})
}
next({ ...to, replace: true });

完美解决

· 阅读需 5 分钟
JaguarJack

很久之前就听说 Github Action 自动化部署,可惜一直没有使用过。最近也是因为需要更新 V3 的文档,要不停的使用 build,还要上传到服务器,反反复复,简直是折磨人。直到今天去到仓库的时候看到 Action tab,这才想试试 Github 这个集成服务。

查看了相关 GitHub Actions 的资料,它居然是2018年10月推出的。有点吃惊,我居然是在四年后才用上该服务。

使用

首先在项目根目录创建一个 .github 文件夹

cd .github && mkdir workflows

// then

touch main.yml

Github Action 使用 YAML 格式文件做工作流,所以部署的一系列操作应该就是在这个文件夹里完成。在使用之前先了解下 Github Action 的基本概念

基本概念

  • workflow 持续集成一次运行的过程,就是一个 workflow (可以简单地理解为一个 yml 文件就是一个 workflow)

  • job 一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务

  • step 每个 job 由多个 step 构成,一步步完成

  • action 每个 step 可以依次执行一个或多个命令

整个 yml 文件就是一个一个 job 组成,下面看一下 catchadmin 的文档部署的 workflow

name: Build and Deploy
on:
## 表示
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master

- name: Install and Build
run: |
yarn install
yarn build
  • master 分支发送 push 事件,才会执行 workflow
  • 只有一个job,运行在虚拟机环境ubuntu-latest
  • 第一步是获取源码,使用的 action 是 actions/checkout
  • 第二步是安装依赖和构建文档

是不是很简单?当然这仅仅是在 ubuntu 虚拟机里构建了文档,但需求是要把每次 push 后构建在 ubuntu 虚拟机里的文档上传到自己的服务器。所以这里还差一个步骤。 首先在服务器使用 ssh-keygen 命令生成私钥和公钥,输入该命令后直接回车就行了,会生成默认的两个文件

  • ~/.ssh/id_rsa 私钥文件,不能泄露
  • ~/.ssh/id_rsa.pub 公钥文件

接下来将公钥里的内容写入 authorized_keys 文件

cat id_rsa.pub >> authorized_keys

// 顺便修改权限
chmod -R 700 ~/.ssh
chmod -R 640 authorized_keys

找到 /etc/ssh/sshd_config 文件, 打开两个配置

PermitRootLogin yes

PubkeyAuthentication yes

然后重新启动 sshd

 systemctl restart sshd

配置 Github 相关

首先找到需要配置的项目,这里说明,下面所说的都是部署到服务器 zRigZq.png 如图,首先找到项目的 Setting,然后找到 Secrets 的 Actions,配置相关服务器变量。本文档主要配置以下几个变量.

  • SERVER_HOST 服务器 IP 地址
  • SERVER_USERNAME 服务器用户名
  • SERVER_WORKDIR 文件上传的位置
  • SERVER_SECRET ssh 私钥,还记得之前的 /.ssh/id_rsa 私钥文件吗?把它的内容复制出来,作为 SERVER_SECRETValue

OK! 这样部署的相关配置就已经配置好了,main.yml 还缺一个部署步骤,看一下完成的配置

name: Build and Deploy
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master

- name: Install and Build
run: |
yarn install
yarn build

## deploy 步骤
- name: Deploy
uses: easingthemes/ssh-deploy@v2.0.7
env:
name: use rsa deploy
REMOTE_HOST: ${{ secrets.SERVER_HOST }}
REMOTE_USER: ${{ secrets.SERVER_USERNAME }}
## 秘钥
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SECRET }}
## 这个参数是必须的
ARGS: -avz --delete
SOURCE: "build/"
SERVER_PORT: "22"
TARGET: ${{ secrets.SERVER_WORKDIR }}
EXCLUDE: "/build/, /node_modules/"

这样就创建完成了。可以试试 PUSH 到仓库,查看流水线的每个步骤,如下图所示,就可以了。因为服务器在美国,使用的是 Rsync 同步文件,可能时间比较长点。 使用这个服务的时候,腾讯云提醒我高危😄。 zRFlT0.png

· 阅读需 2 分钟
JaguarJack

为什么要做封装

在日常的开发中,经常会使用el-switch组件做状态切换。一个常见的场景,就是在 Table 中对某一行数据的状态做切换,假设会和后端进行数据更新。那么以目前el-switch组件的功能,不管接口是否请求成功,如果不做封装的话,它都会切换到相对的状态,无法对它的状态进行控制。

封装

switch 组件

<template>
<el-switch @change="enabled(api, id)" :active-value="Status.ENABLE" :inactive-value="Status.DISABLE" :model-value="modelValue" :loading="loading" />
</template>

<script lang="ts" setup>
import { useEnabled } from '/admin/composables/curd/useEnabled'
import { Status } from '/admin/enum/app'
import { watch } from 'vue'

const props = defineProps({
modelValue: Boolean | Number | String,
api: String,
id: Number | String,
})

const emits = defineEmits(['update:modelValue'])

const { enabled, success, loading } = useEnabled()

watch(success, function () {
emits('update:modelValue', props.modelValue === Status.ENABLE ? Status.DISABLE : Status.ENABLE)
success.value = false
})
</script>

useEnable hook

import http from '/admin/support/http'
import { Code } from '/admin/assets/enum/app'
import Message from '/admin/support/message'
import { ref } from 'vue'

export function useEnabled() {
const success = ref(false)
const loading = ref<boolean>(false)
function enabled(path: string, id: string | number, data: object = {}) {
loading.value = true
http
.put(path + '/enable/' + id, data)
.then(r => {
if (r.data.code === Code.SUCCESS) {
success.value = true
Message.success(r.data.message)
} else {
Message.error(r.data.message)
}
})
.finally(() => {
loading.value = false
})
}

return { enabled, success, loading }
}

这样就可以很好的控制 swtich 的切换了,完美!!!具体代码可参照 catchadmin

· 阅读需 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 文件夹下添加