问卷系统开发 ---前端

手机端调试组件

在index.html文件内加上代码

<script type="text/javascript" src="//cdn.jsdelivr.net/npm/eruda"></script>
<script>
    // 手机端模拟console
    eruda.init();
    // console.log(navigator.language)
</script>

rem适配

  1. 在assets目录下加入flexible.js文件。flexible.js为阿里团队开源的一个库。根据屏幕宽度,给根元素确定一个px字号,页面中的制作稿则统一使用rem这个单位来制作。使用flexible.js轻松搞定各种不同的移动端设备兼容自适应问题。
  2. 在assets/css目录下创建config.scss。在config.scss里编写需要使用的scss方法,混入,变量等等.
// 将px转化为rem。设计稿以iphone7为模版,宽度为375px,所以是 $px / 12.5
@function rem ($px) {
    @return $px / 12.5+rem;
}
  1. 安装sass-loader ,node-sass。 编译scss样式文件

    npm install --save-dev sass-loader
    npm install --save-dev node-sass
    1. vue.config.js中配置config.scss。全局配置最好只放入css变量,配置普通css会导致样式重复。
       // vue.config -> module.css
       // css相关配置
       css: {
           // 是否使用css分离插件 ExtractTextPlugin
           extract: false,
           // 开启 CSS source maps?
           sourceMap: false,
           // css预设器配置项
           loaderOptions: {
               //向所有 Sass/Less 样式传入共享的全局变量
               scss:{
                    // 如果 sass-loader 版本 = 8,这里使用 `prependData` 字段
                    // 如果 sass-loader 版本 < 8,这里使用 `data` 字段
                   additionalData:'@import "~@/assets/css/config.scss";'
               },
           },
           // 启用 CSS modules for all css / pre-processor files.
           requireModuleExtension: true,
       },

    打包文件的压缩优化

    1. 安装压缩插件,压缩图片 插件
    npm i compression-webpack-plugin --save-dev
    npm i image-webpack-loader --save-dev
    1. 编写vue.config ,使vue项目运行时对文件进行压缩
    //vue.config -->module.chainWebpack
    const CompressionPlugin = require('compression-webpack-plugin');//引入gzip压缩插件
    
    // webpack配置
      chainWebpack: config => {
    
           //压缩图片
           config.module.rule('images')
               .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
               .use('image-webpack-loader')
               .loader('image-webpack-loader')
               .options({ bypassOnDebug: true })
    
           //压缩文件
           config.plugin('compressionPlugin')
               .use(new CompressionPlugin({
                   test: /\.js$|\.html$|\.css/,//匹配文件名 
                   threshold: 10240, // 对超过10k的数据压缩
                   deleteOriginalAssets: false // 不删除源文件
               }))
      }
    

    滚动行为在组件之间相互影响的解决办法

    使用路由提供的钩子函数

       router.afterEach((to,from,next) => {
           window.scrollTo(0,0)
       });

    多个组件中监听vuex中同一个action的回调,如何避免重复执行方法

    多个组件同时发出请求,第一个请求赋值给该变量,其他情况直接返回该变量

    //store.js 文件
       const state={
           web3:null
       };
       var flag=false;
       var fun=null;
       const action={
           myFunction(){
               //如果有数据直接返回
               if (state.web3) {   
                   Promise.resolve(state.web3)
                       return   
                   }else{
                       //如果没有数据且是第一次请求则执行函数
                       if(!falg){
                           falg=true;
                           fun=new Promise((resolve,reject)=>{
                               ajax('*****').then(res=>{
                                   state.web3=res
                                   resolve(res)
                               }).catch(err=>{
                                   //报错情况下可以再次执行函数
                                       falg=false;
                               })
                           })
                       }
                       //如果没有数据但是不是第一次请求直接返回promise函数
                   return fun
               }
           }
       }

    滚动行为在组件之间相互影响的解决办法

    使用路由提供的钩子函数,在路由离开时滚动到最顶部。

//router.js 
router.afterEach((to,from,next) => {
    window.scrollTo(0,0)
});

微信分享接口配置 参考—微信js-sdk说明文档

echarts图表强制横屏全屏展示

  1. 使用css3设置旋转中心和旋转角度。编写横屏全屏样式。
//scss 
.st_rotate{
    transform: rotate(90deg)!important;
    transform-origin:0% 0%!important;
    width: 100vh!important;/*2.利用 vh 重置 ‘宽度’ */
    height: 100vw!important;/* 3.利用 vw 重置 ‘高度’ */
    top: 0!important;
    left: 100vw!important;/* 4.旋转后页面超出屏幕,重置页面定位位置 */
    position: absolute!important;
    z-index: 2!important;
    //  对echarts容器重新设置宽高
    #etfCharts{
      width: 100vh!important;
      height: 95vw!important;
    }
}
  1. 在要全屏dom结构上加上css名,重新加载echarts图表
import { kBarline } from "@/assets/common/echarsVue.js"; //净值图、k线图

//Vue script
fullScreen(){
    document.getElementById('ChartsBox').classList.add('st_rotate');   //
    echarts.init(document.getElementById('etfCharts')).dispose();
    var myChart = echarts.init(document.getElementById("etfCharts"));
    kBarline(myChart,data);    //kBarline 封装的创建k线方法
}
//封装的创建k线方法  echarsVue.js
/**
 * k线&柱状
 */
export function kBarline(myChart,rawData){
    var dates = rawData.map(function (item) {
        return formatDateTime(item.TimeStamp);
    });
    var data2=[],data3=[];
    var data = rawData.map(function (item,index) {
        var oldVal=index==0?item.EndPrice:rawData[index-1].EndPrice;
        data2.push((item.EndPrice-oldVal)>0?(item.EndPrice-oldVal):0);
        data3.push((item.EndPrice-oldVal)>0?0:(item.EndPrice-oldVal));
        return [+item.BeginPrice, +item.EndPrice, +item.LowestPrice, +item.HighestPrice,Number(+item.EndPrice-oldVal).toFixed(4),((+item.EndPrice-oldVal)*100/oldVal).toFixed(2)];
    });
    console.log(rawData,data2,data3)

    var option={
        backgroundColor: '#21202D',  //背景颜色
        legend: {
            data: ['15分', '1小时', '4小时', '1天'],
            inactiveColor: '#777',
            textStyle: {
                color: '#fff',
                shadowColor:'none'
            }
        },
        //提示框组件。
        tooltip: {
            trigger: 'axis',
            //坐标轴指示器
            axisPointer: {
                // animation: false,
                type: 'cross',
                lineStyle: {
                    color: '#376df4',
                    width: 1,
                    opacity: 1,
                    type: 'cross',
                }
            },
            formatter: function (params) {
                console.log('params',params)
                var color = '';
                var style = 'float:right;text-align:right';
                if(params[4].value == '--') {
                    color = '#D7DCEF'
                } else {
                    if(params[4]>=0) {
                        color = '#00AF91'
                    } else {
                        color = '#DA435D'
                    }
                }
                var htmlStr = '<div style="width:auto">';
                    htmlStr +='<div>' + '时间' + ':' + '<span style='+style+'>'+params[0].axisValue+'</span></div>';
                    htmlStr +='<div>' + '开' + ':' + '<span style='+style+'>'+params[0].value[1].toFixed(4)+'</span></div>';
                    htmlStr +='<div>' + '收' + ':' + '<span style='+style+'>'+params[0].value[2].toFixed(4)+'</span></div>';
                    htmlStr +='<div>' + '低' + ':' + '<span style='+style+'>'+params[0].value[3].toFixed(4)+'</span></div>';
                    htmlStr +='<div>' + '高' + ':' + '<span style='+style+'>'+params[0].value[4].toFixed(4)+'</span></div>';
                    htmlStr +='<div>' + '涨跌值' + ':' + '<span style="float:right;color:'+color+';text-align:right">'+params[0].value[5]+'</span></div>';
                    htmlStr +='<div>' + '涨跌幅' + ':' + '<span style="float:right;color:'+color+';text-align:right">'+params[0].value[6]+'</span></div>';
                    htmlStr += '</div>'
              
                return htmlStr;
            }
        },
        // 用于区域缩放,从而能自由关注细节的数据信息,或者概览数据整体,或者去除离群点的影响。
        dataZoom: [{
            type: 'inside',
            xAxisIndex: [0, 1],
            start: 40,
            end: 70,
            top: 30,
            height: 20
        }],
        xAxis: [{
                type: 'category',
                data: dates,
                axisLine: { lineStyle: { color: '#8392A5' } },
                scale: true,
                boundaryGap: false,
                splitNumber: 20,
                min: 'dataMin',
                max: 'dataMax',
                splitLine: {
                    show: false
                }, 
            },{
                show:false,
                gridIndex:1,
                scale: true,
                data: dates,
                splitLine: {
                    show: false
                },  
            },
        ],
        yAxis: [
            {
                scale: true,
                splitArea: {
                    show: false
                },
                axisLine: { lineStyle: { color: '#8392A5' } },
                splitLine: {
                    show: false
                },
            },
            {
                show:false,
                splitLine: {
                    show: false
                },
                gridIndex:1,
                scale: true,
                splitArea: {
                    show: false
                }
            }
        ],
        animation: false,
          // 直角坐标系内绘图网格
          grid:[
            {
                left: '12%',
                height: '55%',
                top: '10%',  
            },
            {
                left: '12%',
                top: '70%',
                height: '30%'
            }
          ],
        series: [
            {
                name: 'bar',
                type: 'bar',
                data: data2,
                xAxisIndex: 1,
                yAxisIndex: 1,
                 itemStyle: {
                    color: '#FD1050',
                    // colo12: 'red',
                    // borderColor: '#eee',
                    // borderColor0: '#0CF49B'
                },
               
            },
            {
                name: 'bar',
                type: 'bar',
                data: data3,
                xAxisIndex: 1,
                yAxisIndex: 1,
                 itemStyle: {
                    color: '#FD1050',
                    // colo12: 'red',
                    // borderColor: '#eee',
                    // borderColor0: '#0CF49B'
                },
               
            },
            {
                type: 'candlestick',
                name: '15分',
                data: data,
                itemStyle: {
                    color: '#FD1050',
                    color0: '#0CF49B',
                    borderColor: '#FD1050',
                    borderColor0: '#0CF49B'
                },
                
                markLine: {
                    // show:false,
                    symbol: ['none', 'none'],
                    data: [
                          [
                            {
                                 showSymbol:false,
                                name: 'from lowest to highest',
                                type: 'min',
                                valueDim: 'lowest',
                                symbol: "none",
                                
                                symbolSize: 10,
                                label: {
                                    show: false
                                },
                                emphasis: {
                                    label: {
                                        show: false
                                    }
                                }
                            },
                            {
                                 showSymbol:false,
                                type: 'max',
                                valueDim: 'highest',
                                symbol:"none",
                                symbolSize: 10,
                                label: {
                                    show: false
                                },
                                emphasis: {
                                    label: {
                                        show: false
                                    }
                                }
                            }
                        ],
                        {
                             showSymbol:false,
                            name: 'min line on close',
                            type: 'min',
                            valueDim: 'close'
                        },
                        {
                             showSymbol:false,
                            name: 'max line on close',
                            type: 'max',
                            valueDim: 'close'
                        }
                    ]
                },
                markPoint: {
                    label: {
                            formatter: function (param) {
                                return param != null ? Math.round(param.value): '';
                            },
                            textStyle: {
                               
                                color: '#fff',
                                shadowColor:'none',
                                backgroundColor:"#999",
                                 rich:{
                                     padding:[0 ,100,0,0]
                                 }
                            },
                         distance: 0,
                         position:'top',
                        
                    },
                    data: [
                        {
                            name: 'XX标点',
                            coord: ['2013/5/31', 2300],
                            value: 2300,
                            itemStyle: {
                                color: '#999'
                            }
                        },
                        {
                            name: 'highest value',
                            type: 'max',
                            valueDim: 'highest',
                            symbol:"arrow",
                            symbolSize: 5,
                            symbolRotate:180,
                            symbolOffset:[0,0],
                            itemStyle:{
                                color:"#fff"
                            }
                        },
                        {
                            name: 'lowest value',
                            type: 'min',
                            valueDim: 'lowest',
                            symbol:"arrow",
                            symbolSize: 5,
                            symbolRotate:180,
                            symbolOffset:[0,0],
                             itemStyle:{
                                color:"#fff"
                            }
                        },
                         {
                          name: 'average value on close',
                           type: 'average',
                          valueDim: 'close',
                          symbol:"arrow",
                            symbolSize: 10,
                        }
                    ],
                },
            },
            {
                name: '1小时',
                type: 'line',
                data: calculateMA(5, data),
                smooth: true,
                showSymbol: false,
                lineStyle: {
                    width: 1
                }
            },
            {
                name: '4小时',
                type: 'line',
                data: calculateMA(10, data),
                smooth: true,
                showSymbol: false,
                lineStyle: {
                    width: 1
                }
            },
            {
                name: '1天',
                type: 'line',
                data: calculateMA(20, data),
                smooth: true,
                showSymbol: false,
                lineStyle: {
                    width: 1
                }
            },
            {
                name: 'MA30',
                type: 'line',
                data: calculateMA(30, data),
                smooth: true,
                showSymbol: false,
                lineStyle: {
                    width: 1
                }
            }
        ]
    
    }
    myChart.setOption(option,true);
}

使用js编写订阅者发布者模型

import {
    UpGetFundPrice,
    UpGetCurrencyPriceInfo, 
} from "@/api/common.js";      //接口文档
import md5 from "js-md5"

/**
   * 请求数据结构体
   * @param {*请求的页面名称} observeViewName 
   * @param {*币种名称} requestName
   * @param {*请求类型} requesttype 
   * @param {*回调} callback  
*/
class Observe {
    constructor() {
        this.requestsDict = {}
        this.etfnamesarr = []
        this.isetfvaluerequestfree = true
    }
    // 订阅方法
    subscribe(etfrequest) {
        if (!etfrequest||!etfrequest.requestName ||!etfrequest.requesttype||!etfrequest.observeViewName) {
            return
        }
        var key = etfrequest.requestName   
        var callback = etfrequest.callback
        var requesttype = etfrequest.requesttype
        var observeViewName = etfrequest.observeViewName
        
        //没有对应的key时,初始化
        if (!this.requestsDict[key]) {
            this.requestsDict[key] = {
                'requestKey': [],
                'timerKey': '',
            }
        }

        //添加请求体到数组里
        var requestData = this.requestsDict[key]
        var requestList = requestData['requestKey']
        var isfound = false
        for (let i = 0; i < requestList.length; i++) {
            var request = requestList[i]

            var key2 = request.requestName
            var requesttype2 = request.requesttype
            var observeViewName2 = request.observeViewName
            if (key2 == key && requesttype2 == requesttype && observeViewName2 == observeViewName) {
                isfound = true
                break
            }
        }
        if (!isfound) {
            requestList.push(etfrequest)
        }

        //timer没启动时,启动一下
        var thetimer = requestData['timerKey']
        //  ==1 获取净值
        if (requesttype == 1) {
            UpGetFundPriceWith(etfrequest.requestName)
            if (!thetimer || thetimer === '') {
                let timer = setInterval(() => {
                    UpGetFundPriceWith(etfrequest.requestName)
                }, 3000)
                requestData['timerKey'] = timer
            }
        // 获取价格
        } else if (requesttype == 2) {
            UpGetCurrencyPriceInfoWith(etfrequest.requestName)
            if (!thetimer || thetimer === '') {
                let timer = setInterval(() => {
                    UpGetCurrencyPriceInfoWith(etfrequest.requestName)
                }, 5000)
                requestData['timerKey'] = timer
            }
        }
    }
    // 发布
    notify(key, value) {
        var requestData = this.requestsDict[key]
        if (requestData) {
            var requestList = requestData['requestKey']
            if (requestList && requestList.length > 0) {
                for (let i = 0; i < requestList.length; i++) {
                    var request = requestList[i]
                    var fn = request.callback
                    fn.call(this, { key, value })
                }
            }
        }

    }
    //删除订阅者
    unsubscribe(etfrequest) {
        if (!etfrequest||!etfrequest.requestName||!etfrequest.observeViewName) {
            return
        }
        var key = etfrequest.requestName
        for (var k in this.requestsDict) {
            if (k === key) {
                var requestData = this.requestsDict[key]
                var requestList = requestData['requestKey']
                if (requestList) {
                    for (let i = requestList.length - 1; i >= 0; i--) {
                        var isequal = (requestList[i].observeViewName == etfrequest.observeViewName)
                        if (isequal) {
                            requestList.splice(i, 1)
                        }
                    }

                    if (requestList.length == 0) {
                        var timer = requestData['timerKey']
                        if (timer && timer !== '') {
                            clearInterval(timer);
                            timer = ''
                            requestData['timerKey'] = ''
                        }
                    }
                }
                break
            }
        }
    }
}

function UpGetFundPriceWith(etfname) {
    const dataParams = JSON.stringify({
        name: etfname,
    });
    UpGetFundPrice({
        data: dataParams,
        syn: md5(`${dataParams}X9dsf_)#&334$R(`),
    }).then(
        (result) => {
            if (result && result.data && result.data.code == 10000) {
                let res = result.data.data;
                if (res && res.IsSuccess) {
                    observeManager.notify(etfname, res)
                }
            }
        },
        (error) => { }
    );

}
/**
* 币种价格
*/
function UpGetCurrencyPriceInfoWith(name) {

    const dataParams = JSON.stringify({
        InstrumentID: name,
    });
    UpGetCurrencyPriceInfo({
        data: dataParams,
        syn: md5(`${dataParams}X9dsf_)#&334$R(`),
    }).then(
        (res) => {
            if (res && res.data && res.data.code == 10000) {
                let result = res.data.data;
                if (result) {
                    observeManager.notify(name, result)
                }
            }
        },
        (error) => { }
    );
}
const observeManager = new Observe()

export default observeManager

axios封装

重新构建axios实例对象,使用interceptor对request和response做请求的拦截和后端响应拦截。request中设置请求头,如token,cookie,和header。response写回调函数,监听http status,对返回状态做不同同理,500跳转500页面,404跳转404页面。然后将后台接口封装在一个js文件,再加入一个js接口数据校验文件。导出方法给页面的时间调用。利于维护。

  • 构建axios实例对象
//httpServer.js
//构建axios实例对象
import Vue from 'vue'
import axios from 'axios'
import store from '@/store.js'

//刷新token的请求方法co
const getRefreshToken = () => {
    let data = {
        refreshToken: Vue.prototype.$getStore('refreshToken')
    };
    return axios.post('', data);
}

//添加一个请求拦截器
axios.interceptors.request.use(config => {
    config.timeout = 120000
    config.headers = {
        "Content-Type": "application/json",
        "ETFLanguage":store.state.lan,
    };
    return config
}, error => {
    Vue.prototype.$message({
        showClose: true,
        message: error,
        type: 'error'
    });
    return Promise.reject(error)
})

//添加一个返回拦截器
axios.interceptors.response.use(response => {
    //对响应数据做些事
    if (response.data.code === 1002) {
        let config = response.config;
        return getRefreshToken()
            .then(function(res) {
                let data = res.data;
                if (data.code === 1000) {
                    Vue.prototype.$setStore('authToken', data.data.authToken);
Vue.prototype.$setStore('refreshToken', data.data.refreshToken);
                    //修改原请求的token
                    let authToken = Vue.prototype.$getStore('authToken');
                    config.headers.JWTToken = authToken;
                    /*这边不需要baseURL是因为会重新请求url
                     *url中已经包含baseURL的部分了
                     *如果不修改成空字符串,会变成'api/api/xxxx'的情况*/
                    config.baseURL = '';
                    //重新请求
                    return axios(config).then(
                        (responsedata) => {
                            console.log("重发上一次请求")
                            return responsedata
                        }
                    ).catch(
                        (response) => {
                            console.log("未渲染成功")
                            return response
                        });
                } else {
                    Vue.prototype.$message({ showClose: true, message: '超时退出', type: 'warning' });Vue.prototype.$removeStore('authToken');
Vue.prototype.$removeStore('refreshToken');
Vue.prototype.$removeStore('loginInfouser');
                    router.push({path: '/login'});
                    return { data: { code: '1001' } }
                }
            });

    } else if (response.data.code === 1001) {
        Vue.prototype.$message({ showClose: true, message: '超时退出', type: 'warning' });
        Vue.prototype.$removeStore('authToken');
        Vue.prototype.$removeStore('refreshToken');
        Vue.prototype.$removeStore('loginInfouser');
        router.push({path: '/login'});
        return { data: { code: '1001' } }
    } else {
        return response
    }
}, error => {
    return Promise.resolve(error.response)
})

//错误处理
function errorState(response) {
    // 如果http状态码正常,则直接返回数据
    if (response && (response.status === 200 || response.status === 304 || response.status === 400)) {
        return response
            // 如果不需要除了data之外的数据,可以直接 return response.data
    } else {
        // Vue.prototype.$message({
        //     showClose: true,
        //     message: '网络异常',
        //     type: 'error'
        // });
    }

}

function successState(res) {
    //统一判断后端返回的错误码
    // if (res.data.code == '9999') {
        // Vue.prototype.$message({
        //     showClose: true,
        //     message: '9999-服务器内部异常/网络异常',
        //     type: 'error'
        // });
    // }
}

const httpServer =async (opts, data) => {
    let httpDefaultOpts = { //http默认配置
        method: opts.method || 'post',
        url: opts.url
    }
    if (opts.method == 'get') {
        httpDefaultOpts.params = data
    } else {
        httpDefaultOpts.data = data
    }

    let promise = new Promise(function(resolve, reject) {
        axios(httpDefaultOpts).then(
            (res) => {
                successState(res)
                resolve(res)
            }
        ).catch(
            (response) => {
                errorState(response)
                reject(response)
            }
        )
    })
    return promise
}

export default httpServer
  • 后台接口文件
//@/api/common.js
//后台接口文件
import http from "@/server/httpServer.js";

// 节点接口
export const UpdateFundAssets = data => {
    return http({
        url: "/UpdateFundAssets",
        method: "get"
    }, data);
};
  • js接口数据校验文件
import { UpdateFundAssets} from "@/api/common.js";
/**
 *封装接口数据 
 */
export function bindData(obj) {
    var dataParams = obj ? JSON.stringify(obj) : '';
    var data = {
        data: dataParams,
        syn: md5(`${dataParams}X9dsf_)#&334$R(`)
    }
    return data
}

/**
 * 更新合约数据
 */
export function VerifyUpdateFundAssets(TokenAddress, AccountAddress) {
    if (!(AccountAddress && TokenAddress)) {
        return
    }
    var obj = {
        AccountAddress: AccountAddress.toString() || '',
        TokenAddress: TokenAddress.toString() || '',
    }
    var data = bindData(obj)
    return new Promise((resolve, reject) => {
        UpdateFundAssets(data).then(res => {
            if (res.data.code == 10000) {
                resolve(res.data.data)
            } else {
                reject(res)
            }
        }).catch(err => {
            reject(err)
        })
    })
}

页面弹窗插件化

为了方便弹窗的调用,同时减轻冗余代码,提高代码复用性。在开发过程中将用到的弹窗封装成插件。学习element插件封装方式,使插件既能全部引入,也能局部引入。


Hedgehog
14 声望1 粉丝

下一篇 »
vue自动部署