nuxt.js常见问题总结

最近在开发我司一项目,用的是nuxt.js开发的,nuxt版本号:2.14.8。

主要记录一下,我在这个项目中一些用到的东西吧,地址:http://www.lawlawing.com/

感觉基本包含了nuxt.js的大部分配置了。

安装

第一种办法:

此方法可参考我的这篇文章:初识nuxt.js

我的npm 下载太慢了,所以用了第二种办法

npx create-nuxt-app <project-name>

文档:https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-server

第二种办法:

// 1. 全局安装@vue/cli,已安装的可忽略
> npm i @vue/cli -g
// 2. 拉取nuxt模板
> vue init nuxt/starter {project-name}

// 如下显示则为安装完毕
? Project name project-name
? Project description Nuxt.js project
? Author zhanghaoran <zhanghaoran.rem@qq.com>

   vue-cli · Generated "project-name".

   To get started:

     cd project-name
     npm install # Or yarn
     npm run dev

配置启动端口

同是以本机9363端口启动

第一种办法:

// package.json
{
    ...
    "config": {
        "nuxt": {
            "host": "0.0.0.0",
            "port": "9363"
        }
    }
}

第二种办法:

// nuxt.config.js
export default {
  server: {
    port: 9363, // default: 3000
    host: '0.0.0.0', // default: localhost,
    timing: false
  }
}

文档:https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-server

配置文件别名(alias)

  1. nuxt.config.js中:
import { resolve } from 'path'

module.exports = {
    alias: {
        'img': resolve(__dirname, './assets/images'),
        'css': resolve(__dirname, './assets/css'),
    },
}
  1. 在组件和页面中使用:
data(){
    return{
        img:{
            logo: require('img/public/logo.png')
        }
    }
}

<style scoped>
    @import "css/global.css";
</style>

文档:https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-alias

全局css

// nuxt.config.js
// 这里似乎不能用以上定义的别名路径
css:[
    '~/assets/css/global.css'
],

element-ui按需引入

  1. 下载element-ui与babel
npm i element-ui -S
// 用于按需加载
npm i babel-plugin-component -D
  1. ~/plugins/下创建element-ui.js
// ~/plugins/element-ui.js

import Vue from 'vue'
import { Input, Select, Button, Option } from 'element-ui'

export default ()=>{
    Vue.use(Input)
    Vue.use(Select)
    Vue.use(Button)
    Vue.use(Option)
}
  1. nuxt.config.js中引入element-ui.js:
// nuxt.config.js
plugins: [
    '~/plugins/element-ui'
],
  1. nuxt.cofnig.js中配置按需加载
// nuxt.confg.js
build: {
    vendor: ['element-ui'],
    babel:{
        plugins: [
            ["component", 
                {"libraryName": "element-ui", "styleLibraryName": "theme-chalk"}
            ]
        ]
    },
}

文档:https://element.eleme.cn/#/zh-CN/component/quickstart

  1. 安装

    npm i cookie-universal-nuxt -S
    
  2. nuxt.config.js中引入:

    modules: [
        'cookie-universal-nuxt',
    ],
    
  3. 在客户端中使用:

    const path = "/";
    // 20天
    const expires = new Date(new Date() * 1 + 86400000 * 20);
    const tokenName = 'token';
    
    this.$cookies.set(tokenName, "token", {path: path, domain: CookieDomain, expires: expires});
    this.$cookies.remove(tokenName, {path:path, domain: CookieDomain});
    
  4. 在服务端中使用:

    应该是服务端中,比如在下面的axios中使用

文档:https://www.npmjs.com/package/cookie-universal-nuxt

nuxt-axios

  1. 安装

    npm i @nuxtjs/axios -S
    
  2. 配置axios

    // ~/plugins/axios.js
    
    export default function ({redirect, req, app: { $axios, $cookies }})  {
        $axios.defaults.baseURL = "http://xxx.com";
        $axios.onRequest(config => {
            config.headers.common['Authorization'] = $cookies.get('token');
        });
    
        $axios.interceptors.response.use(
            response => {
                // return response
                if(response.data.code == 401){
                    // TODO: 清楚本地用户数据, 并重定向到登录页面
                }else if(response.data.code == 404){
                    // TODO: 接口404
                }else if(response.data.code == 0){
                    return response.data.data
                }else{
                    return Promise.reject(response)
                }
            },
            error => {
                if(error.response.status == 500){
                    // http状态500,服务器内部错误,重定向到500页面
                    redirect("/500.htm")
                }
                if(error.response.status == 404){
                    // http状态500,请求API找不到,重定向到404页面
                    redirect("/404.html")
                }
                return Promise.reject(error.response)   // 返回接口返回的错误信息
            })
    }
    
  3. nuxt.config.js中引入

    modules: [
        '@nuxtjs/axios'
    ],
    plugins: [
        '~/plugins/axios'
    ],
    
  4. 使用

    // 页面中:
    
    // 组件中:
    

文档:https://www.npmjs.com/package/@nuxtjs/axios

文档:http://www.axios-js.com/zh-cn/docs/nuxtjs-axios.html

环境变量

  1. 下载安装
npm i cross-env -D
  1. 配置启动环境变量
// ~/package.json

"scripts": {
    "dev": "cross-env MODE=development nuxt",
    "build:test": "cross-env MODE=test nuxt build",
    "start:test": "cross-env MODE=test nuxt start",
    "build": "cross-env MODE=production nuxt build",
    "start": "cross-env MODE=production nuxt start",
    "generate": "nuxt generate"
},
  1. nuxt.config.js 中配置env:
export default {
    env:{
        MODE: process.env.MODE
    },
}
  1. 创建在各个环境下的接口地址的文件:

    // ~/plugins/env.js
    
    module.exports = {
        // 开发环境
        development: {
            MODE: 'development',
            API: 'development',
            Cookie_Domain: "development"
        },
        // 测试环境
        test:{
            MODE: 'test',
            API: 'test',
            Cookie_Domain: "test"
        },
        // 线上环境
        production: {
            MODE: 'production',
            API: 'production',
            Cookie_Domain: "production"
        }
    }
    
  2. 创建返回当前环境接口地址的文件:

    // ~/plugins/host.js
    
    const env = require('./env')
    
    // 接口
    export const API = env[process.env.MODE].API;
    // Cookie
    export const CookieDomain = env[process.env.MODE].Cookie_Domain;
    
  3. 使用:

    import {CookieDomain} from "../plugins/host";
    
    // 此时,当前系统在开发环境则返回:development;
    console.log(CookieDomain)
    this.$cookies.set(tokenName, token, {domain: CookieDomain});
    

一些优化项配置

// nuxt.config.js

module.exports = {
    render: {
        // 解决nuxt项目首屏加载全部js
        resourceHints: false,
    },
    build: {
        analyze: false,        // 可视化打包结果
        // css文件在测试与生产环境下外部引入
        extractCSS: process.env.MODE === 'development' ? false : true,
        // 在开发环境下生成sourcemap
        productionSourceMap: process.env.MODE === 'development' ? true : false,
        // gzip打开
        productionGzip: true,
        // gzip的对象
        productionGzipExtensions: ['js', 'css', 'svg', 'png'],
        // element-ui按需引入
        // ... 此处省略,上面有
    },
}

单行文本与多行文本溢出显示省略号

单行文本

width:300px;    
overflow: hidden;    
text-overflow:ellipsis;    
whitewhite-space: nowrap; 

多行文本

display: -webkit-box;    
-webkit-box-orient: vertical;    
-webkit-line-clamp: 3;    
overflow: hidden;
  1. <nuxt-link to="detail" target="_blank">新标签页打开</nuxt-link>
    
  2. 编程式:

    let routeData = this.$router.resolve({
        path: "/detail/1",
    });
    window.open(routeData.href, '_blank');
    

阻止冒泡事件

event.stopPropagation();

<template>
    <div class="contentItem" @click="toDetail()">
        <div class="sign"><span>现行有效</span></div>
        <div class="download" @click="download($event)"></div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "contentItem",
        methods:{
            download(event){
                // 阻止冒泡
                event.stopPropagation();
                console.log('download')
            },
            toDetail(){
                console.log('detail')
            }
        }
    }
</script>

输入框默认聚焦

this.$refs.searchInput.focus()

mounted() {
    this.$nextTick(() => {
        this.$refs.searchInput.focus()
    });
}

el-input回车提交事件

@keyup.enter.native=”toSearch”

<el-input placeholder="请输入您要检索的关键字" v-model="query" class="input-with-select" ref="searchInput" @keyup.enter.native="toSearch">
</el-input>

自定义表单验证规则

手机号码:

// data()
var isMobile = /^1[3|4|5|6|7|8|9][0-9]\d{8}$/.test(value)
var checkPhone = (rule, value, callback) => {
    if (!value) {return callback(new Error('请输入手机号码'));}
    else {
        if (isMobile.test(value)) {callback();
        } else {return callback(new Error('请输入正确的手机号'));}
    }
};

// rules
mobile: [{validator: checkPhone, trigger: 'blur'}],

验证码:

// data()
var isVerCode = /^\d{6}$/
var checkCode = (rule, value, callback) =>{
    if(!value){return callback(new Error('请输入6位验证码'))}
    else {
        if (isVerCode.test(value)) {callback();
        } else {return callback(new Error('请输入正确的验证码'));}
    }
};
// rules
verificationCode: [{validator: checkCode, trigger: 'blur'}],

密码: 最少6位、两次输入密码一致

// data()
var checkPassword = (rule, value, callback) =>{
    if(this.register.password != ''){
        if(this.register.password.length >= 6){
            if(this.register.confirmPassword == this.register.password){
                callback();
            }else{
                return callback(new Error('两次密码不一致'));
            }
        }else{
            return callback(new Error('密码长度6位以上'));
        }
    }else {
        return callback(new Error('密码不能为空'));
    }
};
var checkConfirmPassword = (rule, value, callback) =>{
    if(this.register.confirmPassword != ''){
        if(this.register.confirmPassword.length >= 6){
            if(this.register.confirmPassword == this.register.password){
                callback();
            }else{
                return callback(new Error('两次密码不一致'));
            }
        }else{
            return callback(new Error('密码长度6位以上'));
        }
    }else {
        return callback(new Error('密码不能为空'));
    }
};

// form
register:{
    password:'',
    confirmPassword: ''
},


// rules
password: [{ required: true, message: '请输入注册密码', trigger: 'blur'},{validator: checkPassword, trigger: 'change'}],
                    confirmPassword: [{ required: true, message: '请确认注册密码', trigger: 'blur'},{validator: checkConfirmPassword, trigger: 'change'}],

v-if时记得加 :key

// demo
// 我遇到的是:不加key,dom可以切换成功,但是表单验证呀、绑定的数据不能切换;
<template>
    <div v-if="loginType === 'Password'" :key="loginType">

    </div>

    <div v-if="loginType === 'Code'" :key="loginType">

    </div>

    <button @click="changeLoginType('Password')">切换为密码登录<button>
    <button @click="changeLoginType('Code')">切换为验证码登录<button>
</template>

<script>
    export defalt {
            data(){
                return{
                    loginType: Password
                }
            }

        methods:{
            changeLoginType(loginType){
                this.loginType = loginType
            }
        }
    }
</script>

部署

我的部署环境是 ubuntu+nginx+node14.x+pm2

  1. 服务器安装环境

    安装Nginx

    sudo apt-get install nginx
    

    安装node

    参考:ubuntu系统下安装/卸载最新版Node、NPM

    安装pm2

    npm i pm2 -g
    
  2. 编写部署配置文件:配置程序以集群方式部署

    在程序根路径下新建文件: ~/ecosystem.config.js

    module.exports = {
        apps: [
            {
                name: 'lawlawing',                // 程序名称,自己修改
                exec_mode: 'cluster',            // 模式为集群模式,更多参考pm2文档
                instances: 'max', // Or a number of instances
                script: './node_modules/nuxt/bin/nuxt.js',
                args: 'start',
                env: {
                    "PORT": 9363,                // 启动端口
                    "HOST": "0.0.0.0"
                },
                max_memory_restart: "1000M",        // 最大内存重启
                autorestart: true                    // 是否自动重启
            }
        ]
    }
    
  3. 打包,上传到服务器

    打包

    npm run build
    

    上传服务器

    将打包后的: .nuxt,static,ecosystem.config.js,nuxt.config.js,package.json五个文件/夹 上传到服务器中的程序目录就可以了。

  4. 下载依赖, pm2运行程序

    在刚才上传程序文件的文件下,安装依赖

    npm i
    

    安装完依赖后, 使用pm2启动程序

    pm2 start
    

    两步都是在程序文件的根目录下进行

  5. nginx转发配置

    // 切换到ngxin目录并编辑(我这里直接在nginx.config中添加了,也可以在sites-enabled中添加)
    cd /etc/nginx 
    vim nginx.config
    

    配置

    map $sent_http_content_type $expires {
        "text/html"                 epoch;
        "text/html; charset=utf-8"  epoch;
        default                     off;
    }
    
    server {
        listen 80;
        server_name domain.com;
    
        location / {
            expires $expires;
            proxy_redirect                      off;
            proxy_set_header Host               $host;
            proxy_set_header X-Real-IP          $remote_addr;
            proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto  $scheme;
            proxy_read_timeout          1m;
            proxy_connect_timeout       1m;
                // 因为我在第二步配置文件中写的是以9363端口启动程序,所以这里转发9363
            proxy_pass                          http://127.0.0.1:9363;
        }
    
    }
    

    服务器安全组记得开80/443端口

使用Tinymce编辑器

起因

Nuxt.js是一个服务端渲染框架。在一开始项目中使用的是quill.js编辑器,因其支持服务端渲染,但后期其功能过于少,故选择了非常强大的tinymce编辑器。

正文

npm i @tinymce/tinymce-vue tinymce  -s

template

<template>
    <client-only>
        <editor id="tinymce" v-model="contents" :init="init"></editor>
     </client-only>
</template>

script

    import editor from '@tinymce/tinymce-vue';
    import toolbar from "./toobar";        // toolbar
    import plugins from "./plugins";    // plugins
    import {imageHost} from "../../../plugins/host";

    let tinymce;
    // 在客户端环境下引入
    if (process.client) {
        tinymce = require('tinymce/tinymce');
        require('tinymce/themes/silver');
        require('tinymce/icons/default');
        require('tinymce/plugins/image');// 插入上传图片插件
        require('tinymce/plugins/media');// 插入视频插件
        require('tinymce/plugins/table');// 插入表格插件
        require('tinymce/plugins/lists');// 列表插件
        require('tinymce/plugins/link');//超链接插件
        require('tinymce/plugins/wordcount');// 字数统计插件
        // require('tinymce/plugins/emoticons');// 插入表情插件
        require('tinymce/plugins/fullscreen');
        require('tinymce/plugins/code');
        require('tinymce/plugins/paste');
        require('tinymce/plugins/advlist');
        require('tinymce/plugins/autolink');
        require('tinymce/plugins/autosave');
        require('tinymce/plugins/codesample');
        require('tinymce/plugins/colorpicker');
        require('tinymce/plugins/contextmenu');
        require('tinymce/plugins/directionality');
        require('tinymce/plugins/nonbreaking');
        require('tinymce/plugins/insertdatetime');
        require('tinymce/plugins/preview');
        require('tinymce/plugins/print');
        require('tinymce/plugins/save');
        require('tinymce/plugins/searchreplace');
        require('tinymce/plugins/spellchecker');
        require('tinymce/plugins/tabfocus');
        require('tinymce/plugins/table');
        require('tinymce/plugins/template');
        require('tinymce/plugins/textcolor');
        require('tinymce/plugins/visualblocks');
        require('tinymce/plugins/textpattern');
        require('tinymce/plugins/visualchars');
        require('tinymce/plugins/wordcount');
        require('tinymce/plugins/anchor');
        require('tinymce/plugins/hr');
        require('tinymce/plugins/link');
        require('tinymce/plugins/noneditable');
        require('tinymce/plugins/pagebreak');
    }
    export default {
        name: "tinymce-editor",
        components:{editor},
        // 接收引用此组件的值
        props: ['modifyContent'],
        data() {
            return {
                // 图片地址前缀
                imageHost:imageHost(),
                //初始化配置
                init: {
                    language_url: '/tinymce/langs/zh_CN.js',// 语言包的路径
                    language: 'zh_CN',//语言
                    skin_url: '/tinymce/skins/ui/oxide',// skin路径
                    height: 800,//编辑器高度
                    branding: false,//是否禁用“Powered by TinyMCE”
                    menubar: true,//顶部菜单栏显示
                    object_resizing: false,// 是否禁用表格图片大小调整
                    autosave_ask_before_unload:false,    // 去除关闭/刷新网页时弹出对话框
                    plugins: plugins,
                    toolbar: toolbar,
                    body_class: "panel-body ",
                    end_container_on_empty_block: true,
                    powerpaste_word_import: "clean",
                    code_dialog_height: 450,
                    code_dialog_width: 1000,
                    advlist_bullet_styles: "square",
                    advlist_number_styles: "default",
                    imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
                    // 自定义字体集
                    theme_advanced_fonts:
                        "宋体=宋体;微软雅黑=微软雅黑;新宋体=新宋体;Courier New=courier new,courier,monospace;AkrutiKndPadmini=Akpdmi-n",
                    // 自定义字号
                    fontsize_formats: "8pt 10pt 12pt 14pt 18pt 24pt 36pt",
                    content_css:'/tinymce/skins/content/default/content.css',
                    paste_data_images: true, // 允许粘贴图像
                    // 自定义字体
                    font_formats: `
            微软雅黑=微软雅黑;
            宋体=宋体;
            黑体=黑体;
            仿宋=仿宋;
            楷体=楷体;
            隶书=隶书;
            幼圆=幼圆;
            思源黑体CN=思源黑体CN;
            Andale Mono=andale mono,times;
            Arial=arial, helvetica,
            sans-serif;
            Arial Black=arial black, avant garde;
            Book Antiqua=book antiqua,palatino;
            Comic Sans MS=comic sans ms,sans-serif;
            Courier New=courier new,courier;
            Georgia=georgia,palatino;
            Helvetica=helvetica;
            Impact=impact,chicago;
            Symbol=symbol;
            Tahoma=tahoma,arial,helvetica,sans-serif;
            Terminal=terminal,monaco;
            Times New Roman=times new roman,times;
            Trebuchet MS=trebuchet ms,geneva;
            Verdana=verdana,geneva;
            Webdings=webdings;
            Wingdings=wingdings,zapf dingbats`,
                    content_style: `
            *                         { padding:0; margin:0; }
            html, body                { height:100%; }
            img                       { max-width:100%; display:block;height:auto; }
            a                         { text-decoration: none; }
            iframe                    { width: 100%; }
            p                         { line-height:1.6; margin: 0px; font-size: 16px; font-family:微软雅黑}
            table                     { word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; }
            .mce-object-iframe        { width:100%; box-sizing:border-box; margin:0; padding:0; }
            ul,ol                     { list-style-position:inside; }
            body, td, pre {
            font-family: Verdana, Arial, Helvetica, sans-serif;
            font-size: 12px;
}
          `,
                    style_formats: [
                        {
                            title: "首行缩进",
                            block: "p",
                            styles: { "text-indent": "2em" }
                        },
                        {
                            title: "行高",
                            items: [
                                { title: "1", styles: { "line-height": "1" }, inline: "span" },
                                {
                                    title: "1.5",
                                    styles: { "line-height": "1.5" },
                                    inline: "span"
                                },
                                { title: "2", styles: { "line-height": "2" }, inline: "span" },
                                {
                                    title: "2.5",
                                    styles: { "line-height": "2.5" },
                                    inline: "span"
                                },
                                { title: "3", styles: { "line-height": "3" }, inline: "span" }
                            ]
                        }
                    ],
                    default_link_target: "_blank",
                    link_title: false,
                    convert_urls: false, // 图片上传路径为绝对路径
                    remove_script_host: false,
                    paste_word_valid_elements: "*[*]",
                    paste_convert_word_fake_lists: false,
                    paste_webkit_styles: "all",
                    paste_merge_formats: true,
                    nonbreaking_force_tab: false,
                    paste_auto_cleanup_on_paste: false,
                    forced_root_block: "p",
                    force_p_newlines: true,
                    importcss_append: true,
                    tabfocus_elements: ":prev,:next",
                    // 本地图片上传配置
                    images_upload_handler: (blobInfo, success, failure) => {
                        let params = new FormData();
                        params.append("image", blobInfo.blob());
                        this.$axios.post("/upload_image?random="+Math.random(), params)
                            .then(res=>{
                                if(res.data.code == 0){
                                    success( this.imageHost + res.data.data)
                                } else {
                                    failure("上传失败")
                                }
                            })
                            .catch(()=>{
                                failure("上传失败")
                            })
                    },
                    setup: (editor) => {
                        // 监听编辑器失焦事件
                        var _this = this;
                        editor.on('blur', function () {
                            _this.$emit('saveDraft')
                        })
                    },
                },
                // 绑定的内容
                contents: this.modifyContent
            }
        },
        mounted(){ // 用process.client判断一下环境再执行
            this.$nextTick(()=>{
                if (process.client) {
                    window.tinymce.init({});
                }
            })
        },
        watch: {
            modifyContent(newValue) {
                this.contents = newValue
            },
            contents(newValue) {
                this.$emit('writeContent', newValue);
            }
        }
    }
<template>
    <div class="postEditor">
        <tinymceEditor @writeContent="getWriteContent" v-if="isShowEdit"  @saveDraft="saveDraft"/>
    </div>
</template>

<script>
    import tinymceEditor from "../../components/public/tinymceEditor/tinymce-editor"
    
     export default {
        name: "write",
        middleware: 'auth',        // 登录验证中间件
        components: {tinymceEditor},
        data() {
            return {
                isShowEdit:true,
                articleContent: '',
            }
        },
        method:{
            // 同步编辑器数据
            getWriteContent(content) {
                this.articleContent = content;
            },
            // 保存草稿
            saveDraft(){
                console.log("保存草稿")
            }
        }

至此,以上配置已经满足我的使用了。
最核心的就是:nuxt.js是服务端渲染。 tinymce编辑器在客户端渲染就好了,和正常vue内引用使用一样

在middleware(中间件)中实现路由鉴权

路由鉴权: 就是判断这个路由当前浏览者是否需要权限访问。
一般我是通过判断cookie中存储的token来判断的。

在middleware文件夹下新建“auth.js”的文件

在当前auth.js文件内判断cookie中是否包含token字段

import getCookie from '~/utils/getCookie'

export default function ({route, req, res, redirect}) {
  let isClient = process.client;
  let isServer = process.server;
  let redirectURL = '/sign';
  var token, path;

  // 在服务端
  if (isServer) {
      // 获取服务端cookie
    let cookies = getCookie.getcookiesInServer(req)
    // 获取当前服务端cookie中是否含有token字段
    token = cookies.token ? cookies.token : ''
  }
  // 在客户端
  if (isClient) {
      // 获取客户端(本地)cookie中的token字段
    token = getCookie.getcookiesInClient('token')
  }

  // 判断是否获取到token
  // 未获取到,重定向到登陆页面
  if (!token) {
    redirect(redirectURL)
  }
}

新建获取cookie的文件

/uitls/getCookie.js

首先:下载js-cookie
npm i js-cookie -s

import Cookie from 'js-cookie'

export default {
  //获取服务端cookie
  getcookiesInServer:function (req) {
    let service_cookie = {};
    req && req.headers.cookie && req.headers.cookie.split(';').forEach(function (val) {
      let parts = val.split('=');
      service_cookie[parts[0].trim()] = (parts[1] || '').trim();
    });
    return service_cookie;
  },
  //获取客户端cookie
  getcookiesInClient:function (key) {
    return Cookie.get(key) ? Cookie.get(key) : ''
  }
}

在需要路由鉴权的page页面中使用

比如在 ~/page/index.vue中使用

<script>
    export default {
        name: 'index',
        // auth 为刚才在在middleware文件夹下新建的auth.js文件
        middleware: 'auth',
    }
</script>

npm install js-cookie --save

2.使用

  1. 引用
    import Cookie from 'js-cookie'

  2. 客户端使用

    // 1.获取
    Cookie.get(key)
    // 2.设置
    Cookie.set('name', value, {expires: 过期时间})
    // 3.删除
    Cookie.remove("name")
    // 过期时间设置:
    let seconds = 3600;     // 过期时间 /秒
    let expires = new Date(new Date() * 1 + seconds * 1000);http://blog.zhanghaoran.ren/detail/16)
    

加密解密

在~/plugins下新建 encrytion.js文件

const crypto = require('crypto');

export default {
  aesEncrypt(data, key){
    try{
      var salt =  key.toString() + 'manbanzhen';    // 定义盐值
      const cipher = crypto.createCipher('aes192', salt);   // 采用aes192加密方式
      var crypted = cipher.update(data, 'utf8', 'hex');     // 加密
      crypted += cipher.final('hex');
      return crypted;
    }catch (e) {        // 加捕获是为了在验证失败的时候,直接让用户重新登陆
      alert("检测到存在安全异常,请检查当前网络环境并重新登录!");
      window.location.href = '/login'
    }

    },

  aesDecrypt(encrypted, key) {
    try{
      var salt =  key.toString() + 'manbanzhen';    // 定义盐值
      const decipher = crypto.createDecipher('aes192', salt);   // 解密 aes192
      var decrypted = decipher.update(encrypted, 'hex', 'utf8');    //解密
      decrypted += decipher.final('utf8');
      return decrypted;
    }catch (e) {        // 加捕获是为了在验证失败的时候,直接让用户重新登陆
      // console.log("error*-**--*-")
      alert("检测到存在安全异常,请检查当前网络环境并重新登录!");
      window.location.href = '/login';
    }
    }
}

在需要的地方进行引用:

import encrytion from '~/plugins/encrytion'

// 加密
encrytion.aesEncrypt('待加密的内容''密钥')
// 解密
encrytion.aesDecrypt('待解密的密文''密钥')

使用vue-cropper实现图片裁剪

最近我司一项目,在个人中心页面上需用户上传个性传封面图(类似知乎个人主页上部),用户上传图片大小不一,且封面图大小为1000px*240px;这就需要在用户上传的时候对图片进行合适大小裁剪。

大概思路:
用户上传图片时

  1. 判断图片尺寸大小,宽高不能小于1200px240;小于这个尺寸将无法完美裁剪。
  2. 将图片转化为base64格式,即时回显图片和不浪费服务器资源。

这两步完成后,将进入到vue-cropper的裁剪操作了,具体操作可见文档,我在下面写了我的配置供参考;
vue-cropper地址: https://github.com/xyxiao001/vue-cropper

裁剪操作结束,点击确认,得到裁剪后返回图片对象为blod类型,但服务器只接收file类型,故需转为file类型给服务器;得到服务器图片路径后,替换掉原本图片路径,完成✅

  1. 下载

    npm install vue-cropper
    
  2. 配置

    我使用的是全局配置,此处只写出全局配置

2.1 在~/plugins文件夹下,新建 cropper.js文件;引入vue-cropper

import vueCropper from 'vue-cropper';
import Vue from 'vue';
Vue.use(vueCropper);

2.2 在~/nuxt.config.js文件中,配置插件

export default{
    plugins: [
        { src: '~/plugins/cropper', ssr: false }
    ]
}
  1. 使用
    template
    <div>
     <div>
         <input v-if="status" id="uploadFileInput" type="file" accept="image/png,image/jpeg" >
     </div>
     <div class="cropper-content" v-else>
         <div class="cropper" style="text-align:center">
             <vueCropper
                 ref="cropper"
                 :img="option.img"
                 :outputSize="option.size"
                 :outputType="option.outputType"
                 :info="true"
                 :full="option.full"
                 :canMove="option.canMove"
                 :canMoveBox="option.canMoveBox"
                 :original="option.original"
                 :autoCrop="option.autoCrop"
                 :fixed="option.fixed"
                 :fixedNumber="option.fixedNumber"
                 :centerBox="option.centerBox"
                 :infoTrue="option.infoTrue"
                 :fixedBox="option.fixedBox"
                 :mode="option.mode">
            </vueCropper>
         </div>
         <div class="btnGroup">
             <el-button @click="dialogVisible = false">取 消</el-button>
             <el-button type="primary" @click="finish" :loading="loading">确认</el-button>
         </div>
    </div>
    

style

    .cropper-content{
        width: 100%;
        height: 300px;
    }
    .cropper {
        width: 100%;
        height: 240px;
    }
    .btnGroup{
        float: right;
        margin-top: 10px;
        margin-right: 15px;
    }

script

export default{
    data(){
        status: true,
        option: {
            img: '', // 裁剪图片的地址
            info: true, // 裁剪框的大小信息
            outputSize: 0.8, // 裁剪生成图片的质量
            outputType: 'jpeg', // 裁剪生成图片的格式
            canScale: true, // 图片是否允许滚轮缩放
            autoCrop: true, // 是否默认生成截图框
            autoCropWidth: 1200, // 默认生成截图框宽度
            autoCropHeight: 240, // 默认生成截图框高度
            fixedBox: true, // 固定截图框大小 不允许改变
            fixed: true, // 是否开启截图框宽高固定比例
            fixedNumber: [25, 6], // 截图框的宽高比例
            // full: true, // 是否输出原图比例的截图
            canMoveBox: false, // 截图框能否拖动
            original: false, // 上传图片按照原始比例渲染
            centerBox: true, // 截图框是否被限制在图片里面
            infoTrue: false, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
            mode: 'cover',    // cover  图片铺满容器
         },
    },
    methods:{
      // 触发 input:file
      uploadFile() {
          try{
              this.$refs.upload.querySelector('input').click();
          }catch (e) {}
      }, 
      // 点击上传按钮后的事件
     upload(e) {
         let file = e.target.files[0];
         var self = this;
         this.createReader(file, function (w, h) {
              if(w<1200 || h<240){
                  self.$message.error("请上传宽度大于 1200px,高度大于 240px 的封面图片。");
                  let UFI = document.getElementById("uploadFileInput");
                  UFI.value = '';
              }else{
                 self.file2base64(file)
              }
         })
     } ,

    // 获取图片宽高
   createReader(file, whenReady) {
      var reader = new FileReader;
      reader.onload = function (evt) {
         var image = new Image();
         image.onload = function () {
             var width = this.width;
              var height = this.height;
               if (whenReady) whenReady(width, height);
         };
         image.src = evt.target.result;
      };
      reader.readAsDataURL(file);
   },

    // file类型文件 转base64文件类型
   file2base64(file){
       var reader = new FileReader();
       reader.readAsDataURL(file);
       var self = this;
       reader.onload = function (e) {
         // 图片base64化
         let newUrl = this.result;    //图片路径
         self.$nextTick(() => {
             self.pageImage = newUrl;
             self.option.img = newUrl;
              self.dialogVisible = true
          })
       }
  },

  // 点击确定,这一步是可以拿到处理后的地址, 然后上传到服务器
  finish() {
      this.$refs.cropper.getCropBlob((data) => {
          // 将接收到blod文件对象转为file
          let file = new File([data], 'proFileCover.jpg',{type: data.type,  lastModified: Date.now()});
          let params = new FormData();
          params.append("image", file);
          var self = this;
          self.$axios.post("upload_image", params)
            .then(res=>{
                if(res.data.code == 0){
                  //  显示图片
                  self.pageImage = res.data.data;
                  // 关闭隐藏div,显示图片
                  self.status = false;
                 }  
             })
        }
    },
}

  1. 最后
    通过上述代码操作,就完成了上传图片裁剪的功能。

国际化与本地化(i18n)

更新:2020-11-10

最近我司官网英文版提上了日程,故更新下以前认识浅薄及错误的地方。

欢迎访问我司官网;https://www.qidufanyi.com

有翻译业务也可联系我哟~: 1441576268@qq.com

  1. 安装 vue-i18n
npm install vue-i18n --save
  1. 在 ~/plugins文件夹下新建 i18n.js, 并键入以下代码:
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

export default ({ app, store }) => {
    app.i18n = new VueI18n({
        locale: store.state.locale,
        fallbackLocale: 'zh-CN',  // 我这里默认语言为中文
        messages: {
            'en-US': require('@/locales/en-US.json'),
            'zh-CN': require('@/locales/zh-CN.json')
        }
    })

    app.i18n.path = (link) => {
        // 如果是默认语言,就省略
        if (app.i18n.locale === app.i18n.fallbackLocale) {
            return `/${link}`
        }
        return `/${link}?lang=/${app.i18n.locale}`
    }
}
  1. 在vuex中保存当前语言状态
    在 ~/store文件夹下 新建 index.js文件,键入以下代码:
export const state = () => ({
    locales: ['en-US', 'zh-CN'],
    locale: 'zh-CN'
})

export const mutations = {
    // 此处为设置locale
    SET_LANG(state, locale) {
        if (state.locales.indexOf(locale) !== -1) {
            state.locale = locale
        }
    }
}
  1. 在~/middleware文件夹下新建i18n.js文件用来控制语言切换
import utils from "../utils/utils";
export default function ({
                             isHMR, app, store, route, redirect, query,req
                         }) {

    // 从服务端请求头cookie中获取语种
    let locale;
    locale= utils.getcookiesInServer(req).lang
    // cookie中没有的情况下,获取路由参数中的语种,再没有就默认为中文
    if(!locale){
        locale = query.lang || 'zh-CN';
    }
    // 设置语种
    store.commit('SET_LANG', locale); // set store
    app.i18n.locale = store.state.locale;

    // 跳转该去的地方, isHMR我还没看是什么。
    if (isHMR) {
        return;
    }
    else if (!query.lang) {
        return redirect(route.fullPath);
    }
}
  1. 修改nuxt.config.js文件配置如下:
  router: {
    middleware: 'i18n'
  },
  plugins: [
    '@/plugins/i18n.js',
  ],
  1. 创建本地语言包
    新建 locales文件夹,创建en-US.json, zh-CN.json两个文件

    要保证语种之间键为一致的

// zh-CN.json
{
    "about": {
        "title": "关于",
    }
}

en-US.json

{

    "about": {
        "title": "About",
    }
}
  1. page文件夹下创建文件
    page/index.vue
<template>
<div>
    <h1>{{$t('about.title')}}</h1>
    <button @click="changeLang('zh-CN')" v-if="$i18n.local!='zh-CN'">中文</button>
    <button @click="changeLang('en-US')" v-if="$i18n.local!='en-US'">英文</button>
</div>
</template>

<script>
export default {
  components: {},
  methods:{
     changeLang(l) {
         this.$i18n.locale = l;
         // 此时语言就会显示为切换的语言
    },
  }
};
</script>

欢迎访问旗渡法律翻译来查看最早效果: https://www.qidufanyi.com

另:承接各种翻译业务,包括网站翻译。