vue3+webpack5+vue-i18n国际版官网,支持多国语言中英法等语言切换,暗黑明亮主题风格切换,多页面应用框架,从0-100实现详细步骤说明

398 阅读1分钟

国际版官网框架的搭建vue3+webpack5打包+vue-i18n国际化语言+element-plus组件库

1. 项目结构如下:

截屏2023-12-11 20.02.22.png

2. 主要用webpack5玩转多页面应用框架和单页面应用框架,多页面应用框架webpack.config.js配置如下:

const path = require("path");
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {VueLoaderPlugin} = require('vue-loader');
const isProduction = process.env.NODE_ENV === 'production';
const { setEntry,setHtmlPlugin } = require('./webpack.util.js')
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const ESBuildPlugin = require('esbuild-webpack-plugin').default;

module.exports = () => {
  return {
    mode: isProduction ? "production" : "development",
    entry: setEntry,
    devtool: isProduction ? false : "inline-source-map",
    devServer: {
      open: true,
      hot: true, 
      port: 3001,
    },
    output: {
      path: path.resolve(__dirname, "./dist"),
      filename: "js/[name].[contenthash:10].js",
    },
    mode: isProduction ? 'production' : 'development',
    module: {
      rules: [
        {
          //.js文件loader
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              cacheCompression: false
            }
          }
        },
        {
          //.vue文件loader
          test: /\.vue$/,
          use: 'vue-loader'   
        },
        {
          //.css文件loader
          test: /\.css$/,
          use: [isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader', 'css-loader']
        },
        { //图片
          test: /\.(png|svg|jpg|jpeg|gif|bmp)$/,
          type: 'asset/resource',
          generator:{ 
              filename: 'image/[contenthash:10].[ext]',
          }, 
        },
        {
          test: /\.mp4$/,
          use: {
            loader: 'file-loader',
            options: {
              name: 'video/[contenthash:10].[ext]',
            },
          },
        },
      ]
    },
    resolve: {// 设置模块如何被解析
      alias: {
        vue: "vue/dist/vue.esm-bundler.js"
      },
      extensions: ['.js', '.vue']// 按顺序解析这些后缀名
    },
    optimization: {
      minimizer: [new ESBuildPlugin()],
      splitChunks: {
        cacheGroups: {
          defaultVendors: {
            name: 'chunk-vendors',
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            chunks: 'initial'
          },
          common: {
            name: 'chunk-common',
            minChunks: 2,
            priority: -20,
            chunks: 'initial',
            reuseExistingChunk: true
          }
        }
      }
    },
    plugins: [
      new VueLoaderPlugin(),
      ...setHtmlPlugin(),
      isProduction ? new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash:10].css',
      }) : null,
      new webpack.DefinePlugin({
        __VUE_OPTIONS_API__: false,
        __VUE_PROD_DEVTOOLS__: false,
      }),
      new CleanWebpackPlugin(),
    ].filter(Boolean)
  };
};

3. webpack配置多页面入口在文件webpack.util.js中,多入口根据'./src/pages/**/index.js'下面的文件对应,生成对应的${name}.html,代码如下

/*
 * @Author: qianhua.xiong
 */
const glob = require('glob');
const HtmlWebpackPlugin = require('html-webpack-plugin');

function setEntry() {
  const files = glob.sync('./src/pages/**/index.js')
  const entry = {}
  files.forEach(file => {
    const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.js$/)
    if (ret) {
      entry[ret[1]] = {
        import: file,
        dependOn: 'vue_vendors',
      }
    }
  })
   // 拆分vue依赖
   entry['vue_vendors'] = {
    import: ['vue'],
    filename: 'commom/[name].js'
  }
  return entry
}
function getTemplate() {
    const files = glob.sync(`./src/index.html`)
    return files[0]
}
  
function setHtmlPlugin() {
    const files = glob.sync('./src/pages/**/index.js')
    const options = []
    files.forEach(file => {
      const ret = file.match(/^\.\/src\/pages\/(\S*)\/index\.js$/)
      if (ret) {
        const name = ret[1]
        if(name === 'home'){
          options.push(new HtmlWebpackPlugin({
            filename: 'index.html',
            template: getTemplate(),
            title: name,
            minify: {
              collapseWhitespace: false,
              removeComments: true, 
            },
            chunks: ['vue_vendors',name]
          }))
        }
        options.push(new HtmlWebpackPlugin({
          filename: `${name}.html`,
          template: getTemplate(),
          title: name,
          minify: {
            collapseWhitespace: false,
            removeComments: true, 
          },
          chunks: ['vue_vendors',name]
        }))
      }
    })
    return options
}
module.exports = {
  setEntry,
  setHtmlPlugin
}

最后编译时生成的dist目录文件对应如下:

截屏2023-12-11 20.15.42.png

4. App.vue文件是页面的展示代码如下

<template>
    <HeadContent/>
    <div class="pageContent">
      <component :is="MyComponent" />
    </div>
    <FootContent/>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import HeadContent from './view/head/index.vue';
import FootContent from './view/foot/index.vue';
const getUrlPath = ()=>{
  const url = window.location.href;
  var name = 'home';
  if(url && url.indexOf('.html')>0){
    var pathStr = url.split('.html')[0];
    const  pathList = ['home','join','our','product','technology','about']
    pathList.forEach(item=>{
      if(pathStr.indexOf(item)>-1){
        name = item
      }
    })
  }
  return name 
}
var MyComponent = ref(null);
onMounted(async () => {
  try {
    const component = await import('./view/'+ getUrlPath() +'/index.vue')
    MyComponent.value = component.default;
  } catch (error) {
    console.error('Failed to load component:', error);
  }
});
</script>
  <style>
  * {
    margin: 0;
  }
  </style>

5. index.html为每个页面共用的html文件,后面根据webpack.util.js文件配置的多页面应用可以生成不同的html文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>支持多国语言国际化官网</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

6. pages中index.js代码如下:

index.js为每个页面的vue框架入口配置: 截屏2023-12-11 20.18.36.png

import { createApp } from 'vue';
import i18n from '../../i18n/index';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import './index.css';
import App from '../../App.vue';
const app = createApp(App);
app.use(i18n);
app.use(ElementPlus);
app.mount('#app');

7.view目录中为每个页面对应的业务代码,这里不举例了,重点说一下多国语言的配置,在i18n目录下的index.js文件配置如下:

// index.js
import { createI18n } from 'vue-i18n';
import zh from './zh.js';
import en from './en.js';
import fr from './fr.js';
const messages = {
  en,
  zh,
  fr
}
const language = (navigator.language || 'en').toLocaleLowerCase() // 这是获取浏览器的语言
const i18n = createI18n({
  legacy: false,
  locale: localStorage.getItem('lang') || language.split('-')[0] || 'en', // 首先从缓存里拿,没有的话就用浏览器语言,
  fallbackLocale: 'en', // 设置备用语言
  messages, 
})

export default i18n

不同的语言对应不同的配置,比如en.js:

export default {
    menu: {
        home:'HOME',
        product:'PRODUCTS',
        our:"ABOUT",
        join:"CAREER",
        about:"NEWS",
        technology:"TECHNOLOGIES"
    },
}

法语配置文件fr.js:

export default {
   menu: {
        home:'Accueil',
        product:'Produits',
        our:"A propos",
        join:"Se joindre",
        about:"Actualités",
        technology:"Technologies"
    },
}

中文配置文件zh.js

export default {
     menu: {
        home:'首页',
        product:'产品',
        our:"我们",
        join:"加入",
        about:"动态",
        technology:"技术"
    },
}

页面展示语言切换:

<template>
	<div class="language-menu">
        <div class="menu-item" @click="changeLang('fr')" :class="{'active':locale=== 'fr'}">FR</div><div class="jiange"></div>
    	<div class="menu-item" @click="changeLang('en')" :class="{'active':locale=== 'en'}">EN</div><div class="jiange"></div>
        <div class="menu-item" @click="changeLang('zh')" :class="{'active':locale=== 'zh'}">CN</div>
    </div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const changeLang = (lang) => {
  locale.value = lang
  localStorage.setItem('lang', lang)
}
</script>

8. 页面导航栏的实现

在 template中展示以{{ $t("menu.home")}}的形式


<template>
    <div class="headerPage">
        <div class="logo-wrapper"><a href="/" class="header_logo"></a></div>
        <div class="flex-grow" /> 
        <div class="menu" id="router-menu">
            <div class="menu-item"><a href="/home.html" :class="{'a-active':locationPath('home.html')}">{{ $t("menu.home")}}</a></div>
            <div class="menu-item"><a href="/our.html" :class="{'a-active':locationPath('our.html')}">{{ $t("menu.our") }}</a></div>
            <div class="menu-item"><a href="/technology.html" :class="{'a-active':locationPath('technology.html')}">{{ $t("menu.technology") }}</a></div>
            <div class="menu-item"><a href="/product.html?product1" :class="{'a-active':locationPath('product.html')}" class="parentlink"  @mouseenter="showSubMenu">{{ $t("menu.product") }}</a></div>
            <div class="menu-item"><a href="/join.html" :class="{'a-active':locationPath('join.html')}">{{ $t("menu.join") }}</a></div>
            <div class="menu-item"><a href="/about.html" :class="{'a-active':locationPath('about.html')}">{{ $t("menu.about") }}</a></div>
            <div :class="{ 'submenu':true , visibleSubmenu:submenu }" @mouseenter="showSubMenu" @mouseleave="hideSubMenu" >
                <a href="/product.html?product1" :class="{'a-active':locationPath('/product.html?product1')}">{{ $t("productMenu.0") }}</a>
                <a href="/product.html?product2" :class="{'a-active':locationPath('/product.html?product2')}">{{ $t("productMenu.1") }}</a>
                <a href="/product.html?product3" :class="{'a-active':locationPath('/product.html?product3')}">{{ $t("productMenu.2") }}</a>
            </div>
        </div>
        <ChangeLang/>
    </div>
</template>
<script setup>
import ChangeLang from '../../component/changeLang.vue';
import { ref } from 'vue';
const submenu = ref(false);
const hideSubMenu = ()=>{
    submenu.value = false;
}
const showSubMenu = ()=>{
    submenu.value = true;
}
const locationPath = (params)=>{
  const url = window.location.href;
  var tag = false;
  if(url && url.indexOf('.html')>0){
    if(url.indexOf(params)>-1){
        tag = true
    }
  }
  return tag 
}
</script>

9. 动态监听vue-i18n语言切换,随之动态切换页面的视频或者图片

<script setup>
import {ref, watch} from 'vue';
import { useI18n } from 'vue-i18n';
import visionVideo from '../../video/vision.mp4'; //video文件路径为项目中中文版video文件的路径
import versionEnVideo from '../../video/visionen.mp4'; //英文版video文件的路径
const { locale } = useI18n();
const lang = localStorage.getItem('lang');
console.log(lang,'lang')
const VedioSelect = ref(lang == 'zh' ? visionVideo : versionEnVideo)
// 监听语言变化
watch(locale, (newValue, oldValue) => {
  console.log('组件名----语言变化1');
  console.log(`newValue: ${newValue}`);
  console.log(`oldValue: ${oldValue}`);
  if(newValue == 'en'){
    VedioSelect.value = versionEnVideo
  }else if(newValue == 'zh'){
    VedioSelect.value = visionVideo
  }
});
</script>

大致搞定,多页面入口,有利于seo检索,立刻给自己的官网写一个国际化的网站吧!

github代码地址

github.com/xiongqianhu…

下载后可以直接跑着用,

  • 有单页面应用版本在main分支,
  • 有多页面应用版本在multiple-pages分支,
  • 也有现成的demo例子在internationalWeb-demo分支,
  • 需要的可以直接收藏下载,喜欢的给点一赞,是对我的鼓励哦!