前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vite+Vue3+Typescript后台管理项目 i18n国际化

Vite+Vue3+Typescript后台管理项目 i18n国际化

原创
作者头像
KID.
修改2023-10-19 15:12:34
9570
修改2023-10-19 15:12:34
举报

vue3已经出来很久了,因为工作只是再维护老项目,没有做技术更新,所以对vue3的使用上面会差很多,但是现在又有许多公司要求有vue3使用经验,所以对Vue3 ts自学写的模板项目

这里会写明全部流程及要点。

Vite+Vue3+Typescript项目地址 https://github.com/Seven7v/vue3-Ts-admin

后台管理首页
后台管理首页
登录页面
登录页面

需要的话可以自行下载

  • vite使用的Rollup进行打包,相对来说是比webpack更加轻量级的,这里从项目的启动速度就可以体现出来。
  • vite 天生支持 typescript 使用ts更加友好
  • vite 带有css 预处理器,包括less scss 使用都可以不用安装loader,(在webpack中需要安装loader)
  • vite在修改config文件后不需要重启项目,会自动更新页面

对比Vue3 对比Vue2 的更新

  • 在vue2中,同一元素上的v-for的优先级高于v-if,vue3更改了两者的优先级,v-if的优先级高于v-for
  • destroyed生命周期选项被重命名为 unmounted
  • beforeDestroy 生命周期选项被重命名为 beforeUnmount
  • Proxy 代替Obiect.defineProperty 重构了响应式系统可以监听到数组下标变化,及对象新增属性,因为监听的不是对象属性,而是对象本身,还可拦截 apply、has 等13种方法
  • 支持在<style></style> 里使用 v-bind,给CSS绑定S变量(color: v-bind(str))
  • 新增Composition API 可以更好的逻辑复用和代码组织,同一功能的代码不至于像以前一样太分散

安装vite

使用npm init vite 进行安装

代码语言:txt
复制
PS F:\v3> npm init vite
Need to install the following packages:
  create-vite@4.4.1
Ok to proceed? (y) y
√ Project name: ... vite-project
√ Select a framework: » Vue
√ Select a variant: » TypeScript

Scaffolding project in F:\v3\vite-project...

随后执行 vite 项目就跑起来了。

代码语言:txt
复制
  cd vite-project
  npm install
  npm run dev

vite官网还提供了其他创建方式

npm create vite@latest

使用yarnyarn create vite

使用pnpmpnpm create vite

项目运行成功后 NetWork 不显示链接,

代码语言:txt
复制
  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

这里我们可以更新vite.config.ts

代码语言:txt
复制
 server: {
    host: '0.0.0.0',
    port: 5800, //设置服务启动端口号,是一个可选项,不要设置为本机的端口号,可能会发生冲突
    open: true, //是否自动打开浏览器,可选项
    }

这时终端就会更新为

代码语言:txt
复制
  ➜  Local:   http://localhost:5800/
  ➜  Network: http://192.168.xxx.xx:5800/
  ➜  Network: http://xx.xx.xxxxx:5800/

项目路由

创建好项目后为我们的项目配置路由

代码语言:txt
复制
npm install vue-router@4

vue-router文档提供了使用的手册,

新建 router文件夹,index.ts中的内容如下

代码语言:txt
复制
import { createRouter, createWebHistory } from 'vue-router'
import { App } from 'vue'
import { getUserInfoApi } from '../sever/api'
import routes from './routes' // 页面中 配置的路由
const router = createRouter({
  history: createWebHistory(), //history模式
  routes
})

router.beforeEach(async (to, from) => {
  // 这里可以输入一些页面重定向内容,判断token跳转登录页
})
// export default route 将路由导出的写法

// 这里只导出一个方法,在页面外不可以修改router里的内容
// 封装路由方法,传入app <Element> 代表页面内的标签元素
export const initRouter = (app: App<Element>) => {
  app.use(router)
}

route.ts配置项目路由

代码语言:txt
复制
import { RouteRecordRaw } from 'vue-router'
const Layout = () => import('../layout/index.vue') //页面layout 
const Login = () => import('../pages/login/index.vue')
const Homepage = () => import('../pages/homePage/index.vue')
const Chart = () => import('../pages/chart/index.vue')
const DocumentSetting = () => import('../pages/document/setting.vue')
const DocumentPreview = () => import('../pages/document/preview.vue')
const UserConsole = () => import('../pages/console/userConsole.vue')
const PermissionConsole = () => import('../pages/console/permissionConsole.vue')

const routes: RouteRecordRaw[] = [
  {
    path: '/admin',
    component: Layout,
    name: 'admin',
    meta: {
      icon: 'Menu',
      isNav: true
    },
    children: [
      {
        path: '/admin/home',
        component: Homepage,
        name: 'home',
        meta: {
          isNav: true
        }
      }
    ]
  },
  {
    path: '/login',
    component: Login,
    name: 'login'
  },
  {
    path: '/',
    redirect: '/admin/home'
  }
]

const asyncRouter: RouteRecordRaw[] = [
  {
    path: '/statistics',
    component: Layout,
    name: 'statistics',
    meta: {
      icon: 'PieChart',
      isNav: true,
    },
    children: [
      {
        path: '/statistics/chart',
        component: Chart,
        name: 'chart',
        meta: {
          isNav: true,
        }
      }
    ]
  },
    {
    path: '/document',
    component: Layout,
    name: 'document',
    meta: {
      isNav: true,
      icon: 'Document'
    },
    children: [
      {
        path: '/document/setting',
        component: DocumentSetting,
        name: 'setting',
        meta: {
          isNav: true,
          role: ['admin', 'editor', 'normal']
        }
      },
      {
        path: '/document/table',
        component: DocumentPreview,
        name: 'table',
        meta: {
          isNav: true,
          role: ['admin', 'editor', 'normal']
        }
      }
    ]
  },
]
const CurrentRoute: RouteRecordRaw[] = routes.concat(...asyncRouter)
export default CurrentRoute

i18n

安装 vue-i18n 创建i18n文件 使用方法如下

代码语言:txt
复制
import { App } from 'vue'
import { createI18n } from 'vue-i18n'
import { zh } from './zh'
import { en } from './en'


const language = (navigator.language || 'en').toLocaleLowerCase() // 获取浏览器的语言设置
const i18n = createI18n({
  legacy: false,
  locale: localStorage.getItem('lang') || language, // 优先从本地存储获取语言设置,如果没有则使用浏览器默认语言
  fallbackLocale: 'en', // 当前语言无法找到匹配的翻译时,使用的备选语言
  messages: {
    en,
    zh
  }
})

// 封装i18n方法
export const initI18n = (app: App<Element>) => {
  app.use(i18n)
}

element-plus

成功后可以安装element-plus,官网里包括安装,全局引用及自动按需引用都有配置教程

代码语言:txt
复制
$ npm install element-plus --save

main.ts

main.ts中引入路由, i18n,全局样式

代码语言:txt
复制
import { createApp } from 'vue'
import './style.css'
import './assets/style/common.css'
import 'element-plus/dist/index.css'
import App from './App.vue'
import { initRouter } from './routes'
import { initI18n } from './i18n'
import * as ElIcons from '@element-plus/icons-vue'

setTeam()
const app = createApp(App)
initI18n(app)
// 在页面中调用router封装的方法挂载路由
initRouter(app)
app.mount('#app')
for (const name in ElIcons) app.component(name, (ElIcons as any)[name])

做了这些工作,在页面内修改path就可以进行页面切换了,

切换语言

封装切换项目语言组件,可以写在项目公用组件库里 components文件夹里

changeLang组件内容

代码语言:txt
复制
<script setup lang="ts">
import { getCurrentInstance, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'

// 切换语言
const { proxy } = getCurrentInstance() as any
const { t } = useI18n()
const lang = ref('chinese')

watchEffect(() => {
  if (lang.value === 'chinese') {
    proxy.$i18n.locale = 'zh'
    localStorage.setItem('lang', 'zh')
  }
  if (lang.value === 'english') {
    proxy.$i18n.locale = 'en'
    localStorage.setItem('lang', 'en')
  }
})
</script>

<template>
  <el-select style="width: 100px" v-model="lang">
    <el-option :label="t('Chinese')" value="chinese" />
    <el-option :label="t('English')" value="english" />
  </el-select>
</template>

封装请求方法

这里我们使用axios,请求,对于接口 单是前端项目可以考虑用rap2实现模拟请求,

创建server目录,server目录中index.ts进行封装,api.ts或其他文件用来管理接口内容

index.ts

代码语言:txt
复制
import axios from 'axios'
import { AxiosInstance } from 'axios'
import { ElMessage } from 'element-plus'

const baseUrl = '/api'
export const $axios: AxiosInstance = axios.create({
  baseURL: baseUrl,
  headers: {
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest'
  }
})

$axios.interceptors.request.use(config => {
  var xtoken: any = localStorage.getItem('token')
  if (xtoken) {
    xtoken = xtoken
    config.headers['Authorization'] = xtoken
  }
  return config
})
$axios.interceptors.response.use(
  (res: any) => {
    console.log(res)
    const { code, message } = res.data
    if (code === 200) {
      if (message) {
        ElMessage.success(message)
      }
      return res
    } else {
      ElMessage.error(res.massage)
      return Promise.reject(new Error(res.message))
    }
  },
  (err: any) => {
    console.error(err)
    const message = err.response.data
    ElMessage.error(message)
    return Promise.reject(new Error(err.message))
  }
)

api.ts

代码语言:txt
复制
import { $axios } from './index.ts'
import { InterfaceLoginReq } from '../type'

// 登录
export const loginApi = (params: InterfaceLoginReq) => {
  return $axios.post('/login', params)
}

// 创建用户
export const createUserApi = (params: InterfaceLoginReq) => {
  return $axios.post('/create', params)
}

// 获取用户信息
export const getUserInfoApi = () => {
  return $axios.get('/userInfo')
}

// 登出账号
export const logoutApi = () => {
  return $axios.post('/logout')
}

登录页 表单提交

代码语言:txt
复制
<template>
  <div class="login cen disflex ai-cen">
    <div class="login-form-wrapper disflex">
      <div class="login-img-wrapper bg-prim">
        <div class="login-title fw900">{{ $t('login.management') }}</div>
        <img class="login-img" src="../../assets/img/login.svg" alt="" />
      </div>
      <div class="login-box">
        <el-card class="login-inner">
          <changeLanguage class="login-lang" />

          <el-form
            ref="formRef"
            :model="dynamicValidateForm"
            label-width="100px"
            label-position="left"
            class="login-form mb30"
          >
            <el-form-item
              prop="username"
              :label="t('login.username')"
              :rules="[
                {
                  required: true,
                  message: t('login.usernameRequire'),
                  trigger: 'blur'
                }
              ]"
            >
              <el-input v-model="dynamicValidateForm.username" />
            </el-form-item>
            <el-form-item
              prop="password"
              :label="t('login.password')"
              :rules="[
                {
                  required: true,
                  message: t('login.passwordRequire'),
                  trigger: 'blur'
                }
              ]"
            >
              <el-input type="password" v-model="dynamicValidateForm.password" />
            </el-form-item>
          </el-form>
          <el-button type="primary" class="login-btn" @click="submitForm(formRef)">{{
            t('login.login')
          }}</el-button>
          <el-button class="login-btn" @click="resetForm(formRef)">{{
            t('login.concel')
          }}</el-button>
        </el-card>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import { FormInstance } from 'element-plus'
import { InterfaceLoginReq } from '../../type'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { loginApi, createUserApi } from '../../sever/api'
import { setLoginTimeApi } from '../../sever/data'
import changeLanguage from '../components/changeLanguage.vue'

const { t } = useI18n()
const formRef = ref<FormInstance>()
const dynamicValidateForm = reactive<InterfaceLoginReq>({
  username: '',
  password: ''
})
const router = useRouter()

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate(async valid => {
    if (valid) {
      // 创建用户时使用
      // await createUserApi(dynamicValidateForm)
      const res = await loginApi(dynamicValidateForm)
      if (res.data.code == 200) {
        localStorage.setItem('token', res.data.token)
        const loginTimeParams = {
          username: dynamicValidateForm.username,
          loginTime: new Date()
        }
        const resq = await setLoginTimeApi(loginTimeParams)
        console.log(resq)
        router.push({
          name: 'home'
        })
      }
    } else {
      return false
    }
  })
}

const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}
</script>

<style lang="less" scoped>
.login {
  width: 100%;
  height: 100%;
  &-form-wrapper {
    width: 100%;
    height: 100%;
  }
  &-img-wrapper {
    width: 50%;
  }
  &-title {
    font-size: 40px;
    font-family: Verdana, Geneva, Tahoma, sans-serif;
    color: #fff;
    margin-top: 15%;
    margin-bottom: 15%;
    margin-left: 10%;
  }
  &-img {
    width: 60%;
    margin-left: 30%;
  }
  &-form {
    margin-bottom: 20px;
  }
  &-box {
    width: 50%;
    background-color: #ffffff;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  &-inner {
    position: relative;
    width: 60%;
    height: 400px;
    background-color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  &-lang {
    position: absolute;
    right: 20px;
    top: 20px;
  }
  &-btn {
    width: 100%;
    margin-bottom: 15px;
  }
}
/deep/ .el-button + .el-button {
  margin-left: 0;
}
</style>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Vite+Vue3+Typescript项目地址 https://github.com/Seven7v/vue3-Ts-admin
  • 对比Vue3 对比Vue2 的更新
    • 安装vite
      • 项目路由
        • i18n
          • element-plus
            • main.ts
              • 切换语言
                • 封装请求方法
                  • 登录页 表单提交
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档