最近项目一个接着一个 好久没有总结了 记录以下问题的解决方式
- vue + springboot 开发环境跨域
- vue + nginx 生产环境部署
- vue + axios 配置
- vue + element-ui 修改默认样式
- vue + echarts 实现散点路径地图
- vue + filesaver + xlsx 实现前端导出报表
- vue 拖动生成图表思路
- vue 大屏适配方案
- vue 多设备适配方案
vue + springboot 开发环境跨域
之前用的解决方式一直是后台加注解
@CrossOrigin
这次我在配置文件中增加了代理
vue.config.js
module.exports = {
devServer: {
// 设置代理
proxy: {
// 代理所有/api/cleannet开头的请求 其中api为自定义的虚拟前缀
// 虚拟前缀的作用是解决代理地址和路由地址冲突的情况
'/api/cleannet': {
// 目标地址 手动?打码
target: 'http://10.?.?.?:9876/',
// 将主机标头的原点更改为目标URL
changeOrigin: true,
// 将自定义的虚拟前缀替换掉 保证真实请求无误
pathRewrite: {
'^/api': '/'
}
}
}
}
在.vue
中的请求接口的代码如下
mounted() {
this.getData();
},
methods: {
getData: function() {
this.$axios
.post("/api/cleannet/foo/foo/foo", {
pagesize: this.pagesize,
currentpage: this.currentpage
})
.then(response => {
// do next
});
}
}
注意
1.api
是我自定义的虚拟前缀 真实的接口地址没有这一层 需要覆盖掉
2.api
存在的意义在于解决接口地址和页面地址冲突的情况
vue + nginx 生产环境部署
最开始的时候直接将打包好的dist
丢到nginx
的html
目录下
带来的问题是页面空白
然后采用如下解决方式 将本来为history
模式的mode
修改为默认的hash
或者干脆去掉
再将publicPath
从/
修改为./
这样就带来另一个一个问题
那就是页面的路径会带上丑丑的#
如一个开发环境的登录页面http://localhost:8080/login
带了生产环境就会变成http://10.?.?.?:8080/dist/index.html/#/login
这样看起来很奇怪而且增加了用户的录入成本 因此这次我先在本地试了nginx
的部署方式
mac 安装 nginx
首先要保证你的mac上有homebrew
没有的话讲如下代码拷贝至终端执行
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
如果已经有了homebrew
先更新一下
brew update
我在更新的时候报错了
xcrun: error: invalid active developer path
(/Library/Developer/CommandLineTools), missing xcrun at:
/Library/Developer/CommandLineTools/usr/bin/xcrun
可以看出来是x-code
出了问题 原因大概是我更新了MacOS
到Catalina
总之 在终端执行以下代码即可修复
xcode-select --install
更新好homebrew
之后 开始安装nginx
brew install nginx
nginx
常用命令如下
// 启动ng
nginx
// 停止ng
nginx -s stop
// 重启ng
nginx -s reload
通常我们需要用到的两个目录位置如下
前端dist
存放目录
/usr/local/var/www/
nginx配置文件nginx.conf
位置
/usr/local/etc/nginx/
然后我们修改nginx.conf
的配置如下 此时vue
的所有配置未修改
location / {
alias /usr/local/var/www/dist/;
index index.html;
}
此时访问根路径结果如下
而访问我们的工程路径login
结果如下
依照官网的解决方案在nginx.conf
的根目录下添加如下配置
try_files $uri $uri/ /index.html;
变成如下配置
location / {
alias /usr/local/var/www/dist/;
index index.html;
try_files $uri $uri/ /index.html;
}
再访问http://localhost:9989/login
就可以了
可以看到我们已经实现了去掉#
接下来要解决另一个问题 之前我们为了解决开发环境联调的问题添加了自定义的接口前缀api
那么如何在正式环境的nginx
配置里将它规避掉呢 添加配置如下
location /api {
rewrite /api/(.*)$ /$1 break;
proxy_pass http://10.?.?.?:9876;
}
这样就会将api
过滤掉直接在我们的ip
和端口后拼上真实的请求地址
在部署生产的时候发现更目录被占用 那么就要如何解决将vue
项目部署到非根目录的问题
首先尝试在nginx.conf
配置多个server
server {
listen 7789;
server_name localhost;
location / {
alias /usr/otherProgram;
index index.html index.htm;
}
server {
listen 9989;
server_name localhost;
location / {
alias /usr/myProgram;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
这个方法是可行的 但是配置多个server
意味着多个端口 被告知只打通一个端口 放弃
尝试自定义虚拟路径 配置如下
location @rewrites {
rewrite ^/(newcleannet)/(.+)$ /$1/index.html last;
}
location ^~/newcleannet/ {
alias /usr/local/var/www/dist/;
index index.html index.htm;
try_files $uri $uri/ @rewrites;
}
访问页面显示空白
这时去修改vue项目的配置
router.js
mode: 'history',
base: '/newcleannet/',
// base: process.env.BASE_URL,
vue.config.js
// 生产环境是否生成source map文件 false可以防止反编译和加快构建速度
productionSourceMap: false,
publicPath: '/newcleannet/',
// publicPath: '/',
重新打包部署再测试 可行
vue + axios 配置
之前的项目没有对axios进行过多配置 这次接口有诸多限制 因此简单配置如下
axiosConfig.js
import axios from 'axios'
import qs from 'qs' // 序列化表单
import {
Message,
Loading
} from 'element-ui'
import router from '../router'
// 请求的超时时间
axios.defaults.timeout = 5 * 1000
// 配置cookie
// axios.defaults.withCredentials = true
// 配置请求头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
// 配置接口地址
// axios.defaults.baseURL = ''
let loadingInstance
// 请求拦截器 post传参序列化
axios.interceptors.request.use(
config => {
loadingInstance = Loading.service({
lock: true,
text: '拼命加载数据中',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.6)'
})
if (config.method === 'post') {
config.data = qs.stringify(config.data)
}
return config
},
err => {
loadingInstance.close()
Message.error('请求错误')
return Promise.reject(err)
}
)
// 返回拦截器 返回状态判断
axios.interceptors.response.use(
res => {
if (res.data.STATUS === '0000') {
loadingInstance.close()
return res.data
} else {
loadingInstance.close()
Message.error(res.data.MESSAGE)
}
},
err => {
loadingInstance.close()
Message.error('请求失败')
if (err.response && err.response.data.STATUS === '401') {
Message.error(err.response.data.MESSAGE)
router.push('/login')
}
return Promise.reject(err)
}
)
main.js
import axios from 'axios'
import './config/axiosConfig' // axios请求配置
vue + element-ui 修改默认样式
需要修改element-ui
的默认样式 定制主题无法完全满足 修改的时候以前是通过!important
后缀覆盖然后不写scoped
这样带来的问题是 页面路由之后很容造成样式互相污染 这次采用如下解决方式
Login.vue
<template>
...
<el-input
v-model="form.name"
class="custom-input"
@keyup.enter.native="signIn"
>
</el-input>
...
</template>
<style scoped>
.custom-input /deep/ .el-input__inner {
width: 220px;
background: none;
color: rgba(255, 255, 255, 87%);
}
.custom-input /deep/ .el-input__inner:focus {
border-color: #bb86fc;
}
.custom-input /deep/ .el-input__suffix {
display: none;
}
</style>
主要在需要修改的样式父层写自定义的样式如.custom-input
然后写/deep/
用来实现样式穿透 最后加上scoped
保证样式的作用域只在当前文件生效
vue + echarts 实现散点路径地图
之前用的是通过ak
在线加载百度地图的方式 但是这次主要针对内网用户 所以使用了离线地图的方式 直接上代码
Dashboard.vue
<template>
...
<HighRiskMap />
...
</template>
<script>
import HighRiskMap from "@/components/Charts3/HighRiskMap.vue";
export default {
components: {
HighRiskMap
}
}
</script>
HighRiskMap.vue
<template>
<div id="china_map"></div>
</template>
<script>
let echarts = require("echarts/lib/echarts"); // 主模块
require("echarts/lib/chart/scatter"); // 散点图
require("echarts/lib/chart/effectScatter"); // 散点图放大
require("echarts/lib/chart/map"); // 地图
require("echarts/lib/component/legend"); // 图例
require("echarts/lib/component/tooltip"); // 提示框
require("echarts/lib/component/geo"); // 地图geo
require("echarts/map/js/china"); // 中国地图JS文件
export default {
data() {
return {
geoCoordMap: {},
data: [],
lines: []
};
},
mounted() {
this.getData();
},
methods: {
getData() {
this.$axios.get("/api/?/?/?").then(response => {
if (response) {
this.geoCoordMap = response.geoCoord;
this.data = response.dataHover;
this.lines = response.lines;
this._render_de_map();
} else {
this.$message.warning("没有找到匹配的数据");
}
});
},
convertData: function(data) {
let res = [];
for (let i = 0; i < data.length; i++) {
let geoCoord = this.geoCoordMap[data[i].name];
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value)
});
}
}
return res;
},
_render_de_map: function() {
let chinaMap = echarts.init(document.getElementById("china_map"));
chinaMap.setOption({
backgroundColor: "transparent",
tooltip: {
trigger: "item",
formatter: function(params) {
return params.name + " : " + params.value[2];
}
},
legend: {
orient: "vertical",
left: "left",
top: "top",
data: ["高危地区前五", "高危地区"],
textStyle: {
fontSize: this.GLOBAL.fontSize(0.14),
color: "rgba(255,255,255,60%)"
}
},
geo: {
map: "china",
zoom: 1.5,
roam: true,
label: {
emphasis: {
show: false
}
},
itemStyle: {
// 地图背景色
normal: {
areaColor: "rgba(24,255,255,38%)",
borderColor: "rgba(24,255,255,60%)",
shadowBlur: 12,
shadowColor: "#1a237e"
},
// 悬浮时
emphasis: {
areaColor: "rgba(24,255,255,87%)"
}
}
},
series: [
{
type: "lines",
coordinateSystem: "geo",
polyline: true,
data: this.lines,
silent: true,
lineStyle: {
normal: {
color: "#18ffff",
opacity: 0.2,
width: 2
}
},
progressiveThreshold: 500,
progressive: 200
},
{
type: "lines",
coordinateSystem: "geo",
polyline: true,
data: this.lines,
lineStyle: {
normal: {
color: "#80deea",
width: 0
}
},
effect: {
constantSpeed: 30,
show: true,
trailLength: 0.2,
symbolSize: 2
},
zlevel: 1
},
{
name: "高危地区",
type: "scatter",
coordinateSystem: "geo",
data: this.convertData(this.data),
symbolSize: this.GLOBAL.fontSize(0.12),
label: {
normal: {
show: false,
fontSize: this.GLOBAL.fontSize(0.12)
},
emphasis: {
show: false
}
},
itemStyle: {
normal: {
color: "#ffab40",
shadowBlur: 6,
shadowColor: "#ff6f00"
}
}
},
{
name: "高危地区前五",
type: "effectScatter",
coordinateSystem: "geo",
data: this.convertData(
this.data
.sort(function(a, b) {
return b.value - a.value;
})
.slice(0, 5)
),
symbolSize: this.GLOBAL.fontSize(0.12),
showEffectOn: "render",
rippleEffect: {
brushType: "stroke"
},
hoverAnimation: true,
label: {
normal: {
formatter: "{b}",
position: "right",
show: true,
fontSize: this.GLOBAL.fontSize(0.12)
}
},
itemStyle: {
normal: {
color: "#ff4081",
shadowBlur: 6,
shadowColor: "#880e4f"
}
},
zlevel: 1
}
]
});
}
}
};
</script>
<style scoped>
#china_map {
width: 100%;
height: 320px;
}
</style>
请注意 对于
echarts
的字体无法根据屏幕大小变化导致在大屏里显示过小的问题 上面代码也给出了解决方法
HighRiskMap.vue
...
fontSize: this.GLOBAL.fontSize(0.12)
...
Global.vue
// 大屏字体适配
const fontSize = function(res) {
let clientWidth =
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;
if (!clientWidth) return;
let fontSize = 100 * (clientWidth / 1440);
return res * fontSize;
};
这样就实现了离线的地图的散点和路径效果
vue + FileSaver + XLSX 实现前端导出报表
这个方法可以将当前页面的表格数据导出为Excel
安装依赖并引入依赖
yarn add file-saver xlsx
ReplyTracking.vue
<el-tooltip content="导出当前" placement="top">
<el-button
class="dark-sec-btn"
icon="jw-icon-daochu3"
circle
@click="exportTable">
</el-button>
</el-tooltip>
...
<script>
export default {
methods: {
exportTable: function() {
this.GLOBAL.exportTable(this.$router.history.current.meta.name);
}
}
}
</script>
Global.vue
<script>
import FileSaver from "file-saver";
import XLSX from "xlsx";
// 前端导出当前表格数据
const exportTable = function(params) {
// 因为element-ui的表格的fixed属性导致多出一个table 会下载重复内容 这里删除掉
let table = document.querySelector("#tableForExport").cloneNode(true);
if (table.querySelector(".el-table__fixed-right")) {
table.removeChild(table.querySelector(".el-table__fixed-right"));
}
if (table.querySelector(".el-table__fixed")) {
table.removeChild(table.querySelector(".el-table__fixed"));
}
let wb = XLSX.utils.table_to_book(table);
let wbout = XLSX.write(wb, {
bookType: "xlsx",
bookSST: true,
type: "array"
});
try {
FileSaver.saveAs(
new Blob([wbout], { type: "application/octet-stream" }),
`${params}.xlsx`
);
} catch (e) {
if (typeof console !== "undefined") console.log(e, wbout);
}
return wbout;
};
</script>
请注意 对于有
el-table
固定列的情况 就会在生成的Excel
里有完全重复的表格数据 上面代码也给出了解决方式
let table = document.querySelector("#tableForExport").cloneNode(true);
if (table.querySelector(".el-table__fixed-right")) {
table.removeChild(table.querySelector(".el-table__fixed-right"));
}
if (table.querySelector(".el-table__fixed")) {
table.removeChild(table.querySelector(".el-table__fixed"));
}
vue 拖动生成图表思路
主要用到了这个组件
import VueDraggableResizable from "vue-draggable-resizable";
或者
import draggable from "vuedraggable";
暂时还不够完美 后续补充
vue 大屏适配方案
主要是通过大屏页面引入rem.js
再配合postcss-px2rem
组件实现 .vue
不需要自己计算rem
的值 依然写px
rem.js
// rem等比适配配置文件
// 基准大小
const baseSize = 16
// 设置 rem 函数
function setRem () {
// 当前页面宽度相对于 1440宽的缩放比例 可根据自己需要修改
if (document.documentElement.clientWidth >== 1440) {
const scale = document.documentElement.clientWidth / 1440
// 设置页面根节点字体大小(“Math.min(scale, 2)” 指最高放大比例为2 可调整)
document.documentElement.style.fontSize = baseSize * Math.min(scale, 3) + 'px'
}
}
// 初始化
setRem()
// 改变窗口大小时重新设置 rem
window.onresize = function () {
setRem()
}
vue.config.js
// 引入等比适配插件
const px2rem = require('postcss-px2rem')
// 配置基本大小
const postcss = px2rem({
// 基准大小 baseSize,需要和rem.js中相同
remUnit: 16
})
module.exports = {
css: {
loaderOptions: {
postcss: {
plugins: [
postcss
]
}
}
}
}
暂时还不够完美 后续补充
vue 多设备适配方案
当然是引入大名鼎鼎的手淘适配组件实现 也是正常写px
设计稿默认540
还是720
我忘了
main.js
import 'lib-flexible'
vue 大屏分辨率解决方案新思路
app.vue
<style lang="scss">
$bgc1: #4b5790;
$bgc2: #252f56;
html,body {
margin: 0;
overflow: hidden;
height: 100%;
width: 100%;
}
#app {
width: 100%;
height: 100%;
background: linear-gradient(to bottom, $bgc1, $bgc2);
}
</style>
子组件
child.vue
<div class="wrap" :style="{ transform: 'scale(' + trans + ') translate(-50%, -50%)'}">
...
</div>
<script>
...
mounted () {
this.transformX = document.body.clientWidth / 1920
this.transformY = document.body.clientHeight / 1080
if (document.body.clientWidth / document.body.clientHeight > 16 / 9) {
this.trans = this.transformY
} else {
this.trans = this.transformX
}
}
...
</script>
<style scoped lang="scss">
.wrap {
position: relative;
width: 1920px;
height: 1080px;
transform-origin: 0 0;
background-size: 100% 100%;
font-family: "PingFang SC", "Microsoft YaHei";
top: 50%;
left: 50%;
}
</style>