最近在开发我司一项目,用的是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)
nuxt.config.js
中:
import { resolve } from 'path'
module.exports = {
alias: {
'img': resolve(__dirname, './assets/images'),
'css': resolve(__dirname, './assets/css'),
},
}
- 在组件和页面中使用:
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按需引入
- 下载element-ui与babel
npm i element-ui -S
// 用于按需加载
npm i babel-plugin-component -D
- 在
~/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)
}
- 在
nuxt.config.js
中引入element-ui.js
:
// nuxt.config.js
plugins: [
'~/plugins/element-ui'
],
- 在
nuxt.cofnig.js
中配置按需加载
// nuxt.confg.js
build: {
vendor: ['element-ui'],
babel:{
plugins: [
["component",
{"libraryName": "element-ui", "styleLibraryName": "theme-chalk"}
]
]
},
}
nuxt-cookie
安装
npm i cookie-universal-nuxt -S
在
nuxt.config.js
中引入:modules: [ 'cookie-universal-nuxt', ],
在客户端中使用:
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});
在服务端中使用:
应该是服务端中,比如在下面的axios中使用
nuxt-axios
安装
npm i @nuxtjs/axios -S
配置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) // 返回接口返回的错误信息 }) }
在
nuxt.config.js
中引入modules: [ '@nuxtjs/axios' ], plugins: [ '~/plugins/axios' ],
使用
// 页面中: // 组件中:
环境变量
- 下载安装
npm i cross-env -D
- 配置启动环境变量
// ~/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"
},
nuxt.config.js
中配置env:
export default {
env:{
MODE: process.env.MODE
},
}
创建在各个环境下的接口地址的文件:
// ~/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" } }
创建返回当前环境接口地址的文件:
// ~/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;
使用:
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;
nuxt-link 在新标签页打开
<nuxt-link to="detail" target="_blank">新标签页打开</nuxt-link>
编程式:
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
服务器安装环境
安装Nginx
sudo apt-get install nginx
安装node
安装pm2
npm i pm2 -g
编写部署配置文件:配置程序以集群方式部署
在程序根路径下新建文件:
~/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 // 是否自动重启 } ] }
打包,上传到服务器
打包
npm run build
上传服务器
将打包后的:
.nuxt
,static
,ecosystem.config.js
,nuxt.config.js
,package.json
五个文件/夹 上传到服务器中的程序目录就可以了。下载依赖, pm2运行程序
在刚才上传程序文件的文件下,安装依赖
npm i
安装完依赖后, 使用pm2启动程序
pm2 start
两步都是在程序文件的根目录下进行
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编辑器。
正文
- 安装tinymce
npm i @tinymce/tinymce-vue tinymce -s
在~/static/目录下创建tinymce文件夹,下载完tinymce后在node_modules 中找到 tinymce/skins目录,将其复制到static/tinymce目录。
下载中文语言包。
下载解压后,把langs文件夹放到~/static/tinymce/下。
在~/components/下创建 tinymceEditor.vue文件。
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>
js-cookie 扩展
1.安装js-cookie
npm install js-cookie --save
2.使用
引用
import Cookie from 'js-cookie'
客户端使用
// 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;这就需要在用户上传的时候对图片进行合适大小裁剪。
大概思路:
用户上传图片时
- 判断图片尺寸大小,宽高不能小于1200px240;小于这个尺寸将无法完美裁剪。
- 将图片转化为base64格式,即时回显图片和不浪费服务器资源。
这两步完成后,将进入到vue-cropper的裁剪操作了,具体操作可见文档,我在下面写了我的配置供参考;
vue-cropper地址: https://github.com/xyxiao001/vue-cropper
裁剪操作结束,点击确认,得到裁剪后返回图片对象为blod类型,但服务器只接收file类型,故需转为file类型给服务器;得到服务器图片路径后,替换掉原本图片路径,完成✅
下载
npm install vue-cropper
配置
我使用的是全局配置,此处只写出全局配置
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 }
]
}
- 使用
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;
}
})
}
},
}
- 最后
通过上述代码操作,就完成了上传图片裁剪的功能。
国际化与本地化(i18n)
更新:2020-11-10
最近我司官网英文版提上了日程,故更新下以前认识浅薄及错误的地方。
欢迎访问我司官网;https://www.qidufanyi.com
有翻译业务也可联系我哟~: 1441576268@qq.com
- 安装 vue-i18n
npm install vue-i18n --save
- 在 ~/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}`
}
}
- 在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
}
}
}
- 在~/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);
}
}
- 修改nuxt.config.js文件配置如下:
router: {
middleware: 'i18n'
},
plugins: [
'@/plugins/i18n.js',
],
创建本地语言包
新建 locales文件夹,创建en-US.json, zh-CN.json两个文件要保证语种之间键为一致的
// zh-CN.json
{
"about": {
"title": "关于",
}
}
en-US.json
{
"about": {
"title": "About",
}
}
- 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
另:承接各种翻译业务,包括网站翻译。