HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

关于直播业务的一点经验

直播 webrtc

由于公司业务需求,有视频直播这方面的需求。花了两个多月研究了一下视频直播方面的方案,分享一下经验给大家。

首先,我认为,RTMP推流不适合直播,无它,再牛逼的RTMP服务器延迟也在2秒以上,无法保证一秒以内的延迟,因而我认为RTMP只适合流媒体展现。

实际上webrtc才是直播的未来,我特意花了一个星期研究了一下webrtc,现在我已经在安卓上用MUI框架,实现了多人语音视频直播,而且主持人能够关闭某个人的麦克风,切换某个人的前后摄像头,调整某个人的画质,经过我和同事测试,在2人均使用4G的情况下,我是用电信4G,同事使用联通4G,房间服务器是移动宽带,实际测试,画面无卡顿,语音无延迟。

在3个人的情况下,当画质开高的时候,就可以看到我的电信4G,带宽达到了1.2MB/s,由于4G网速有限,同事的联通4G已经只能看到2个人的画面了。当我将所有人的画质调到320*480之后,5个人实时语音视频毫无卡顿。

但是,当我在ipad上使用MUI测试webrtc的时候,却发现,似乎不太顺利开,目前我正在测试iphone上面MUI框架对webrtc的支持情况,根据我之前的经验,应该比ipad要好一些。

总结:webrtc才是直播业务的王道,毕竟实时语音通信讲究的是0延迟,画面可以渣,但是语音不能延迟,这是底线。

啦啦啦啦啦啦。。。。。。我可以向公司交差了。。。

再多说一句,BAT真的黑啊,开源的webrtc技术,到了他们手里,收费高昂。。。。。。。。。。。。。

继续阅读 »

由于公司业务需求,有视频直播这方面的需求。花了两个多月研究了一下视频直播方面的方案,分享一下经验给大家。

首先,我认为,RTMP推流不适合直播,无它,再牛逼的RTMP服务器延迟也在2秒以上,无法保证一秒以内的延迟,因而我认为RTMP只适合流媒体展现。

实际上webrtc才是直播的未来,我特意花了一个星期研究了一下webrtc,现在我已经在安卓上用MUI框架,实现了多人语音视频直播,而且主持人能够关闭某个人的麦克风,切换某个人的前后摄像头,调整某个人的画质,经过我和同事测试,在2人均使用4G的情况下,我是用电信4G,同事使用联通4G,房间服务器是移动宽带,实际测试,画面无卡顿,语音无延迟。

在3个人的情况下,当画质开高的时候,就可以看到我的电信4G,带宽达到了1.2MB/s,由于4G网速有限,同事的联通4G已经只能看到2个人的画面了。当我将所有人的画质调到320*480之后,5个人实时语音视频毫无卡顿。

但是,当我在ipad上使用MUI测试webrtc的时候,却发现,似乎不太顺利开,目前我正在测试iphone上面MUI框架对webrtc的支持情况,根据我之前的经验,应该比ipad要好一些。

总结:webrtc才是直播业务的王道,毕竟实时语音通信讲究的是0延迟,画面可以渣,但是语音不能延迟,这是底线。

啦啦啦啦啦啦。。。。。。我可以向公司交差了。。。

再多说一句,BAT真的黑啊,开源的webrtc技术,到了他们手里,收费高昂。。。。。。。。。。。。。

收起阅读 »

(借鉴柠檬app文章)--实现彩色运动轨迹uni-app

**1.将轨迹地图部分放到hybrid->html->文件drawmap.html(也可以外部链接)

  1. 利用uni自带的webview技术,引入地图轨迹**
    代码:
    
    drawmap.html---  
    <!DOCTYPE html>  
    <html lang="en">  

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ImageLayer</title>
<meta id="viewport" name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0" />
<style>
html{
font-size: 24px;
}
*{
padding: 0;
margin: 0;
//禁止移动端长按复制文本
-webkit-touch-callout:none;


-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
}
body{
background: beige;
font-family:-apple-system-font,"Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
}
.map-container{
margin-top: -60px;
width: 100%;
height: 500px;
/ height: calc(100% - 9rem); /
position: absolute;
min-width: 300px;
background-color: #033447;
background: #033447;
}
.info-box{
position: absolute;
top: 13rem;
right: 0.5rem;
background: rgba(0,0,0,0.3);
height: 2.2rem;
border-radius: 1.1rem;
line-height: 1.4;
padding-left: 0.4rem;
z-index: 999;
transition: margin 0.4s;
-webkit-transition: margin 0.4s;
}
.avatar{
width: 2rem;
height: 2rem;
float: right;
border: 0.1rem solid #fff;
border-radius: 50%;
}
.name{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
color: #fff;
font-size: 0.7rem;
margin-top: 0.3rem;
}
.time{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
font-size: 0.5rem;
color: rgba(255,255,255,0.8);
}
p{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
}

</style>  

</head>

<body>
<div class="container">
<div class="info-box">
<img class="avatar" src="./img/applogo.png" alt="">
<p class="name">app名称</p>
<p class="time" id="time">09月10日</p>
</div>

    <div id="map-container" class="map-container"></div>  

</div>  
<script src="https://webapi.amap.com/maps?v=1.4.15&key=高德key值"></script>  
<script>  
    var pointsList = [];  
    function getNowFormatDate() {  
      var date = new Date();  
      var seperator1 = "月";  
      var month = date.getMonth() + 1;//月  
      var strDate = date.getDate(); //日  
      if (month >= 1 && month <= 9) {  
        month = "0" + month;  
      }  
      if (strDate >= 0 && strDate <= 9) {  
        strDate = "0" + strDate;  
      }  
      var currentdate = month + seperator1 + strDate + '日';  
        document.getElementById("time").innerHTML = currentdate  
    //   return currentdate;  
    }  
    getNowFormatDate();  
    // 从地址栏中获取训练id;查询  
    var reg = new RegExp("(^|&)"+ 'userid' +"=([^&]*)(&|$)");  
    var r = window.location.search.substr(1).match(reg);  
    // console.log(unescape(r[2]));  
    // var index=window.location.search.fiIndexOf("\-");  
    // obj=obj.substring(index+1,obj.length);  
    var url = '请求经纬度的接口/getDynamicTrack?userid='+unescape(r[2])  
    // 请球数据  
    function getData(url){  
        // console.log("调用"+url)  
        var httpRequest = new XMLHttpRequest();  
        httpRequest.open('GET', url, true);  
        httpRequest.send();  
        /**  
         * 获取数据后的处理程序  
         */  
        httpRequest.onreadystatechange = function () {  
            if (httpRequest.readyState == 4 && httpRequest.status == 200) {  
                var json = httpRequest.responseText;//获取到json字符串,还需解析  
                pointsList = JSON.parse(json).data;  
                // console.log(pointsList);  
                preProcessList();  
                setCustomLayer();  
            }  
        };  
    }  
    getData(url);  

    var mapZooms = [3, 18]  
    var map = new AMap.Map('map-container', {  
        mapStyle: 'https://amap://styles/blue',  
        resizeEnable: true, //是否监控地图容器尺寸变化  
        dragEnable: false,  //地图是否可通过鼠标拖拽平移,  
        doubleClickZoom: false, //地图是否可通过双击鼠标放大地图  
        keyboardEnable: false, // 地图是否可通过键盘控制,默认为true方向键控制地图平移,  
        scrollWheel: false, // 地图是否可通过鼠标滚轮缩放浏览  
        touchZoom: false, // 地图在移动终端上是否可通过多点触控缩放浏览地图  
        zooms: mapZooms  
    });  
    map.setFeatures(['bg','road']); // 多个种类要素显示  
    // 监听容器变化  
    // map.on('zoomend', function(ev) {  
    //   console.log('缩放等级变化了-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    //   setCustomLayer();  
    // });  
    // map.on('click', function(ev) {  
    //   console.log('鼠标点击-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('moveend', function(ev) {  
    //   console.log('地图中心点变化-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('touchend', function(ev) {  
    //   console.log('手机触摸-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom());  
    //   setCustomLayer();  
    // });  
    // 获取经纬度(这里处理数据)--start  
    /*  
     * 添加Canvas图层  
     */  

    var polylineArr = [];  
    var colorSteps = 10; // 颜色等级  
    var maxSpeed = 0;  
    var minSpeed = 0;  
    var averageSpeed = 0; // 平均速度  
    var totalSpeed = 0;  
    // setAvgSpeed();  
    var speedTopStep = 0;  
    var speedBellowStep = 0;  
    function setPoints(){  
        var makers = [];  
        var markersArg = [{  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-start.png',  
            position: [pointsList[0].longitude, pointsList[0].latitude]  
        }, {  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-end.png',  
            position: [pointsList[pointsList.length-1].longitude, pointsList[pointsList.length-1].latitude]  
        }];  
        markersArg.forEach(function(marker) {  
            var maker = new AMap.Marker({  
                map: map,  
                position: [marker.position[0], marker.position[1]],  
                icon: new AMap.Icon({  
                    size: new AMap.Size(20, 20),  //图标大小  
                    image: marker.icon,  
                    imageSize: new AMap.Size(20,20)  
                }),  
                offset: new AMap.Pixel(-10, -10)  
            });  
        });  
    }  
    function preProcessList() {  
        for (var i = 0; i < pointsList.length; i++) {  
            var tmp = [];  
            var speed = pointsList[i].speed;  
            tmp[0] = pointsList[i].longitude;  
            tmp[1] = pointsList[i].latitude;  
            // 数组累加求平均data.reduce((totalPrice, item) => totalPrice + item.price, 0);  
            totalSpeed += speed  
            polylineArr.push(tmp);  
            // 获取最高速度和最低速度  
            if (speed > -1 && speed > maxSpeed) {  
                maxSpeed = speed;  
            }  
            if (speed > -1 && speed < minSpeed) {  
                minSpeed = speed;  
            }  

        }  
        averageSpeed = totalSpeed/pointsList.length;  
        // console.log('平均速度'+averageSpeed)  
        speedTopStep = (maxSpeed - averageSpeed) / colorSteps;  
        speedBellowStep = (averageSpeed - minSpeed) / colorSteps;  
        // console.log('最大速度颜色等级'+speedTopStep)  
        // console.log('最小速度颜色等级'+speedBellowStep)  
        /** 使用高德地图自带的覆盖方法, 自动定位并缩放, 但不显示-start **/  
        // console.log('开始定位和自适应屏幕')  
        var polyline = new AMap.Polyline({  
            path: polylineArr,  
            strokeColor: "#FF33FF", // //线颜色  
            strokeOpacity: 0,  
            strokeWeight: 0,  
            zIndex:0  
         });  
        polyline.setMap(map);  
        // 绘制开始点和结束点  
        setPoints();  
        // setPoints(); // 绘制标记点--每1公里标记一下  
        map.setFitView(); //屏幕自适应-// 自动适配到合适视野范围// 无参数,默认包括所有覆盖物的情况  
        /* end */  
        // console.log('结束定位和自适应屏幕')  
    }  

    // 绘制标记点  

    /* end */  

    /* 不同颜色的轨迹-start */  
    // 处理颜色的方法  

    var bellowColors = ["#ffff00", "#fff500", "#ffe800", "#ffd900", "#ffc700", "#ffb400", "#ffa000", "#ff8a00", "#ff6000", "#ff2700", "#ff0000"];  
    var topColors = ["#ffff00", "#eeff00", "#d7ff00", "#bcff00", "#9fff00", "#7fff00", "#5fff00", "#41ff00", "#27ff00", "#10ff00", "#00ff00"];  
    function getColor(speed) {  
        if(speed < 0 ){  
            return '#ffff00';  
        }  
        var subSpeed = speed - averageSpeed;  
        if (subSpeed > 0) {  
            if(speedTopStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(subSpeed / speedTopStep);  
            // console.log('等级'+level)  
            return topColors[level];  
        } else {  
            if(speedBellowStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(Math.abs(subSpeed) / speedBellowStep);  
            return bellowColors[level];  
        }  
    }  

    //  初始化canvas  
    var canvas = '';  
    function setCustomLayer() {  
        // console.log('初始化canvas')  
        // CustomLayer类(插件)--自定义图层  
        map.plugin(['AMap.CustomLayer'], function () {  
            canvas = document.createElement('canvas');  
            var customLayer = new AMap.CustomLayer(canvas, {  
                zIndex: 99,  
                zooms: map.getZoom()  
            });  
            customLayer.setMap(map); // 设置图层所属的地图对象,传入null时从当前地图移除  
            customLayer.render = drawLine(); // 初始化完成时候,开发者需要给该图层设定render方法,该方法需要实现图层的绘制,API会在合适的时机自动调用该方法  
        });  
    }  
    // setTimeout(function(){   

    // }, 3000);  

    // 绘制  
    function drawLine() {  
        console.log('开始绘制轨迹')  
        var context = canvas.getContext('2d');// 一个 CanvasRenderingContext2D 对象,使用它可以绘制到 Canvas 元素中。  
        canvas.width = map.getSize().width;  
        canvas.height = map.getSize().height;  
        context.globalAlpha=1; // 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。  
        context.clearRect(0, 0, canvas.width, canvas.height); //(x, y, widh, height)清除指定的矩形区域,然后这块区域会变的完全透明。 再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法  
        context.lineWidth = 3; // 线宽。只能是正值。默认是1.0。  
        context.lineCap = 'round'; // 线条末端样式。(butt:线段末端以方形结束;round:线段末端以圆形结束;square:线段末端以方形结束,)  
        // 遍历所有的点  
        var startColor = '';  
        var endColor = '';  
        // 第一个点  
        pointsList[0].containerPos = map.lngLatToContainer([pointsList[0].longitude, pointsList[0].latitude]);  
        for (var j = 1, pointLen = pointsList.length; j < pointLen; j++) {   
             pointsList[j].containerPos = map.lngLatToContainer([pointsList[j].longitude, pointsList[j].latitude]); // 转换为地图识别的格式  
             var startX = pointsList[j - 1].containerPos.x;  //开始点的x坐标  
             var startY = pointsList[j - 1].containerPos.y;  //开始点的y坐标  
             var endX = pointsList[j].containerPos.x;   //结束点的x坐标  
             var endY = pointsList[j].containerPos.y;   //结束点的y坐标  
             context.beginPath(); // 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径  
             context.moveTo(startX, startY);  // 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。  
             context.lineTo(endX, endY); // //绘制一条从当前位置到指定坐标(200, 50)的直线.  
             context.strokeStyle = getColor(pointsList[j].speed); //根据速度来获取颜色---设置图形轮廓的颜色  
             context.stroke(); // 通过线条来绘制图形轮廓  
             //设置渐变  
             startColor = getColor(pointsList[j-1].speed);  
             endColor = getColor(pointsList[j].speed);  
             var grd = context.createLinearGradient(startX, startY, endX, endY); // 创建线性的渐变对象  
             grd.addColorStop(0, startColor);  
             grd.addColorStop(1, endColor);  
             context.strokeStyle = grd;  
             context.stroke();  
        }       
    }  

    /* end */  

</script>  

</body>

</html>


4.finsh.vue页面引入

// #ifdef APP-PLUS
wv = plus.webview.create("","custom-webview",{
plusrequire:"none",
'uni-app': 'none',
top: 0 ,
height: 400,
})
wv.loadURL("/hybrid/html/drawmap.html?userid="+that.userId)
var currentWebview = this.$scope.$getAppWebview();
currentWebview.append(wv);
setTimeout(function() {
// console.log(wv.getStyle())
}, 1000);
// #endif



继续阅读 »

**1.将轨迹地图部分放到hybrid->html->文件drawmap.html(也可以外部链接)

  1. 利用uni自带的webview技术,引入地图轨迹**
    代码:
    
    drawmap.html---  
    <!DOCTYPE html>  
    <html lang="en">  

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ImageLayer</title>
<meta id="viewport" name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0" />
<style>
html{
font-size: 24px;
}
*{
padding: 0;
margin: 0;
//禁止移动端长按复制文本
-webkit-touch-callout:none;


-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
}
body{
background: beige;
font-family:-apple-system-font,"Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
}
.map-container{
margin-top: -60px;
width: 100%;
height: 500px;
/ height: calc(100% - 9rem); /
position: absolute;
min-width: 300px;
background-color: #033447;
background: #033447;
}
.info-box{
position: absolute;
top: 13rem;
right: 0.5rem;
background: rgba(0,0,0,0.3);
height: 2.2rem;
border-radius: 1.1rem;
line-height: 1.4;
padding-left: 0.4rem;
z-index: 999;
transition: margin 0.4s;
-webkit-transition: margin 0.4s;
}
.avatar{
width: 2rem;
height: 2rem;
float: right;
border: 0.1rem solid #fff;
border-radius: 50%;
}
.name{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
color: #fff;
font-size: 0.7rem;
margin-top: 0.3rem;
}
.time{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
font-size: 0.5rem;
color: rgba(255,255,255,0.8);
}
p{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
}

</style>  

</head>

<body>
<div class="container">
<div class="info-box">
<img class="avatar" src="./img/applogo.png" alt="">
<p class="name">app名称</p>
<p class="time" id="time">09月10日</p>
</div>

    <div id="map-container" class="map-container"></div>  

</div>  
<script src="https://webapi.amap.com/maps?v=1.4.15&key=高德key值"></script>  
<script>  
    var pointsList = [];  
    function getNowFormatDate() {  
      var date = new Date();  
      var seperator1 = "月";  
      var month = date.getMonth() + 1;//月  
      var strDate = date.getDate(); //日  
      if (month >= 1 && month <= 9) {  
        month = "0" + month;  
      }  
      if (strDate >= 0 && strDate <= 9) {  
        strDate = "0" + strDate;  
      }  
      var currentdate = month + seperator1 + strDate + '日';  
        document.getElementById("time").innerHTML = currentdate  
    //   return currentdate;  
    }  
    getNowFormatDate();  
    // 从地址栏中获取训练id;查询  
    var reg = new RegExp("(^|&)"+ 'userid' +"=([^&]*)(&|$)");  
    var r = window.location.search.substr(1).match(reg);  
    // console.log(unescape(r[2]));  
    // var index=window.location.search.fiIndexOf("\-");  
    // obj=obj.substring(index+1,obj.length);  
    var url = '请求经纬度的接口/getDynamicTrack?userid='+unescape(r[2])  
    // 请球数据  
    function getData(url){  
        // console.log("调用"+url)  
        var httpRequest = new XMLHttpRequest();  
        httpRequest.open('GET', url, true);  
        httpRequest.send();  
        /**  
         * 获取数据后的处理程序  
         */  
        httpRequest.onreadystatechange = function () {  
            if (httpRequest.readyState == 4 && httpRequest.status == 200) {  
                var json = httpRequest.responseText;//获取到json字符串,还需解析  
                pointsList = JSON.parse(json).data;  
                // console.log(pointsList);  
                preProcessList();  
                setCustomLayer();  
            }  
        };  
    }  
    getData(url);  

    var mapZooms = [3, 18]  
    var map = new AMap.Map('map-container', {  
        mapStyle: 'https://amap://styles/blue',  
        resizeEnable: true, //是否监控地图容器尺寸变化  
        dragEnable: false,  //地图是否可通过鼠标拖拽平移,  
        doubleClickZoom: false, //地图是否可通过双击鼠标放大地图  
        keyboardEnable: false, // 地图是否可通过键盘控制,默认为true方向键控制地图平移,  
        scrollWheel: false, // 地图是否可通过鼠标滚轮缩放浏览  
        touchZoom: false, // 地图在移动终端上是否可通过多点触控缩放浏览地图  
        zooms: mapZooms  
    });  
    map.setFeatures(['bg','road']); // 多个种类要素显示  
    // 监听容器变化  
    // map.on('zoomend', function(ev) {  
    //   console.log('缩放等级变化了-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    //   setCustomLayer();  
    // });  
    // map.on('click', function(ev) {  
    //   console.log('鼠标点击-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('moveend', function(ev) {  
    //   console.log('地图中心点变化-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('touchend', function(ev) {  
    //   console.log('手机触摸-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom());  
    //   setCustomLayer();  
    // });  
    // 获取经纬度(这里处理数据)--start  
    /*  
     * 添加Canvas图层  
     */  

    var polylineArr = [];  
    var colorSteps = 10; // 颜色等级  
    var maxSpeed = 0;  
    var minSpeed = 0;  
    var averageSpeed = 0; // 平均速度  
    var totalSpeed = 0;  
    // setAvgSpeed();  
    var speedTopStep = 0;  
    var speedBellowStep = 0;  
    function setPoints(){  
        var makers = [];  
        var markersArg = [{  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-start.png',  
            position: [pointsList[0].longitude, pointsList[0].latitude]  
        }, {  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-end.png',  
            position: [pointsList[pointsList.length-1].longitude, pointsList[pointsList.length-1].latitude]  
        }];  
        markersArg.forEach(function(marker) {  
            var maker = new AMap.Marker({  
                map: map,  
                position: [marker.position[0], marker.position[1]],  
                icon: new AMap.Icon({  
                    size: new AMap.Size(20, 20),  //图标大小  
                    image: marker.icon,  
                    imageSize: new AMap.Size(20,20)  
                }),  
                offset: new AMap.Pixel(-10, -10)  
            });  
        });  
    }  
    function preProcessList() {  
        for (var i = 0; i < pointsList.length; i++) {  
            var tmp = [];  
            var speed = pointsList[i].speed;  
            tmp[0] = pointsList[i].longitude;  
            tmp[1] = pointsList[i].latitude;  
            // 数组累加求平均data.reduce((totalPrice, item) => totalPrice + item.price, 0);  
            totalSpeed += speed  
            polylineArr.push(tmp);  
            // 获取最高速度和最低速度  
            if (speed > -1 && speed > maxSpeed) {  
                maxSpeed = speed;  
            }  
            if (speed > -1 && speed < minSpeed) {  
                minSpeed = speed;  
            }  

        }  
        averageSpeed = totalSpeed/pointsList.length;  
        // console.log('平均速度'+averageSpeed)  
        speedTopStep = (maxSpeed - averageSpeed) / colorSteps;  
        speedBellowStep = (averageSpeed - minSpeed) / colorSteps;  
        // console.log('最大速度颜色等级'+speedTopStep)  
        // console.log('最小速度颜色等级'+speedBellowStep)  
        /** 使用高德地图自带的覆盖方法, 自动定位并缩放, 但不显示-start **/  
        // console.log('开始定位和自适应屏幕')  
        var polyline = new AMap.Polyline({  
            path: polylineArr,  
            strokeColor: "#FF33FF", // //线颜色  
            strokeOpacity: 0,  
            strokeWeight: 0,  
            zIndex:0  
         });  
        polyline.setMap(map);  
        // 绘制开始点和结束点  
        setPoints();  
        // setPoints(); // 绘制标记点--每1公里标记一下  
        map.setFitView(); //屏幕自适应-// 自动适配到合适视野范围// 无参数,默认包括所有覆盖物的情况  
        /* end */  
        // console.log('结束定位和自适应屏幕')  
    }  

    // 绘制标记点  

    /* end */  

    /* 不同颜色的轨迹-start */  
    // 处理颜色的方法  

    var bellowColors = ["#ffff00", "#fff500", "#ffe800", "#ffd900", "#ffc700", "#ffb400", "#ffa000", "#ff8a00", "#ff6000", "#ff2700", "#ff0000"];  
    var topColors = ["#ffff00", "#eeff00", "#d7ff00", "#bcff00", "#9fff00", "#7fff00", "#5fff00", "#41ff00", "#27ff00", "#10ff00", "#00ff00"];  
    function getColor(speed) {  
        if(speed < 0 ){  
            return '#ffff00';  
        }  
        var subSpeed = speed - averageSpeed;  
        if (subSpeed > 0) {  
            if(speedTopStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(subSpeed / speedTopStep);  
            // console.log('等级'+level)  
            return topColors[level];  
        } else {  
            if(speedBellowStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(Math.abs(subSpeed) / speedBellowStep);  
            return bellowColors[level];  
        }  
    }  

    //  初始化canvas  
    var canvas = '';  
    function setCustomLayer() {  
        // console.log('初始化canvas')  
        // CustomLayer类(插件)--自定义图层  
        map.plugin(['AMap.CustomLayer'], function () {  
            canvas = document.createElement('canvas');  
            var customLayer = new AMap.CustomLayer(canvas, {  
                zIndex: 99,  
                zooms: map.getZoom()  
            });  
            customLayer.setMap(map); // 设置图层所属的地图对象,传入null时从当前地图移除  
            customLayer.render = drawLine(); // 初始化完成时候,开发者需要给该图层设定render方法,该方法需要实现图层的绘制,API会在合适的时机自动调用该方法  
        });  
    }  
    // setTimeout(function(){   

    // }, 3000);  

    // 绘制  
    function drawLine() {  
        console.log('开始绘制轨迹')  
        var context = canvas.getContext('2d');// 一个 CanvasRenderingContext2D 对象,使用它可以绘制到 Canvas 元素中。  
        canvas.width = map.getSize().width;  
        canvas.height = map.getSize().height;  
        context.globalAlpha=1; // 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。  
        context.clearRect(0, 0, canvas.width, canvas.height); //(x, y, widh, height)清除指定的矩形区域,然后这块区域会变的完全透明。 再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法  
        context.lineWidth = 3; // 线宽。只能是正值。默认是1.0。  
        context.lineCap = 'round'; // 线条末端样式。(butt:线段末端以方形结束;round:线段末端以圆形结束;square:线段末端以方形结束,)  
        // 遍历所有的点  
        var startColor = '';  
        var endColor = '';  
        // 第一个点  
        pointsList[0].containerPos = map.lngLatToContainer([pointsList[0].longitude, pointsList[0].latitude]);  
        for (var j = 1, pointLen = pointsList.length; j < pointLen; j++) {   
             pointsList[j].containerPos = map.lngLatToContainer([pointsList[j].longitude, pointsList[j].latitude]); // 转换为地图识别的格式  
             var startX = pointsList[j - 1].containerPos.x;  //开始点的x坐标  
             var startY = pointsList[j - 1].containerPos.y;  //开始点的y坐标  
             var endX = pointsList[j].containerPos.x;   //结束点的x坐标  
             var endY = pointsList[j].containerPos.y;   //结束点的y坐标  
             context.beginPath(); // 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径  
             context.moveTo(startX, startY);  // 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。  
             context.lineTo(endX, endY); // //绘制一条从当前位置到指定坐标(200, 50)的直线.  
             context.strokeStyle = getColor(pointsList[j].speed); //根据速度来获取颜色---设置图形轮廓的颜色  
             context.stroke(); // 通过线条来绘制图形轮廓  
             //设置渐变  
             startColor = getColor(pointsList[j-1].speed);  
             endColor = getColor(pointsList[j].speed);  
             var grd = context.createLinearGradient(startX, startY, endX, endY); // 创建线性的渐变对象  
             grd.addColorStop(0, startColor);  
             grd.addColorStop(1, endColor);  
             context.strokeStyle = grd;  
             context.stroke();  
        }       
    }  

    /* end */  

</script>  

</body>

</html>


4.finsh.vue页面引入

// #ifdef APP-PLUS
wv = plus.webview.create("","custom-webview",{
plusrequire:"none",
'uni-app': 'none',
top: 0 ,
height: 400,
})
wv.loadURL("/hybrid/html/drawmap.html?userid="+that.userId)
var currentWebview = this.$scope.$getAppWebview();
currentWebview.append(wv);
setTimeout(function() {
// console.log(wv.getStyle())
}, 1000);
// #endif



收起阅读 »

uni仿keep跑步彩色运动轨迹,--利用高德自带的API + H5的canvas(借鉴柠檬-很感谢)

**1.将轨迹地图部分放到hybrid->html->文件drawmap.html(也可以外部链接)

  1. 利用uni自带的webview技术,引入地图轨迹**
    代码:
    drawmap.html---
    
    <!DOCTYPE html>  
    <html lang="en">  

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ImageLayer</title>
<meta id="viewport" name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0" />
<style>
html{
font-size: 24px;
}
{
padding: 0;
margin: 0;
//禁止移动端长按复制文本
-webkit-touch-callout:none;
-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
}
body{
background: beige;
font-family:-apple-system-font,"Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
}
.map-container{
margin-top: -60px;
width: 100%;
height: 500px;
/
height: calc(100% - 9rem); */
position: absolute;
min-width: 300px;
background-color: #033447;
background: #033447;
}
.info-box{
position: absolute;
top: 13rem;
right: 0.5rem;
background: rgba(0,0,0,0.3);
height: 2.2rem;
border-radius: 1.1rem;
line-height: 1.4;
padding-left: 0.4rem;
z-index: 999;
transition: margin 0.4s;
-webkit-transition: margin 0.4s;
}
.avatar{
width: 2rem;
height: 2rem;
float: right;
border: 0.1rem solid #fff;
border-radius: 50%;
}
.name{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
color: #fff;
font-size: 0.7rem;
margin-top: 0.3rem;
}
.time{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
font-size: 0.5rem;
color: rgba(255,255,255,0.8);
}
p{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
}

</style>  

</head>

<body>
<div class="container">
<div class="info-box">
<img class="avatar" src="./img/applogo.png" alt="">
<p class="name">app名称</p>
<p class="time" id="time">09月10日</p>
</div>

    <div id="map-container" class="map-container"></div>  

</div>  
<script src="https://webapi.amap.com/maps?v=1.4.15&key=8af5729787cab8a4b5930a9b20d141bc"></script>  
<script>  
    var pointsList = [];  
    function getNowFormatDate() {  
      var date = new Date();  
      var seperator1 = "月";  
      var month = date.getMonth() + 1;//月  
      var strDate = date.getDate(); //日  
      if (month >= 1 && month <= 9) {  
        month = "0" + month;  
      }  
      if (strDate >= 0 && strDate <= 9) {  
        strDate = "0" + strDate;  
      }  
      var currentdate = month + seperator1 + strDate + '日';  
        document.getElementById("time").innerHTML = currentdate  
    //   return currentdate;  
    }  
    getNowFormatDate();  
    // 从地址栏中获取训练id;查询  
    var reg = new RegExp("(^|&)"+ 'userid' +"=([^&]*)(&|$)");  
    var r = window.location.search.substr(1).match(reg);  
    // console.log(unescape(r[2]));  
    // var index=window.location.search.fiIndexOf("\-");  
    // obj=obj.substring(index+1,obj.length);  
    var url = '请求经纬度的接口/getDynamicTrack?userid='+unescape(r[2])  
    // 请球数据  
    function getData(url){  
        // console.log("调用"+url)  
        var httpRequest = new XMLHttpRequest();  
        httpRequest.open('GET', url, true);  
        httpRequest.send();  
        /**  
         * 获取数据后的处理程序  
         */  
        httpRequest.onreadystatechange = function () {  
            if (httpRequest.readyState == 4 && httpRequest.status == 200) {  
                var json = httpRequest.responseText;//获取到json字符串,还需解析  
                pointsList = JSON.parse(json).data;  
                // console.log(pointsList);  
                preProcessList();  
                setCustomLayer();  
            }  
        };  
    }  
    getData(url);  

    var mapZooms = [3, 18]  
    var map = new AMap.Map('map-container', {  
        mapStyle: 'https://amap://styles/blue',  
        resizeEnable: true, //是否监控地图容器尺寸变化  
        dragEnable: false,  //地图是否可通过鼠标拖拽平移,  
        doubleClickZoom: false, //地图是否可通过双击鼠标放大地图  
        keyboardEnable: false, // 地图是否可通过键盘控制,默认为true方向键控制地图平移,  
        scrollWheel: false, // 地图是否可通过鼠标滚轮缩放浏览  
        touchZoom: false, // 地图在移动终端上是否可通过多点触控缩放浏览地图  
        zooms: mapZooms  
    });  
    map.setFeatures(['bg','road']); // 多个种类要素显示  
    // 监听容器变化  
    // map.on('zoomend', function(ev) {  
    //   console.log('缩放等级变化了-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    //   setCustomLayer();  
    // });  
    // map.on('click', function(ev) {  
    //   console.log('鼠标点击-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('moveend', function(ev) {  
    //   console.log('地图中心点变化-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('touchend', function(ev) {  
    //   console.log('手机触摸-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom());  
    //   setCustomLayer();  
    // });  
    // 获取经纬度(这里处理数据)--start  
    /*  
     * 添加Canvas图层  
     */  

    var polylineArr = [];  
    var colorSteps = 10; // 颜色等级  
    var maxSpeed = 0;  
    var minSpeed = 0;  
    var averageSpeed = 0; // 平均速度  
    var totalSpeed = 0;  
    // setAvgSpeed();  
    var speedTopStep = 0;  
    var speedBellowStep = 0;  
    function setPoints(){  
        var makers = [];  
        var markersArg = [{  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-start.png',  
            position: [pointsList[0].longitude, pointsList[0].latitude]  
        }, {  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-end.png',  
            position: [pointsList[pointsList.length-1].longitude, pointsList[pointsList.length-1].latitude]  
        }];  
        markersArg.forEach(function(marker) {  
            var maker = new AMap.Marker({  
                map: map,  
                position: [marker.position[0], marker.position[1]],  
                icon: new AMap.Icon({  
                    size: new AMap.Size(20, 20),  //图标大小  
                    image: marker.icon,  
                    imageSize: new AMap.Size(20,20)  
                }),  
                offset: new AMap.Pixel(-10, -10)  
            });  
        });  
    }  
    function preProcessList() {  
        for (var i = 0; i < pointsList.length; i++) {  
            var tmp = [];  
            var speed = pointsList[i].speed;  
            tmp[0] = pointsList[i].longitude;  
            tmp[1] = pointsList[i].latitude;  
            // 数组累加求平均data.reduce((totalPrice, item) => totalPrice + item.price, 0);  
            totalSpeed += speed  
            polylineArr.push(tmp);  
            // 获取最高速度和最低速度  
            if (speed > -1 && speed > maxSpeed) {  
                maxSpeed = speed;  
            }  
            if (speed > -1 && speed < minSpeed) {  
                minSpeed = speed;  
            }  

        }  
        averageSpeed = totalSpeed/pointsList.length;  
        // console.log('平均速度'+averageSpeed)  
        speedTopStep = (maxSpeed - averageSpeed) / colorSteps;  
        speedBellowStep = (averageSpeed - minSpeed) / colorSteps;  
        // console.log('最大速度颜色等级'+speedTopStep)  
        // console.log('最小速度颜色等级'+speedBellowStep)  
        /** 使用高德地图自带的覆盖方法, 自动定位并缩放, 但不显示-start **/  
        // console.log('开始定位和自适应屏幕')  
        var polyline = new AMap.Polyline({  
            path: polylineArr,  
            strokeColor: "#FF33FF", // //线颜色  
            strokeOpacity: 0,  
            strokeWeight: 0,  
            zIndex:0  
         });  
        polyline.setMap(map);  
        // 绘制开始点和结束点  
        setPoints();  
        // setPoints(); // 绘制标记点--每1公里标记一下  
        map.setFitView(); //屏幕自适应-// 自动适配到合适视野范围// 无参数,默认包括所有覆盖物的情况  
        /* end */  
        // console.log('结束定位和自适应屏幕')  
    }  

    // 绘制标记点  

    /* end */  

    /* 不同颜色的轨迹-start */  
    // 处理颜色的方法  

    var bellowColors = ["#ffff00", "#fff500", "#ffe800", "#ffd900", "#ffc700", "#ffb400", "#ffa000", "#ff8a00", "#ff6000", "#ff2700", "#ff0000"];  
    var topColors = ["#ffff00", "#eeff00", "#d7ff00", "#bcff00", "#9fff00", "#7fff00", "#5fff00", "#41ff00", "#27ff00", "#10ff00", "#00ff00"];  
    function getColor(speed) {  
        if(speed < 0 ){  
            return '#ffff00';  
        }  
        var subSpeed = speed - averageSpeed;  
        if (subSpeed > 0) {  
            if(speedTopStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(subSpeed / speedTopStep);  
            // console.log('等级'+level)  
            return topColors[level];  
        } else {  
            if(speedBellowStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(Math.abs(subSpeed) / speedBellowStep);  
            return bellowColors[level];  
        }  
    }  

    //  初始化canvas  
    var canvas = '';  
    function setCustomLayer() {  
        // console.log('初始化canvas')  
        // CustomLayer类(插件)--自定义图层  
        map.plugin(['AMap.CustomLayer'], function () {  
            canvas = document.createElement('canvas');  
            var customLayer = new AMap.CustomLayer(canvas, {  
                zIndex: 99,  
                zooms: map.getZoom()  
            });  
            customLayer.setMap(map); // 设置图层所属的地图对象,传入null时从当前地图移除  
            customLayer.render = drawLine(); // 初始化完成时候,开发者需要给该图层设定render方法,该方法需要实现图层的绘制,API会在合适的时机自动调用该方法  
        });  
    }  
    // setTimeout(function(){   

    // }, 3000);  

    // 绘制  
    function drawLine() {  
        console.log('开始绘制轨迹')  
        var context = canvas.getContext('2d');// 一个 CanvasRenderingContext2D 对象,使用它可以绘制到 Canvas 元素中。  
        canvas.width = map.getSize().width;  
        canvas.height = map.getSize().height;  
        context.globalAlpha=1; // 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。  
        context.clearRect(0, 0, canvas.width, canvas.height); //(x, y, widh, height)清除指定的矩形区域,然后这块区域会变的完全透明。 再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法  
        context.lineWidth = 3; // 线宽。只能是正值。默认是1.0。  
        context.lineCap = 'round'; // 线条末端样式。(butt:线段末端以方形结束;round:线段末端以圆形结束;square:线段末端以方形结束,)  
        // 遍历所有的点  
        var startColor = '';  
        var endColor = '';  
        // 第一个点  
        pointsList[0].containerPos = map.lngLatToContainer([pointsList[0].longitude, pointsList[0].latitude]);  
        for (var j = 1, pointLen = pointsList.length; j < pointLen; j++) {   
             pointsList[j].containerPos = map.lngLatToContainer([pointsList[j].longitude, pointsList[j].latitude]); // 转换为地图识别的格式  
             var startX = pointsList[j - 1].containerPos.x;  //开始点的x坐标  
             var startY = pointsList[j - 1].containerPos.y;  //开始点的y坐标  
             var endX = pointsList[j].containerPos.x;   //结束点的x坐标  
             var endY = pointsList[j].containerPos.y;   //结束点的y坐标  
             context.beginPath(); // 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径  
             context.moveTo(startX, startY);  // 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。  
             context.lineTo(endX, endY); // //绘制一条从当前位置到指定坐标(200, 50)的直线.  
             context.strokeStyle = getColor(pointsList[j].speed); //根据速度来获取颜色---设置图形轮廓的颜色  
             context.stroke(); // 通过线条来绘制图形轮廓  
             //设置渐变  
             startColor = getColor(pointsList[j-1].speed);  
             endColor = getColor(pointsList[j].speed);  
             var grd = context.createLinearGradient(startX, startY, endX, endY); // 创建线性的渐变对象  
             grd.addColorStop(0, startColor);  
             grd.addColorStop(1, endColor);  
             context.strokeStyle = grd;  
             context.stroke();  
        }       
    }  

    /* end */  

</script>  

</body>

</html>



4.finsh.vue页面引入  
// #ifdef APP-PLUS  
        wv = plus.webview.create("","custom-webview",{  
            plusrequire:"none",  
            'uni-app': 'none',   
            top: 0 ,  
            height: 400,  
        })  
        wv.loadURL("/hybrid/html/drawmap.html?userid="+that.userId)  
        var currentWebview = this.$scope.$getAppWebview();   
        currentWebview.append(wv);  
        setTimeout(function() {  
            // console.log(wv.getStyle())  
        }, 1000);  
// #endif
继续阅读 »

**1.将轨迹地图部分放到hybrid->html->文件drawmap.html(也可以外部链接)

  1. 利用uni自带的webview技术,引入地图轨迹**
    代码:
    drawmap.html---
    
    <!DOCTYPE html>  
    <html lang="en">  

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>ImageLayer</title>
<meta id="viewport" name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0" />
<style>
html{
font-size: 24px;
}
{
padding: 0;
margin: 0;
//禁止移动端长按复制文本
-webkit-touch-callout:none;
-webkit-user-select:none;
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
user-select:none;
}
body{
background: beige;
font-family:-apple-system-font,"Helvetica Neue","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
}
.map-container{
margin-top: -60px;
width: 100%;
height: 500px;
/
height: calc(100% - 9rem); */
position: absolute;
min-width: 300px;
background-color: #033447;
background: #033447;
}
.info-box{
position: absolute;
top: 13rem;
right: 0.5rem;
background: rgba(0,0,0,0.3);
height: 2.2rem;
border-radius: 1.1rem;
line-height: 1.4;
padding-left: 0.4rem;
z-index: 999;
transition: margin 0.4s;
-webkit-transition: margin 0.4s;
}
.avatar{
width: 2rem;
height: 2rem;
float: right;
border: 0.1rem solid #fff;
border-radius: 50%;
}
.name{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
color: #fff;
font-size: 0.7rem;
margin-top: 0.3rem;
}
.time{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
font-size: 0.5rem;
color: rgba(255,255,255,0.8);
}
p{
padding-right: 2.5rem;
white-space: nowrap;
text-align: right;
}

</style>  

</head>

<body>
<div class="container">
<div class="info-box">
<img class="avatar" src="./img/applogo.png" alt="">
<p class="name">app名称</p>
<p class="time" id="time">09月10日</p>
</div>

    <div id="map-container" class="map-container"></div>  

</div>  
<script src="https://webapi.amap.com/maps?v=1.4.15&key=8af5729787cab8a4b5930a9b20d141bc"></script>  
<script>  
    var pointsList = [];  
    function getNowFormatDate() {  
      var date = new Date();  
      var seperator1 = "月";  
      var month = date.getMonth() + 1;//月  
      var strDate = date.getDate(); //日  
      if (month >= 1 && month <= 9) {  
        month = "0" + month;  
      }  
      if (strDate >= 0 && strDate <= 9) {  
        strDate = "0" + strDate;  
      }  
      var currentdate = month + seperator1 + strDate + '日';  
        document.getElementById("time").innerHTML = currentdate  
    //   return currentdate;  
    }  
    getNowFormatDate();  
    // 从地址栏中获取训练id;查询  
    var reg = new RegExp("(^|&)"+ 'userid' +"=([^&]*)(&|$)");  
    var r = window.location.search.substr(1).match(reg);  
    // console.log(unescape(r[2]));  
    // var index=window.location.search.fiIndexOf("\-");  
    // obj=obj.substring(index+1,obj.length);  
    var url = '请求经纬度的接口/getDynamicTrack?userid='+unescape(r[2])  
    // 请球数据  
    function getData(url){  
        // console.log("调用"+url)  
        var httpRequest = new XMLHttpRequest();  
        httpRequest.open('GET', url, true);  
        httpRequest.send();  
        /**  
         * 获取数据后的处理程序  
         */  
        httpRequest.onreadystatechange = function () {  
            if (httpRequest.readyState == 4 && httpRequest.status == 200) {  
                var json = httpRequest.responseText;//获取到json字符串,还需解析  
                pointsList = JSON.parse(json).data;  
                // console.log(pointsList);  
                preProcessList();  
                setCustomLayer();  
            }  
        };  
    }  
    getData(url);  

    var mapZooms = [3, 18]  
    var map = new AMap.Map('map-container', {  
        mapStyle: 'https://amap://styles/blue',  
        resizeEnable: true, //是否监控地图容器尺寸变化  
        dragEnable: false,  //地图是否可通过鼠标拖拽平移,  
        doubleClickZoom: false, //地图是否可通过双击鼠标放大地图  
        keyboardEnable: false, // 地图是否可通过键盘控制,默认为true方向键控制地图平移,  
        scrollWheel: false, // 地图是否可通过鼠标滚轮缩放浏览  
        touchZoom: false, // 地图在移动终端上是否可通过多点触控缩放浏览地图  
        zooms: mapZooms  
    });  
    map.setFeatures(['bg','road']); // 多个种类要素显示  
    // 监听容器变化  
    // map.on('zoomend', function(ev) {  
    //   console.log('缩放等级变化了-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    //   setCustomLayer();  
    // });  
    // map.on('click', function(ev) {  
    //   console.log('鼠标点击-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('moveend', function(ev) {  
    //   console.log('地图中心点变化-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom())  
    // });  
    // map.on('touchend', function(ev) {  
    //   console.log('手机触摸-----------')  
    //   console.log(map.getSize().width)  
    //   console.log(map.getSize().height)  
    //   console.log('获取地图等级'+map.getZoom());  
    //   setCustomLayer();  
    // });  
    // 获取经纬度(这里处理数据)--start  
    /*  
     * 添加Canvas图层  
     */  

    var polylineArr = [];  
    var colorSteps = 10; // 颜色等级  
    var maxSpeed = 0;  
    var minSpeed = 0;  
    var averageSpeed = 0; // 平均速度  
    var totalSpeed = 0;  
    // setAvgSpeed();  
    var speedTopStep = 0;  
    var speedBellowStep = 0;  
    function setPoints(){  
        var makers = [];  
        var markersArg = [{  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-start.png',  
            position: [pointsList[0].longitude, pointsList[0].latitude]  
        }, {  
            icon: 'http://statics.oulafen.com/h5_draw_map_color/image/point-end.png',  
            position: [pointsList[pointsList.length-1].longitude, pointsList[pointsList.length-1].latitude]  
        }];  
        markersArg.forEach(function(marker) {  
            var maker = new AMap.Marker({  
                map: map,  
                position: [marker.position[0], marker.position[1]],  
                icon: new AMap.Icon({  
                    size: new AMap.Size(20, 20),  //图标大小  
                    image: marker.icon,  
                    imageSize: new AMap.Size(20,20)  
                }),  
                offset: new AMap.Pixel(-10, -10)  
            });  
        });  
    }  
    function preProcessList() {  
        for (var i = 0; i < pointsList.length; i++) {  
            var tmp = [];  
            var speed = pointsList[i].speed;  
            tmp[0] = pointsList[i].longitude;  
            tmp[1] = pointsList[i].latitude;  
            // 数组累加求平均data.reduce((totalPrice, item) => totalPrice + item.price, 0);  
            totalSpeed += speed  
            polylineArr.push(tmp);  
            // 获取最高速度和最低速度  
            if (speed > -1 && speed > maxSpeed) {  
                maxSpeed = speed;  
            }  
            if (speed > -1 && speed < minSpeed) {  
                minSpeed = speed;  
            }  

        }  
        averageSpeed = totalSpeed/pointsList.length;  
        // console.log('平均速度'+averageSpeed)  
        speedTopStep = (maxSpeed - averageSpeed) / colorSteps;  
        speedBellowStep = (averageSpeed - minSpeed) / colorSteps;  
        // console.log('最大速度颜色等级'+speedTopStep)  
        // console.log('最小速度颜色等级'+speedBellowStep)  
        /** 使用高德地图自带的覆盖方法, 自动定位并缩放, 但不显示-start **/  
        // console.log('开始定位和自适应屏幕')  
        var polyline = new AMap.Polyline({  
            path: polylineArr,  
            strokeColor: "#FF33FF", // //线颜色  
            strokeOpacity: 0,  
            strokeWeight: 0,  
            zIndex:0  
         });  
        polyline.setMap(map);  
        // 绘制开始点和结束点  
        setPoints();  
        // setPoints(); // 绘制标记点--每1公里标记一下  
        map.setFitView(); //屏幕自适应-// 自动适配到合适视野范围// 无参数,默认包括所有覆盖物的情况  
        /* end */  
        // console.log('结束定位和自适应屏幕')  
    }  

    // 绘制标记点  

    /* end */  

    /* 不同颜色的轨迹-start */  
    // 处理颜色的方法  

    var bellowColors = ["#ffff00", "#fff500", "#ffe800", "#ffd900", "#ffc700", "#ffb400", "#ffa000", "#ff8a00", "#ff6000", "#ff2700", "#ff0000"];  
    var topColors = ["#ffff00", "#eeff00", "#d7ff00", "#bcff00", "#9fff00", "#7fff00", "#5fff00", "#41ff00", "#27ff00", "#10ff00", "#00ff00"];  
    function getColor(speed) {  
        if(speed < 0 ){  
            return '#ffff00';  
        }  
        var subSpeed = speed - averageSpeed;  
        if (subSpeed > 0) {  
            if(speedTopStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(subSpeed / speedTopStep);  
            // console.log('等级'+level)  
            return topColors[level];  
        } else {  
            if(speedBellowStep===0){  
                return topColors[10];  
            }  
            var level = parseInt(Math.abs(subSpeed) / speedBellowStep);  
            return bellowColors[level];  
        }  
    }  

    //  初始化canvas  
    var canvas = '';  
    function setCustomLayer() {  
        // console.log('初始化canvas')  
        // CustomLayer类(插件)--自定义图层  
        map.plugin(['AMap.CustomLayer'], function () {  
            canvas = document.createElement('canvas');  
            var customLayer = new AMap.CustomLayer(canvas, {  
                zIndex: 99,  
                zooms: map.getZoom()  
            });  
            customLayer.setMap(map); // 设置图层所属的地图对象,传入null时从当前地图移除  
            customLayer.render = drawLine(); // 初始化完成时候,开发者需要给该图层设定render方法,该方法需要实现图层的绘制,API会在合适的时机自动调用该方法  
        });  
    }  
    // setTimeout(function(){   

    // }, 3000);  

    // 绘制  
    function drawLine() {  
        console.log('开始绘制轨迹')  
        var context = canvas.getContext('2d');// 一个 CanvasRenderingContext2D 对象,使用它可以绘制到 Canvas 元素中。  
        canvas.width = map.getSize().width;  
        canvas.height = map.getSize().height;  
        context.globalAlpha=1; // 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。  
        context.clearRect(0, 0, canvas.width, canvas.height); //(x, y, widh, height)清除指定的矩形区域,然后这块区域会变的完全透明。 再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法  
        context.lineWidth = 3; // 线宽。只能是正值。默认是1.0。  
        context.lineCap = 'round'; // 线条末端样式。(butt:线段末端以方形结束;round:线段末端以圆形结束;square:线段末端以方形结束,)  
        // 遍历所有的点  
        var startColor = '';  
        var endColor = '';  
        // 第一个点  
        pointsList[0].containerPos = map.lngLatToContainer([pointsList[0].longitude, pointsList[0].latitude]);  
        for (var j = 1, pointLen = pointsList.length; j < pointLen; j++) {   
             pointsList[j].containerPos = map.lngLatToContainer([pointsList[j].longitude, pointsList[j].latitude]); // 转换为地图识别的格式  
             var startX = pointsList[j - 1].containerPos.x;  //开始点的x坐标  
             var startY = pointsList[j - 1].containerPos.y;  //开始点的y坐标  
             var endX = pointsList[j].containerPos.x;   //结束点的x坐标  
             var endY = pointsList[j].containerPos.y;   //结束点的y坐标  
             context.beginPath(); // 新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径  
             context.moveTo(startX, startY);  // 把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。  
             context.lineTo(endX, endY); // //绘制一条从当前位置到指定坐标(200, 50)的直线.  
             context.strokeStyle = getColor(pointsList[j].speed); //根据速度来获取颜色---设置图形轮廓的颜色  
             context.stroke(); // 通过线条来绘制图形轮廓  
             //设置渐变  
             startColor = getColor(pointsList[j-1].speed);  
             endColor = getColor(pointsList[j].speed);  
             var grd = context.createLinearGradient(startX, startY, endX, endY); // 创建线性的渐变对象  
             grd.addColorStop(0, startColor);  
             grd.addColorStop(1, endColor);  
             context.strokeStyle = grd;  
             context.stroke();  
        }       
    }  

    /* end */  

</script>  

</body>

</html>



4.finsh.vue页面引入  
// #ifdef APP-PLUS  
        wv = plus.webview.create("","custom-webview",{  
            plusrequire:"none",  
            'uni-app': 'none',   
            top: 0 ,  
            height: 400,  
        })  
        wv.loadURL("/hybrid/html/drawmap.html?userid="+that.userId)  
        var currentWebview = this.$scope.$getAppWebview();   
        currentWebview.append(wv);  
        setTimeout(function() {  
            // console.log(wv.getStyle())  
        }, 1000);  
// #endif
收起阅读 »

hbuilder的几个功能细节的建议

本人使用过微软系列的vs各种, phpstrom,webstrom,notepad++,安卓as, java的eclipse 大概主流的这些吧,说这些是因为我想谦卑的希望xbuilder 能在细节上添加或者改进一下,
或许用起来更流畅,当然当前的一些功能也非常的具有其他软件没有的.
第一点: 右键 复制 剪贴 只需要这四个字就可以,有时候用右键时 ,找复制 还需要思考
哦~~这文章输入框还不能直接粘贴 qq截切过来的图.....我还是不提建议了...
还是说完吧
支持左侧拖js 文件到文档,可以把目录引用到代码里,多方便.
我看很多java 家族左侧文件名有个通病,就是点击两次不能改名,还得右键重命名,这个功能也挺不错的
千篇一律的ctrl+鼠标左键找 源头,为何hbuilder 选择用alt 加鼠标左键.

继续阅读 »

本人使用过微软系列的vs各种, phpstrom,webstrom,notepad++,安卓as, java的eclipse 大概主流的这些吧,说这些是因为我想谦卑的希望xbuilder 能在细节上添加或者改进一下,
或许用起来更流畅,当然当前的一些功能也非常的具有其他软件没有的.
第一点: 右键 复制 剪贴 只需要这四个字就可以,有时候用右键时 ,找复制 还需要思考
哦~~这文章输入框还不能直接粘贴 qq截切过来的图.....我还是不提建议了...
还是说完吧
支持左侧拖js 文件到文档,可以把目录引用到代码里,多方便.
我看很多java 家族左侧文件名有个通病,就是点击两次不能改名,还得右键重命名,这个功能也挺不错的
千篇一律的ctrl+鼠标左键找 源头,为何hbuilder 选择用alt 加鼠标左键.

收起阅读 »

关于uni.downloadFile与uni.saveFiled文件名不一,解决方案思路

uni.downloadFile 下载的文件是临时的,名字是下载文件的名字
uni.saveFiled 本地保存 ,名字是时间戳

实现缓存:如何让保存到 本地的文件 和 下载文件的原名 一样 (好像两个都不支持下载文件到指定位置和改变下载文件名)

解决方案思路
可以考虑uni.setStorageSync(本地缓存) 同时缓存两个值 当下载文件名改变时 才进行下一步操作

有好的方案 大家可以进行讨论

继续阅读 »

uni.downloadFile 下载的文件是临时的,名字是下载文件的名字
uni.saveFiled 本地保存 ,名字是时间戳

实现缓存:如何让保存到 本地的文件 和 下载文件的原名 一样 (好像两个都不支持下载文件到指定位置和改变下载文件名)

解决方案思路
可以考虑uni.setStorageSync(本地缓存) 同时缓存两个值 当下载文件名改变时 才进行下一步操作

有好的方案 大家可以进行讨论

收起阅读 »

开发者中心Unipush分组对比优化介绍

unipush

消息推送分组对比功能优化介绍

分组测试功能支持在同一个推送计划中,添加2-5条文案/人群进行对比测试,通过对各测试组的到达、展示、点击数据的监测分析,筛选出最优文案/人群,并支持实时及定时地按最优测试组补发剩余用户,提高通知消息点击率,助力运营提效。

【更新简述】

  1. 支持实时及定时按最优测试组补发剩余用户
  2. 测试分组:从最多支持2组增加到最多支持5组测试
  3. 页面改版,模块划分为:测试内容、推送设置、Android配置、ios配置
  4. 测试模式:仍旧支持“对比通知文案”和“对比用户群体”
  5. 测试目标平台:在支持Android的基础上,新增支持iOS
  6. 测试人群:在支持随机用户、标签用户、用户分组的基础上,新增支持CID用户及别名用户
  7. 测试推送设置:在支持设置消息有效时长、定时的基础上,新增支持定速、厂商策略
  8. 新增Android配置:支持设置通知渠道模板、后续动作
  9. 新增iOS配置:支持设置badge,sound,附加字段
  10. 新增测试任务管理:支持取消测试、停止测试、停止补发

备注:AB测试不适合进行大范围推送场景,为了达到精准推送的目的,每组参测人数建议大于10000人,所有组合计参测人数最大值是150000人。测试组测试人数建议相同

【更新详情】

1.支持实时及定时按最优测试组补发剩余用户

新增对分组测试的到达、展示、点击等后效数据进行实时监测分析,筛选出最优测试组,支持实时及定时的自动或手动补发,将测试得到的最佳文案/人群,推送给剩余的目标用户,优化推送的点击效果。

(1)自动补发

①根据测试的后效数据分析,自动筛选出最佳文案/人群,并直接推送给剩余的目标用户,优化推送的点击效果。

②测试的后效数据分析主要通过点击率实现:点击率=点击数/到达率。

③可设置定时补发功能,也就是设置测试消息下发后、自动补发的生效起始时间,支持15分钟-12小时。

④可设置默认文案功能,用于在多文案/人群测试中,在多文案中选择出其中一个文案作为默认文案,当出现点击率无法及时统计而导致系统无法自动选出最佳文案/人群补发时,发送默认文案。

(2)手动补发

在【数据统计】-【推送记录】-【分组测试记录】中查看分组测试的详情及效果数据,自行手动选择补发组。

(3)【测试人群】选择【CID用户】和【别名用户】时,不支持补发功能。

(4)当在【按最优测试结果补发剩余用户】选择【自动补发】或【手动补发】时,剩余用户的补发推送完成后,会在【推送记录】-【通知消息】的记录中,生成一条相应的推送记录,可查看推送详情及效果数据。

2.测试分组:从最多支持2组增加到最多支持5组测试

3.页面改版,模块划分为:测试内容、推送设置、Android配置、iOS配置

4.测试模式:仍旧支持“对比通知文案”和“对比用户群体”

(1)对比通知文案:向多组属性相似的人群推送多个不同的文案,测试哪个文案更受用户欢迎;

(2)对比用户群体:将相同的文案内容推送给多组属性不同的人群,测试该文案内容更受哪类人群欢迎。

5.测试目标平台:在支持Android的基础上,新增支持iOS

6.测试人群:在支持随机用户、标签用户、用户分组的基础上,新增支持CID用户及别名用户

7.测试推送设置:在支持设置消息有效时长、定时的基础上,新增支持定速、厂商策略

8.新增Android配置:支持设置通知渠道模板、后续动作

9.新增iOS配置:支持设置badge、sound、附加字段

10.新增测试任务管理:支持取消测试、停止测试、停止补发

(1)测试任务定时,还未开始下发测试组前,操作支持:取消测试、取消补发

(2)已下发测试任务,还未开始补发剩余用户前,操作支持:停止测试、取消补发

继续阅读 »

消息推送分组对比功能优化介绍

分组测试功能支持在同一个推送计划中,添加2-5条文案/人群进行对比测试,通过对各测试组的到达、展示、点击数据的监测分析,筛选出最优文案/人群,并支持实时及定时地按最优测试组补发剩余用户,提高通知消息点击率,助力运营提效。

【更新简述】

  1. 支持实时及定时按最优测试组补发剩余用户
  2. 测试分组:从最多支持2组增加到最多支持5组测试
  3. 页面改版,模块划分为:测试内容、推送设置、Android配置、ios配置
  4. 测试模式:仍旧支持“对比通知文案”和“对比用户群体”
  5. 测试目标平台:在支持Android的基础上,新增支持iOS
  6. 测试人群:在支持随机用户、标签用户、用户分组的基础上,新增支持CID用户及别名用户
  7. 测试推送设置:在支持设置消息有效时长、定时的基础上,新增支持定速、厂商策略
  8. 新增Android配置:支持设置通知渠道模板、后续动作
  9. 新增iOS配置:支持设置badge,sound,附加字段
  10. 新增测试任务管理:支持取消测试、停止测试、停止补发

备注:AB测试不适合进行大范围推送场景,为了达到精准推送的目的,每组参测人数建议大于10000人,所有组合计参测人数最大值是150000人。测试组测试人数建议相同

【更新详情】

1.支持实时及定时按最优测试组补发剩余用户

新增对分组测试的到达、展示、点击等后效数据进行实时监测分析,筛选出最优测试组,支持实时及定时的自动或手动补发,将测试得到的最佳文案/人群,推送给剩余的目标用户,优化推送的点击效果。

(1)自动补发

①根据测试的后效数据分析,自动筛选出最佳文案/人群,并直接推送给剩余的目标用户,优化推送的点击效果。

②测试的后效数据分析主要通过点击率实现:点击率=点击数/到达率。

③可设置定时补发功能,也就是设置测试消息下发后、自动补发的生效起始时间,支持15分钟-12小时。

④可设置默认文案功能,用于在多文案/人群测试中,在多文案中选择出其中一个文案作为默认文案,当出现点击率无法及时统计而导致系统无法自动选出最佳文案/人群补发时,发送默认文案。

(2)手动补发

在【数据统计】-【推送记录】-【分组测试记录】中查看分组测试的详情及效果数据,自行手动选择补发组。

(3)【测试人群】选择【CID用户】和【别名用户】时,不支持补发功能。

(4)当在【按最优测试结果补发剩余用户】选择【自动补发】或【手动补发】时,剩余用户的补发推送完成后,会在【推送记录】-【通知消息】的记录中,生成一条相应的推送记录,可查看推送详情及效果数据。

2.测试分组:从最多支持2组增加到最多支持5组测试

3.页面改版,模块划分为:测试内容、推送设置、Android配置、iOS配置

4.测试模式:仍旧支持“对比通知文案”和“对比用户群体”

(1)对比通知文案:向多组属性相似的人群推送多个不同的文案,测试哪个文案更受用户欢迎;

(2)对比用户群体:将相同的文案内容推送给多组属性不同的人群,测试该文案内容更受哪类人群欢迎。

5.测试目标平台:在支持Android的基础上,新增支持iOS

6.测试人群:在支持随机用户、标签用户、用户分组的基础上,新增支持CID用户及别名用户

7.测试推送设置:在支持设置消息有效时长、定时的基础上,新增支持定速、厂商策略

8.新增Android配置:支持设置通知渠道模板、后续动作

9.新增iOS配置:支持设置badge、sound、附加字段

10.新增测试任务管理:支持取消测试、停止测试、停止补发

(1)测试任务定时,还未开始下发测试组前,操作支持:取消测试、取消补发

(2)已下发测试任务,还未开始补发剩余用户前,操作支持:停止测试、取消补发

收起阅读 »

UDP 通信 (android)

            var DatagramPacket;  
            var DatagramSocket;  
            var InetAddress;   
            var JString;  
            var socket;  
            var port = 10000;  
            var timeout = 6000;  

            try {   
                DatagramPacket = plus.android.importClass('java.net.DatagramPacket');  
                DatagramSocket = plus.android.importClass('java.net.DatagramSocket');  
                InetAddress = plus.android.importClass('java.net.InetAddress');  
                JString = plus.android.importClass('java.lang.String');  

                if (DatagramSocket == undefined) {     
                    return;  
                }  

                socket = new DatagramSocket(port);  

                // 设置接收超时时长  
                socket.setSoTimeout(timeout);  

                // 创建广播地址  
                var broadcastAddress = InetAddress.getByName('255.255.255.255');  

                // 发送广播数据  
                var sendData = this.stringToByte("广播的数据");  
                var sendPacket = new DatagramPacket(sendData, sendData.length, broadcastAddress, port);  
                socket.send(sendPacket);  

                // 接收数据  
                var isReceive = true;    
                while(isReceive){  
                    try{  
                        // 设置接收缓存,需要用0填充,否则为 null 无法接收。   
                        var buffer = new Array(1024).fill(0);    
                        var packet = new DatagramPacket(buffer, buffer.length);  

                        socket.receive(packet);    

                        var data = new JString(packet.getData()).trim();   
                        if (data.length == 0){  
                            // 接收超时,结束接收  
                            isReceive = false;  
                        }else{   
                            console.log('===========收到数据============',data);  
                        }   
                    }catch(ex){   
                        isReceive = false;  
                    }  
                }         
            } catch (ex) {   
                console.log('===========出错了============',ex);  
            } finally {  
                if (socket != undefined){    
                    socket.close();  
                }     
            }
继续阅读 »
            var DatagramPacket;  
            var DatagramSocket;  
            var InetAddress;   
            var JString;  
            var socket;  
            var port = 10000;  
            var timeout = 6000;  

            try {   
                DatagramPacket = plus.android.importClass('java.net.DatagramPacket');  
                DatagramSocket = plus.android.importClass('java.net.DatagramSocket');  
                InetAddress = plus.android.importClass('java.net.InetAddress');  
                JString = plus.android.importClass('java.lang.String');  

                if (DatagramSocket == undefined) {     
                    return;  
                }  

                socket = new DatagramSocket(port);  

                // 设置接收超时时长  
                socket.setSoTimeout(timeout);  

                // 创建广播地址  
                var broadcastAddress = InetAddress.getByName('255.255.255.255');  

                // 发送广播数据  
                var sendData = this.stringToByte("广播的数据");  
                var sendPacket = new DatagramPacket(sendData, sendData.length, broadcastAddress, port);  
                socket.send(sendPacket);  

                // 接收数据  
                var isReceive = true;    
                while(isReceive){  
                    try{  
                        // 设置接收缓存,需要用0填充,否则为 null 无法接收。   
                        var buffer = new Array(1024).fill(0);    
                        var packet = new DatagramPacket(buffer, buffer.length);  

                        socket.receive(packet);    

                        var data = new JString(packet.getData()).trim();   
                        if (data.length == 0){  
                            // 接收超时,结束接收  
                            isReceive = false;  
                        }else{   
                            console.log('===========收到数据============',data);  
                        }   
                    }catch(ex){   
                        isReceive = false;  
                    }  
                }         
            } catch (ex) {   
                console.log('===========出错了============',ex);  
            } finally {  
                if (socket != undefined){    
                    socket.close();  
                }     
            }
收起阅读 »

rsa 加密(已经做成js 可以随便使用)默认支持大数据分块加密,支持私玥加密,公玥解密-公玥加密,私玥解密.

基于jsencrypt 的修改.
直接引用 import RSAEncrypt from '@/common/rsa.js';
中文大数据加密需要把数据转换位base64

var rsa=new RSA();  
//生成私玥  
var pri=rsa.Key.private();  
//生成公玥  
var pub=rsa.Key.public();  
//待加密的数据,中文base64之后的.自己base64解码后就是原始数据,这里已经超过512了.  
var estr="IOi/kOihjOWIsHByb21pc2UoKS50aGVu77yM5Y+R546w5pivcHJvbWlzZeWvueixoe+8jOaOqOWIsOW+ruS7u+WKoemYn+WIl+S4rQ0KDQomIzE4Mzsg5omn6KGMY29uc29sZS5sb2coJiMzOTtlbmQgYXN5bmMmIzM5OykNCg0KJiMxODM7IHRlc3Qx5aSW6Z2i55qE5ZCM5q2l5Luj56CB5omn6KGM57uT5p2f5ZCO77yM5Zue5YiwdGVzdDHkuK3vvIxjb25zb2xlLmxvZyhhd2FpdCB0ZXN0MigpKeaJp+ihjOWujOaIkOWQjui/lOWbnlByb21pc2UgeyZsdDtyZXNvbHZlZCZndDs6ICJyZXR1cm4gdGVzdDIgdmFsdWUife+8jOaYr3Byb21pc2Xlr7nosaHvvIzmjqjliLDlvq7ku7vliqHpmJ/liJfkuK0NCg0KJiMxODM7IOatpOaXtuesrOS4gOS4quWuj+S7u+WKoee7k+adn++8jOaJp+ihjOaJgOacieeahOW+ruS7u+WKoe+8jOWboOS4uuW+ruS7u+WKoemYn+WIl+WFiOi/m+WFiOWHuu+8jOaJgOS7peWFiOaJp+ihjGNvbnNvbGUubG9nKCYjMzk7cHJvbWlzZTImIzM5OynvvIzlkI7miafooYxjb25zb2xlLmxvZygmIzM5O3JldHVybiB0ZXN0MiB2YWx1ZSYjMzk7KQ0KDQomIzE4Mzsg5omn6KGMdGVzdDLlrozmiJDlkI7vvIzlkI7pnaLnmoTku6PnoIHkuI3lho3pmLvloZ7vvIzmiafooYxjb25zb2xlLmxvZygmIzM5O2VuZCB0ZXN0MSYjMzk7KTsNCg0KJiMxODM7IOaJp+ihjOS4i+S4quWuj+S7u+WKoe+8jOWNs+aJp+ihjGNvbnNvbGUubG9nKCYjMzk7c2V0VGltZW91dCYjMzk7KTsNCg0K6KGl5YWF5LiL5pyJ5YWz5a6P5Lu75Yqh5ZKM5b6u5Lu75Yqh55qE55+l6K+G";  

//私玥加密  
console.log("私玥加密");  
var ec=rsa.encrypt.private(estr,pri);  
console.log(ec);  
//公玥解密  
console.log("公玥解密");  
var dc=rsa.decrypt.public(ec,pub);  
console.log(dc);  

//公玥加密  
console.log("公玥加密");  
var ecp=rsa.encrypt.public(estr,pub);  
console.log(ecp);  
//私玥解密  
console.log("私玥解密");  
var dcp=rsa.decrypt.private(ecp,pri);  
console.log(dcp);  

附近里面2个js 一个rsa.js 可以直接使用.
另一个是 修改过后的jsencrypt.js
导入到 common文件夹 引入 rsa.js 就OK

欢迎技术交流.

继续阅读 »

基于jsencrypt 的修改.
直接引用 import RSAEncrypt from '@/common/rsa.js';
中文大数据加密需要把数据转换位base64

var rsa=new RSA();  
//生成私玥  
var pri=rsa.Key.private();  
//生成公玥  
var pub=rsa.Key.public();  
//待加密的数据,中文base64之后的.自己base64解码后就是原始数据,这里已经超过512了.  
var estr="IOi/kOihjOWIsHByb21pc2UoKS50aGVu77yM5Y+R546w5pivcHJvbWlzZeWvueixoe+8jOaOqOWIsOW+ruS7u+WKoemYn+WIl+S4rQ0KDQomIzE4Mzsg5omn6KGMY29uc29sZS5sb2coJiMzOTtlbmQgYXN5bmMmIzM5OykNCg0KJiMxODM7IHRlc3Qx5aSW6Z2i55qE5ZCM5q2l5Luj56CB5omn6KGM57uT5p2f5ZCO77yM5Zue5YiwdGVzdDHkuK3vvIxjb25zb2xlLmxvZyhhd2FpdCB0ZXN0MigpKeaJp+ihjOWujOaIkOWQjui/lOWbnlByb21pc2UgeyZsdDtyZXNvbHZlZCZndDs6ICJyZXR1cm4gdGVzdDIgdmFsdWUife+8jOaYr3Byb21pc2Xlr7nosaHvvIzmjqjliLDlvq7ku7vliqHpmJ/liJfkuK0NCg0KJiMxODM7IOatpOaXtuesrOS4gOS4quWuj+S7u+WKoee7k+adn++8jOaJp+ihjOaJgOacieeahOW+ruS7u+WKoe+8jOWboOS4uuW+ruS7u+WKoemYn+WIl+WFiOi/m+WFiOWHuu+8jOaJgOS7peWFiOaJp+ihjGNvbnNvbGUubG9nKCYjMzk7cHJvbWlzZTImIzM5OynvvIzlkI7miafooYxjb25zb2xlLmxvZygmIzM5O3JldHVybiB0ZXN0MiB2YWx1ZSYjMzk7KQ0KDQomIzE4Mzsg5omn6KGMdGVzdDLlrozmiJDlkI7vvIzlkI7pnaLnmoTku6PnoIHkuI3lho3pmLvloZ7vvIzmiafooYxjb25zb2xlLmxvZygmIzM5O2VuZCB0ZXN0MSYjMzk7KTsNCg0KJiMxODM7IOaJp+ihjOS4i+S4quWuj+S7u+WKoe+8jOWNs+aJp+ihjGNvbnNvbGUubG9nKCYjMzk7c2V0VGltZW91dCYjMzk7KTsNCg0K6KGl5YWF5LiL5pyJ5YWz5a6P5Lu75Yqh5ZKM5b6u5Lu75Yqh55qE55+l6K+G";  

//私玥加密  
console.log("私玥加密");  
var ec=rsa.encrypt.private(estr,pri);  
console.log(ec);  
//公玥解密  
console.log("公玥解密");  
var dc=rsa.decrypt.public(ec,pub);  
console.log(dc);  

//公玥加密  
console.log("公玥加密");  
var ecp=rsa.encrypt.public(estr,pub);  
console.log(ecp);  
//私玥解密  
console.log("私玥解密");  
var dcp=rsa.decrypt.private(ecp,pri);  
console.log(dcp);  

附近里面2个js 一个rsa.js 可以直接使用.
另一个是 修改过后的jsencrypt.js
导入到 common文件夹 引入 rsa.js 就OK

欢迎技术交流.

收起阅读 »

uni-app如何支持npm分包

分包 uniapp

一、背景

我们美家移动端业务使用uni-app实现一码多端,目前上线了APP(包括Android和ios),微信小程序、百度小程序和H5。

从去年6月份以来,随着业务的快速发展,从最初单一产品线,不断孵化出包括逛逛美家、美家工作台APP、美家旺铺、美家直播Live、智能名片多个比较庞大的产品体系。之间存在大量可复用的模块、组件和页面,于是搭建npm私有仓库,目前接近50个包,接近400个版本,周下载量接近140次,接下来会加快节奏,将更多公共资源整合进来。

uni-app项目引入npm包(即使是分包页面引入),打包微信小程序后,都会统一编译到小程序根目录的 node-modules 文件夹。其中,我们有个自主开发的直播模块,打包后,接近1M大小,多个项目引入后,导致主包过大,如果粗暴将node-modules设为分包,会影响主包的其他页面。咨询uni-app相关开发人员,得到的回复是:确实不支持npm分包,处理起来会比较复杂,暂时也没有支持计划。为了满足业务需要,只能自力更生!

二、思路

将需要分包的npm包复制一份到分包目录,改成相对路径引入,这样打包之后,引入的npm包就会在分包里面。很明显,这种事情不能靠手工去处理。uni-app底层是通过webpack打包,可以通过开发插件解决问题。想到两种方案:

  1. 添加JSON配置文件,手动维护需要分包的npm包和引用文件路径的映射关系,分包页面通过相对路径引入npm包,在webpack解析模块前,比如 beforeRun阶段,根据配置,将npm包自动copy到引用文件所在的目录。

  2. DSL阶段:约定一种引入分包的import语法。AST阶段:添加Babel模块解析器解析分包语法,再将解析的npm包copy到引用文件所在目录,同时模块路径改成相对路径。

三、对比

  • 方案1,配置繁琐,而且引用和配置存在松散耦合关系,随着项目的不断迭代,容易导致JSON配置文件越来越臃肿和冗余,不好维护。
  • 方案2,语法层控制,不存在松散耦合,是否需要分包,切换语法就好,添加删除引入,不需要修改配置,不会出现历史包袱问题。

四、实现

本着约定大于配置的原则,最终我们采用方案2。

约定

分包import语法:

import module from "subpack:module"

模块解析器

基于babel-plugin-module-resolver模块解析插件进行扩展。

1.安装依赖

npm install babel-plugin-module-resolver --save-dev 

2.配置babel.config.js

1、通过cli构建的项目:在项目根目录下。
2、通过HBuilderX创建的项目:安装目录下HBuilderX/plugins/uniapp-cli/babel.config.js

const fs = require('fs-extra');  
const path = require("path");  
const plugins = [  
  ["module-resolver", {  
    "root": ["./"],  
    resolvePath(sourcePath, currentFile, opts) {  
      // 解析模块路径  
      const [scope,pakageName] = sourcePath.split(":");  
      // 判断是否为分包模块  
      if(scope == "subpack"){  
        // 如果是,将npm包copy到当前引入文件所在目录  
        fs.copySync(`./node_modules/${pakageName}/`, `${path.dirname(currentFile)}/${pakageName}`);  
        // 改成相对路径引用  
        sourcePath = `./${pakageName}`  

      }  
      return sourcePath;  
    }  

  }]  
]  
...

五、写在最后

通过上述方案,暂时可以满足我们目前的业务需求。还存在两个问题:

  1. 通过HBuilderX创建的项目,根目录配置 babel.config.js无效,需要去安装目录下手动配置,版本升级,会被覆盖。
  2. 约定分包引入的npm包,其引入的npm包,是否需要一起做分包处理,目前我们是没做的,还在权衡。

如果大家有其他思路,欢迎一起沟通讨论。最后感谢一下优秀的Dcloud团队

原文地址

继续阅读 »

一、背景

我们美家移动端业务使用uni-app实现一码多端,目前上线了APP(包括Android和ios),微信小程序、百度小程序和H5。

从去年6月份以来,随着业务的快速发展,从最初单一产品线,不断孵化出包括逛逛美家、美家工作台APP、美家旺铺、美家直播Live、智能名片多个比较庞大的产品体系。之间存在大量可复用的模块、组件和页面,于是搭建npm私有仓库,目前接近50个包,接近400个版本,周下载量接近140次,接下来会加快节奏,将更多公共资源整合进来。

uni-app项目引入npm包(即使是分包页面引入),打包微信小程序后,都会统一编译到小程序根目录的 node-modules 文件夹。其中,我们有个自主开发的直播模块,打包后,接近1M大小,多个项目引入后,导致主包过大,如果粗暴将node-modules设为分包,会影响主包的其他页面。咨询uni-app相关开发人员,得到的回复是:确实不支持npm分包,处理起来会比较复杂,暂时也没有支持计划。为了满足业务需要,只能自力更生!

二、思路

将需要分包的npm包复制一份到分包目录,改成相对路径引入,这样打包之后,引入的npm包就会在分包里面。很明显,这种事情不能靠手工去处理。uni-app底层是通过webpack打包,可以通过开发插件解决问题。想到两种方案:

  1. 添加JSON配置文件,手动维护需要分包的npm包和引用文件路径的映射关系,分包页面通过相对路径引入npm包,在webpack解析模块前,比如 beforeRun阶段,根据配置,将npm包自动copy到引用文件所在的目录。

  2. DSL阶段:约定一种引入分包的import语法。AST阶段:添加Babel模块解析器解析分包语法,再将解析的npm包copy到引用文件所在目录,同时模块路径改成相对路径。

三、对比

  • 方案1,配置繁琐,而且引用和配置存在松散耦合关系,随着项目的不断迭代,容易导致JSON配置文件越来越臃肿和冗余,不好维护。
  • 方案2,语法层控制,不存在松散耦合,是否需要分包,切换语法就好,添加删除引入,不需要修改配置,不会出现历史包袱问题。

四、实现

本着约定大于配置的原则,最终我们采用方案2。

约定

分包import语法:

import module from "subpack:module"

模块解析器

基于babel-plugin-module-resolver模块解析插件进行扩展。

1.安装依赖

npm install babel-plugin-module-resolver --save-dev 

2.配置babel.config.js

1、通过cli构建的项目:在项目根目录下。
2、通过HBuilderX创建的项目:安装目录下HBuilderX/plugins/uniapp-cli/babel.config.js

const fs = require('fs-extra');  
const path = require("path");  
const plugins = [  
  ["module-resolver", {  
    "root": ["./"],  
    resolvePath(sourcePath, currentFile, opts) {  
      // 解析模块路径  
      const [scope,pakageName] = sourcePath.split(":");  
      // 判断是否为分包模块  
      if(scope == "subpack"){  
        // 如果是,将npm包copy到当前引入文件所在目录  
        fs.copySync(`./node_modules/${pakageName}/`, `${path.dirname(currentFile)}/${pakageName}`);  
        // 改成相对路径引用  
        sourcePath = `./${pakageName}`  

      }  
      return sourcePath;  
    }  

  }]  
]  
...

五、写在最后

通过上述方案,暂时可以满足我们目前的业务需求。还存在两个问题:

  1. 通过HBuilderX创建的项目,根目录配置 babel.config.js无效,需要去安装目录下手动配置,版本升级,会被覆盖。
  2. 约定分包引入的npm包,其引入的npm包,是否需要一起做分包处理,目前我们是没做的,还在权衡。

如果大家有其他思路,欢迎一起沟通讨论。最后感谢一下优秀的Dcloud团队

原文地址

收起阅读 »

gin-vue-admin基于vue和gin框架的快速后台管理系统脚手架 让你的开发如虎添翼

Messaging Vue

项目文档

在线文档 : https://www.gin-vue-admin.com/

开发教学

1. 基本介绍

1.1 项目介绍

在线预览

测试用户名:admin

测试密码:123456

Gin-vue-admin是一个基于vue和gin开发的全栈前后端分离的后台管理系统,集成jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能,提供多种示例文件,让您把更多时间专注在业务开发上。

1.2 贡献指南

Hi! 首先感谢你使用 gin-vue-admin。

Gin-vue-admin 是一套为后台管理平台准备的一整套前后端分离架构式的开源框架,旨在快速搭建后台管理系统。

Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-admin 贡献代码或提供建议,请阅读以下内容。

1.2.1 Issue 规范

  • issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。如果你在使用时产生了疑问,请到 Slack 或 Gitter 里咨询。

  • 在提交 issue 之前,请搜索相关内容是否已被提出。

1.2.2 Pull Request 规范

  • 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。

  • commit 信息要以[文件名]: 描述信息 的形式填写,例如 README.md: fix xxx bug

  • <font color=red>确保 PR 是提交到 develop 分支,而不是 master 分支。</font>

  • 如果是修复 bug,请在 PR 中给出描述信息。

  • 合并代码需要两名维护人员参与:一人进行 review 后 approve,另一人再次 review,通过后即可合并。

1.3 版本列表

2. 使用说明

- node版本 > v8.6.0  
- golang版本 >= v1.11  
- IDE推荐:Goland  
- 各位在clone项目以后,把db文件导入自己创建的库后,最好前往七牛云申请自己的空间地址。  
- gormv2版本初始化数据库可以利用批量创建功能,这里已经写好初始化代码,需要在main.go内打开 initialize.Data() 的注释即可  
- 替换掉项目中的七牛云公钥,私钥,仓名和默认url地址,以免发生测试文件数据错乱

使用docker-compose体验本项目

  • 安装 docker-compose 官方文档
  • # 在Linux安装  
    # 1.1 运行此命令以下载Docker Compose的当前稳定版本  
    sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose  
    # 1.2 将可执行权限应用于二进制文件  
    sudo chmod +x /usr/local/bin/docker-compose   
  • # 使用Python的pip安装   
    pip3 install docker-compose -i https://pypi.tuna.tsinghua.edu.cn/simple  
  • 使用 Docker Desktop
  • Windows: https://hub.docker.com/editions/community/docker-ce-desktop-windows
  • Mac: https://hub.docker.com/editions/community/docker-ce-desktop-mac/
  • 使用git克隆本项目
    • git clone https://github.com/flipped-aurora/gin-vue-admin.git  
  • 使用docker-compose up一键启动启动项目

2.1 web端

# clone the project  
git clone https://github.com/piexlmax/gin-vue-admin.git  

# enter the project directory  
cd web  

# install dependency  
npm install  

# develop  
npm run serve

2.2 server端

# 使用 go.mod  

# 安装go依赖包  
go list (go mod tidy)  

# 编译  
go build

2.3 swagger自动化API文档

2.3.1 安装 swagger

(1)可以翻墙
go get -u github.com/swaggo/swag/cmd/swag
(2)无法翻墙

由于国内没法安装 go.org/x 包下面的东西,推荐使用 goproxy.io

如果您使用的 Go 版本是 1.13 及以上(推荐)  
# 启用 Go Modules 功能  
go env -w GO111MODULE=on   
# 配置 GOPROXY 环境变量  
go env -w GOPROXY=https://goproxy.io,direct  

# 执行  
go get -g -v github.com/swaggo/swag/cmd/swag  

# 到GOPATH的/src/github.com/swaggo/swag/cmd/swag路径下执行  
go install

2.3.2 生成API文档

cd server  
swag init

执行上面的命令后,server目录下会出现docs文件夹,登录http://localhost:8888/swagger/index.html,即可查看swagger文档

3. 技术选型

  • 前端:用基于vueElement-UI构建基础页面。
  • 后端:用Gin快速搭建基础restful风格API,Gin是一个go语言编写的Web框架。
  • 数据库:采用MySql(5.6.44)版本,使用gorm实现对数据库的基本操作,已添加对sqlite数据库的支持。
  • 缓存:使用Redis实现记录当前活跃用户的jwt令牌并实现多点登录限制。
  • API文档:使用Swagger构建自动化文档。
  • 配置文件:使用fsnotifyviper实现yaml格式的配置文件。
  • 日志:使用go-logging实现日志记录。

4. 项目架构

4.1 系统架构图

系统架构图

4.2 前端详细设计图 (提供者:<a href="https://github.com/baobeisuper">baobeisuper</a>)

前端详细设计图

4.3 目录结构

    ├─server         (后端文件夹)  
    │  ├─api            (API)  
    │  ├─config         (配置包)  
    │  ├─core           (內核)  
    │  ├─db             (数据库脚本)  
    │  ├─docs           (swagger文档目录)  
    │  ├─global         (全局对象)  
    │  ├─initialiaze    (初始化)  
    │  ├─middleware     (中间件)  
    │  ├─model          (结构体层)  
    │  ├─resource       (资源)  
    │  ├─router         (路由)  
    │  ├─service         (服务)  
    │  └─utils          (公共功能)  
    └─web            (前端文件)  
        ├─public        (发布模板)  
        └─src           (源码包)  
            ├─api       (向后台发送ajax的封装层)  
            ├─assets    (静态文件)  
            ├─components(组件)  
            ├─router    (前端路由)  
            ├─store     (vuex 状态管理仓)  
            ├─style     (通用样式文件)  
            ├─utils     (前端工具库)  
            └─view      (前端页面)  

5. 主要功能

  • 权限管理:基于jwtcasbin实现的权限管理
  • 文件上传下载:实现基于七牛云的文件上传操作(为了方便大家测试,我公开了自己的七牛测试号的各种重要token,恳请大家不要乱传东西)
  • 分页封装:前端使用mixins封装分页,分页方法调用mixins即可
  • 用户管理:系统管理员分配用户角色和角色权限。
  • 角色管理:创建权限控制的主要对象,可以给角色分配不同api权限和菜单权限。
  • 菜单管理:实现用户动态菜单配置,实现不同角色不同菜单。
  • api管理:不同用户可调用的api接口的权限不同。
  • 配置管理:配置文件可前台修改(测试环境不开放此功能)。
  • 富文本编辑器:MarkDown编辑器功能嵌入。
  • 条件搜索:增加条件搜索示例。
  • restful示例:可以参考用户管理模块中的示例API。
    前端文件参考: src\view\superAdmin\api\api.vue   
    后台文件参考: model\dnModel\api.go 
  • 多点登录限制:需要在config.yaml中把system中的useMultipoint修改为true(需要自行配置Redis和Config中的Redis参数,测试阶段,有bug请及时反馈)。
  • 分片长传:提供文件分片上传和大文件分片上传功能示例。
  • 表单生成器:表单生成器借助 @form-generator
  • 代码生成器:后台基础逻辑以及简单curd的代码生成器。

6. 计划任务

  • [ ] 导入,导出Excel
  • [ ] Echart图表支持
  • [ ] 工作流,任务交接功能开发
  • [ ] 单独前端使用模式以及数据模拟

7. 知识库

7.1 团队博客

https://www.yuque.com/flipped-aurora

内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994,欢迎您提出宝贵的需求。

7.2 教学视频

(1)环境搭建

Bilibili:https://www.bilibili.com/video/BV1Fg4y187Bw/ (v1.0版本视频,v2.0操作相同目录不同)

(2)模板使用

Bilibili:https://www.bilibili.com/video/BV16K4y1r7BD/ (v1.0版本视频,v2.0操作相同目录不同)

(3)2.0目录以及开发体验

Bilibili:https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461

(4)golang基础教学视频录制中...

https://space.bilibili.com/322210472/channel/detail?cid=108884

8. 捐赠

如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: 点我

9. 商用注意事项

如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。

继续阅读 »

项目文档

在线文档 : https://www.gin-vue-admin.com/

开发教学

1. 基本介绍

1.1 项目介绍

在线预览

测试用户名:admin

测试密码:123456

Gin-vue-admin是一个基于vue和gin开发的全栈前后端分离的后台管理系统,集成jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能,提供多种示例文件,让您把更多时间专注在业务开发上。

1.2 贡献指南

Hi! 首先感谢你使用 gin-vue-admin。

Gin-vue-admin 是一套为后台管理平台准备的一整套前后端分离架构式的开源框架,旨在快速搭建后台管理系统。

Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-admin 贡献代码或提供建议,请阅读以下内容。

1.2.1 Issue 规范

  • issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。如果你在使用时产生了疑问,请到 Slack 或 Gitter 里咨询。

  • 在提交 issue 之前,请搜索相关内容是否已被提出。

1.2.2 Pull Request 规范

  • 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。

  • commit 信息要以[文件名]: 描述信息 的形式填写,例如 README.md: fix xxx bug

  • <font color=red>确保 PR 是提交到 develop 分支,而不是 master 分支。</font>

  • 如果是修复 bug,请在 PR 中给出描述信息。

  • 合并代码需要两名维护人员参与:一人进行 review 后 approve,另一人再次 review,通过后即可合并。

1.3 版本列表

2. 使用说明

- node版本 > v8.6.0  
- golang版本 >= v1.11  
- IDE推荐:Goland  
- 各位在clone项目以后,把db文件导入自己创建的库后,最好前往七牛云申请自己的空间地址。  
- gormv2版本初始化数据库可以利用批量创建功能,这里已经写好初始化代码,需要在main.go内打开 initialize.Data() 的注释即可  
- 替换掉项目中的七牛云公钥,私钥,仓名和默认url地址,以免发生测试文件数据错乱

使用docker-compose体验本项目

  • 安装 docker-compose 官方文档
  • # 在Linux安装  
    # 1.1 运行此命令以下载Docker Compose的当前稳定版本  
    sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose  
    # 1.2 将可执行权限应用于二进制文件  
    sudo chmod +x /usr/local/bin/docker-compose   
  • # 使用Python的pip安装   
    pip3 install docker-compose -i https://pypi.tuna.tsinghua.edu.cn/simple  
  • 使用 Docker Desktop
  • Windows: https://hub.docker.com/editions/community/docker-ce-desktop-windows
  • Mac: https://hub.docker.com/editions/community/docker-ce-desktop-mac/
  • 使用git克隆本项目
    • git clone https://github.com/flipped-aurora/gin-vue-admin.git  
  • 使用docker-compose up一键启动启动项目

2.1 web端

# clone the project  
git clone https://github.com/piexlmax/gin-vue-admin.git  

# enter the project directory  
cd web  

# install dependency  
npm install  

# develop  
npm run serve

2.2 server端

# 使用 go.mod  

# 安装go依赖包  
go list (go mod tidy)  

# 编译  
go build

2.3 swagger自动化API文档

2.3.1 安装 swagger

(1)可以翻墙
go get -u github.com/swaggo/swag/cmd/swag
(2)无法翻墙

由于国内没法安装 go.org/x 包下面的东西,推荐使用 goproxy.io

如果您使用的 Go 版本是 1.13 及以上(推荐)  
# 启用 Go Modules 功能  
go env -w GO111MODULE=on   
# 配置 GOPROXY 环境变量  
go env -w GOPROXY=https://goproxy.io,direct  

# 执行  
go get -g -v github.com/swaggo/swag/cmd/swag  

# 到GOPATH的/src/github.com/swaggo/swag/cmd/swag路径下执行  
go install

2.3.2 生成API文档

cd server  
swag init

执行上面的命令后,server目录下会出现docs文件夹,登录http://localhost:8888/swagger/index.html,即可查看swagger文档

3. 技术选型

  • 前端:用基于vueElement-UI构建基础页面。
  • 后端:用Gin快速搭建基础restful风格API,Gin是一个go语言编写的Web框架。
  • 数据库:采用MySql(5.6.44)版本,使用gorm实现对数据库的基本操作,已添加对sqlite数据库的支持。
  • 缓存:使用Redis实现记录当前活跃用户的jwt令牌并实现多点登录限制。
  • API文档:使用Swagger构建自动化文档。
  • 配置文件:使用fsnotifyviper实现yaml格式的配置文件。
  • 日志:使用go-logging实现日志记录。

4. 项目架构

4.1 系统架构图

系统架构图

4.2 前端详细设计图 (提供者:<a href="https://github.com/baobeisuper">baobeisuper</a>)

前端详细设计图

4.3 目录结构

    ├─server         (后端文件夹)  
    │  ├─api            (API)  
    │  ├─config         (配置包)  
    │  ├─core           (內核)  
    │  ├─db             (数据库脚本)  
    │  ├─docs           (swagger文档目录)  
    │  ├─global         (全局对象)  
    │  ├─initialiaze    (初始化)  
    │  ├─middleware     (中间件)  
    │  ├─model          (结构体层)  
    │  ├─resource       (资源)  
    │  ├─router         (路由)  
    │  ├─service         (服务)  
    │  └─utils          (公共功能)  
    └─web            (前端文件)  
        ├─public        (发布模板)  
        └─src           (源码包)  
            ├─api       (向后台发送ajax的封装层)  
            ├─assets    (静态文件)  
            ├─components(组件)  
            ├─router    (前端路由)  
            ├─store     (vuex 状态管理仓)  
            ├─style     (通用样式文件)  
            ├─utils     (前端工具库)  
            └─view      (前端页面)  

5. 主要功能

  • 权限管理:基于jwtcasbin实现的权限管理
  • 文件上传下载:实现基于七牛云的文件上传操作(为了方便大家测试,我公开了自己的七牛测试号的各种重要token,恳请大家不要乱传东西)
  • 分页封装:前端使用mixins封装分页,分页方法调用mixins即可
  • 用户管理:系统管理员分配用户角色和角色权限。
  • 角色管理:创建权限控制的主要对象,可以给角色分配不同api权限和菜单权限。
  • 菜单管理:实现用户动态菜单配置,实现不同角色不同菜单。
  • api管理:不同用户可调用的api接口的权限不同。
  • 配置管理:配置文件可前台修改(测试环境不开放此功能)。
  • 富文本编辑器:MarkDown编辑器功能嵌入。
  • 条件搜索:增加条件搜索示例。
  • restful示例:可以参考用户管理模块中的示例API。
    前端文件参考: src\view\superAdmin\api\api.vue   
    后台文件参考: model\dnModel\api.go 
  • 多点登录限制:需要在config.yaml中把system中的useMultipoint修改为true(需要自行配置Redis和Config中的Redis参数,测试阶段,有bug请及时反馈)。
  • 分片长传:提供文件分片上传和大文件分片上传功能示例。
  • 表单生成器:表单生成器借助 @form-generator
  • 代码生成器:后台基础逻辑以及简单curd的代码生成器。

6. 计划任务

  • [ ] 导入,导出Excel
  • [ ] Echart图表支持
  • [ ] 工作流,任务交接功能开发
  • [ ] 单独前端使用模式以及数据模拟

7. 知识库

7.1 团队博客

https://www.yuque.com/flipped-aurora

内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994,欢迎您提出宝贵的需求。

7.2 教学视频

(1)环境搭建

Bilibili:https://www.bilibili.com/video/BV1Fg4y187Bw/ (v1.0版本视频,v2.0操作相同目录不同)

(2)模板使用

Bilibili:https://www.bilibili.com/video/BV16K4y1r7BD/ (v1.0版本视频,v2.0操作相同目录不同)

(3)2.0目录以及开发体验

Bilibili:https://www.bilibili.com/video/BV1aV411d7Gm#reply2831798461

(4)golang基础教学视频录制中...

https://space.bilibili.com/322210472/channel/detail?cid=108884

8. 捐赠

如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: 点我

9. 商用注意事项

如果您将此项目用于商业用途,请遵守Apache2.0协议并保留作者技术支持声明。

收起阅读 »

有谁知道吗?uniapp云打包之后接口请求不成功aa11

uniapp

11dd有谁知道吗?uniapp云打包之后接口请求不成功dd11

11dd有谁知道吗?uniapp云打包之后接口请求不成功dd11

有谁知道吗?uniapp云打包之后接口请求不成功aa11

uniapp

11dd有谁知道吗?uniapp云打包之后接口请求不成功dd11

11dd有谁知道吗?uniapp云打包之后接口请求不成功dd11