最近項目一個接著一個 好久沒有總結了 記錄以下問題的解決方式
- 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>