[vue]跨域|部署|大屏|地图|样式|等问题的解决

最近项目一个接着一个 好久没有总结了 记录以下问题的解决方式

  • 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丢到nginxhtml目录下
带来的问题是页面空白
然后采用如下解决方式 将本来为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出了问题 原因大概是我更新了MacOSCatalina
总之 在终端执行以下代码即可修复

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;
}

此时访问根路径结果如下

image.png

而访问我们的工程路径login结果如下

image.png

依照官网的解决方案在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;
  }

访问页面显示空白

image.png

这时去修改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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容