美文网首页Vue.jsvue
[vue]跨域|部署|大屏|地图|样式|等问题的解决

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

作者: 快乐的大鹅 | 来源:发表于2019-11-11 20:15 被阅读0次

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

    • 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>
    

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/idrnbctx.html