乐趣区

关于前端:APICloud-AVM框架-开发CRM客户管理系统

CRM 客户管理系统,通过信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而晋升其治理形式,向客户提供翻新式的个性化的客户交互和服务的过程。其最终目标是吸引新客户、保留老客户以及将已有客户转为忠诚客户,减少市场。

APP 开发采纳 APICloud AVM 框架,后盾采纳 PHP。

思维导图

性能介绍
1. 客户治理:录入客户信息、客户跟进、客户销售记录、间接拨打客户电话、条件筛选查问、公共客户

2. 申请、收、发货治理

3. 文档库、知识库

4. 工作日志、日程治理

5. 产品治理、库存治理

6. 门店治理、员工治理

7. 统计分析:客户统计分析、门店统计分析、员工统计分析、销售统计分析

8. 通讯录、音讯揭示

9. 即时通讯、视频会议

利用模块

我的项目目录

开发介绍
首页导航
零碎首页应用 tabLayout,能够将相干参数配置在 JSON 文件中,再在 config.xml 中将 content 的值设置成该 JSON 文件的门路。如果底部导航没有非凡需要这里强烈建议大家应用 tabLayout 为 APP 进行布局,官网曾经将各类手机屏幕及不同的分辨率进行了适配,免去了很多对于适配方面的问题。
{

"name": "root",
"hideNavigationBar": true,
"navigationBar": {
  "background": "#035dff",
  "color": "#fff",
  "shadow": "#035dff",
  "hideBackButton": true
},
"tabBar": {
  "scrollEnabled": false,
  "background": "#fff",
  "shadow": "#f1f1f1",
  "color": "#8a8a8a",
  "selectedColor": "#000000",
  "index":0,
  "preload": 0,
  "frames": [{
    "name": "home",
    "url": "pages/main/home.stml",
    "title": "主页"
  }, {
    "name": "notice",
    "url": "pages/notice/notice-index.stml",
    "title": "音讯告诉"
  }, {
    "name": "tellbook",
    "url": "pages/main/tellbook.stml",
    "title": "通讯录"
  }, {
    "name": "my",
    "url": "pages/seeting/my.stml",
    "title": "集体核心"
  }],
  "list": [{
    "text": "主页",
    "iconPath": "image/navbar/home-o.png",
    "selectedIconPath": "image/navbar/home.png",
    "scale":3
  }, {
    "text": "揭示",
    "iconPath": "image/navbar/notice-o.png",
    "selectedIconPath": "image/navbar/notice.png",
    "scale":3
  }, {
    "text": "通讯录",
    "iconPath": "image/navbar/book-o.png",
    "selectedIconPath": "image/navbar/book.png",
    "scale":3
  }, {
    "text": "设置",
    "iconPath": "image/navbar/set-o.png",
    "selectedIconPath": "image/navbar/set.png",
    "scale":3
  }]
}

}
动静权限
在首页的 apiready 中依据提醒受权须要获取的权限,APP 每次启动的时候就会判断是否已受权,如果未受权就是提醒进行受权。

        apiready(){let limits=[];
            // 获取权限
            var resultList = api.hasPermission({list: ['storage', 'location', 'camera', 'photos', 'phone']
            });
            if (resultList[0].granted) {// 已受权,能够持续下一步操作} else {limits.push(resultList[0].name);
            }
            if (resultList[1].granted) {// 已受权,能够持续下一步操作} else {limits.push(resultList[1].name);
            }
            if (resultList[2].granted) {// 已受权,能够持续下一步操作} else {limits.push(resultList[2].name);
            }
            if (resultList[3].granted) {// 已受权,能够持续下一步操作} else {limits.push(resultList[3].name);
            }
            if (resultList[4].granted) {// 已受权,能够持续下一步操作} else {limits.push(resultList[4].name);
            }
            if(limits.length>0){
                api.requestPermission({list: limits,}, (res) => {});
            }
        }

音讯事件通过 sendEvent 把事件播送进来,而后在其余页面通过 addEventListener 监听事件,通过事件名和附带的参数进行其余操作。举例:登录胜利之后,须要在集体核心加载个人信息,在首页加载相干集体的数据;增加某项数据之后,须要进行刷新列表等等。

methods: {

        login(){if (!this.data.username) {this.showToast("姓名不能为空");
                return;
            }
            if (!this.data.password) {this.showToast("明码不能为空");
                return;
            } 
            var data={
                secret:'',
                user:this.data.username,
                psw:this.data.password
            };
            api.showProgress();
            POST('Index/queryuserinfo',data,{}).then(ret =>{// console.log(JSON.stringify(ret));
                if(ret.flag=='Success'){api.setPrefs({key:'username',value:ret.data.username});
                    //api.setPrefs({key:'password',value:ret.data.password});
                    api.setPrefs({key:'userid',value:ret.data.id});
                    api.setPrefs({key:'roleid',value:ret.data.roleid});
                    api.setPrefs({key:'rolename',value:ret.data.rolename});
                    api.setPrefs({key:'organid',value:ret.data.organid});
                    api.setPrefs({key:'organname',value:ret.data.organname});
                    api.setPrefs({key:'organtype',value:ret.data.organtype});
                    api.setPrefs({key:'phone',value:ret.data.phone});        
                    api.setPrefs({key:'name',value:ret.data.name});    

                    api.sendEvent({name: 'loginsuccess',});
                    api.closeWin();}
                else{
                    api.toast({msg:'登录失败!请稍后再试。'})
                }
                api.hideProgress();}).catch(err =>{
                api.toast({msg:JSON.stringify(err)
                })
            })
        }
    }

双击退出程序
在首页、登录页或其余须要双击退出程序的页面,在 apiready 中增加。

      apiready(){        
        // 监听返回  双击退出程序
        api.setPrefs({
            key: 'time_last',
            value: '0'
        });
        api.addEventListener({name : 'keyback'}, (ret, err) => {var time_last = api.getPrefs({sync: true,key: 'time_last'});
            var time_now = Date.parse(new Date());
            if (time_now - time_last > 2000) {api.setPrefs({key:'time_last',value:time_now});
                api.toast({
                    msg : '再按一次退出 APP',
                    duration : 2000,
                    location : 'bottom'
                });
            } else {
                api.closeWidget({silent : true});
            }
        });
      }

清空缓存
官网自带的 API clearCache,可状况全副缓存,也可抉择革除多少天前的缓存。

音讯推送采纳极光推送,须要集成 ajpush 模块。

具体应用办法可具体浏览官网模块文档。

 推送性能初始化须要在 APP 每次启动的时候进行集成,将初始化极光推送的办法集成在 util 工具类中,在首页进行初始化。
fnReadyAJpush(){

    var jpush = api.require('ajpush');
    api.addEventListener({name:'pause'}, function(ret,err) {onPause();// 监听利用进入后盾,告诉 jpush 暂停事件
    })

    api.addEventListener({name:'resume'}, function(ret,err) {onResume();// 监听利用复原到前台,告诉 jpush 复原事件
    })

    // 设置初始化
    jpush.init(function(ret, err){if(ret && ret.status){var ali=$api.getStorage('userid');
            var tag=$api.getStorage('roleid');
            // 绑定别名
            if($api.getStorage('userid')){
                jpush.bindAliasAndTags({
                    alias:ali,
                    tags:[tag]
                }, function(ret, err){if(ret.statusCode==0){api.toast({ msg: '推送初始化胜利'});
                    }
                    else{api.toast({ msg: '绑定别名失败'});
                    }
                });
            }
            // 监听音讯
            jpush.setListener(function(ret) {
                var content = ret.content;
                alert(content);
            });
            }
        else{api.toast({ msg: '推送服务初始化失败'});
            }
    });
}

初始化应用,每次启动 APP 的时候须要,从新登陆之后可能存在切换账号的状况,也须要从新登陆。

定位性能
因为零碎中的定位只须要确定以后地位即可,所有定位性能应用的是 aMapLBS 模块,此模块没有关上地图的性能,只须要在用到的页面间接调用获取定位即可。

应用前须要在 config.xml 中进行配置,具体参数须要去高德开放平台去申请。

        // 获取以后地位信息和经纬度           
        setLocation(){var aMapLBS = api.require('aMapLBS');
            aMap.updateLocationPrivacy({
                privacyAgree:'didAgree',
                privacyShow:'didShow',
                containStatus:'didContain'
            });
            aMapLBS.configManager({
                accuracy: 'hundredMeters',
                filter: 1
            }, (ret, err) => {if (ret.status) {
                    aMapLBS.singleLocation({timeout: 2}, (ret, err) => {if (ret.status) {                    
                            this.data.lon = ret.lon;
                            this.data.lat = ret.lat;
                        }
                    });
                    aMapLBS.singleAddress({timeout: 2}, (ret, err) => {if (ret.status) {this.data.address = ret.formattedAddress;}
                    });
                }
                else{
                    api.toast({msg:'定位初始化失败,请开启手机定位。'})
                    return false;
                }
            });
        }

视频、语音通话
采纳 tecnetRTC 开发音视频通话性能。须要先去腾讯云平台创立利用申请 key,在通过官网提供的办法生成 userSig。

生成 userSig 代码
// 获取腾讯视频 RTC usersig

public function getQQrtcusersig(){checkscret('secret');// 验证受权码
  checkdataPost('userid');// 用户 ID

  $sdkappid=C('sdkappid');
  $key=C('usersig_key');

  $userid=$_POST['userid'];
  
  require 'vendor/autoload.php';
  
  $api = new \Tencent\TLSSigAPIv2($sdkappid, $key);
  $sig = $api->genSig($userid);
  
  if($sig){returnApiSuccess('查问胜利',$sig);
  }
  else{returnApiError( '查问失败,请稍后再试');
    exit();}
}

用户视频画面须要依据以后视频用户数,进行计算调整。

<template>

<view class="page">
    <view class="video-bk"></view>        
    <view class="footer">
        <view class="footer-item" @click="setLoud">
            <image class="footer-item-ico" src='../../image/loud-on.png' mode="widthFix" v-if="isLoud"></image>
            <image class="footer-item-ico" src='../../image/loud-off.png' mode="widthFix" v-else></image>
            <text class="footer-item-label"> 免提 </text>
        </view>
        <view class="footer-item" @click="setRTC">
            <image class="footer-item-ico" src='../../image/stop.png' mode="widthFix" v-if="isStart"></image>
            <image class="footer-item-ico" src='../../image/start.png' mode="widthFix" v-else></image>
            <text class="footer-item-label" v-if="isStart"> 完结 </text>
            <text class="footer-item-label" v-else> 开始 </text>
        </view>
        <view class="footer-item" @click="setMute">
            <image class="footer-item-ico" src='../../image/mute-on.png' mode="widthFix" v-if="isMute"></image>
            <image class="footer-item-ico" src='../../image/mute-off.png' mode="widthFix" v-else></image>
            <text class="footer-item-label"> 静音 </text>
        </view>
    </view>
</view>

</template>
<script>

import $util from '../../utils/utils.js'
import {POST} from '../../script/req.js'
export default {
    name: 'rtcvideo',
    apiready(){
        this.data.roomId = api.pageParam.id;
        this.data.meetStart = api.pageParam.userid;
        var tencentTRTC= api.require('tencentTRTC');
        api.setNavBarAttr({shadow:'#000000'});
        //IOS 禁用左右滑动
        api.setWinAttr({slidBackEnabled: false});
        api.addEventListener({name:'keyback'}, (ret) =>{// 禁用返回按钮})
        api.addEventListener({name: 'navbackbtn'}, (ret, err) => {
            api.confirm({
                title: '揭示',
                msg: '你确定要来到本次会议吗?',
                buttons: ['确定', '持续']
            },(ret)=>{
                var index = ret.buttonIndex;
                if(index==1){tencentTRTC.exitRoom({});
                    api.closeWin();}
            })
        }); 
        // 增加右键切换摄像头
        api.setNavBarAttr({
            rightButtons: [{iconPath:'widget://image/switch.png'}]
        });            
        api.addEventListener({name:'navitembtn'}, (ret)=>{if(ret.type=='right'){
                 // 切换前后摄像头
                tencentTRTC.switchCamera({});
                api.toast({msg:'切换胜利'})
            }
        })

        // 视频模块监听事件
        var tencentTRTC= api.require('tencentTRTC');
        //view disappear 监听用户间接敞开 APP 的状况 默认把用户本人退出房间
        api.addEventListener({name:'viewdisappear'},function(ret,err){tencentTRTC.exitRoom({});
        });

        // 监听 RTC 事件
        tencentTRTC.setTRTCListener({},(ret, err) => {// console.log(JSON.stringify(ret));
            // console.log(JSON.stringify(err));
            if(ret.status){if(ret.action=='enterRoom'){
                    // 开启语音
                    tencentTRTC.startLocalAudio({});                
                    tencentTRTC.startLocalPreview({
                        rect:{
                            x: 0,
                            y: api.safeArea.top+45,
                            w: api.frameWidth,
                            h: api.frameHeight/3
                        }
                    });
                }
                // 有用户退出房间
                else if(ret.action=='remoteUserEnterRoom'){// console.log(this.data.rectindex);
                    if(this.data.index>4)
                    var thisRect = {x: (api.frameWidth/4)*this.data.rectindex,
                        y: api.frameHeight/3+api.safeArea.top+45,
                        w: api.frameWidth/4,
                        h: api.frameWidth/4
                    }
                    tencentTRTC.startRemoteView({
                        rect:thisRect,
                        remoteUid:ret.remoteUserEnterRoom.userId,
                    });
                    this.data.rectindex++;
                }
                // 有用户来到房间
                else if(ret.action=='remoteUserLeaveRoom'){
                    tencentTRTC.stopRemoteView({remoteUid:ret.remoteUserLeaveRoom.userId,});
                }
            }
            else{
                api.toast({msg:JSON.stringify(err)
                })
            }
        });
    },
    data() {
        return{
            isMute:false,
            isLoud:false,
            isStart:false,
            rects:[],
            rectindex:0,
            roomId:'',
            meetStart:'',
            mTop:api.safeArea.top+45
        }
    },
    methods: {setMute(){var tencentTRTC= api.require('tencentTRTC');
            this.data.isMute = !this.data.isMute;
            tencentTRTC.muteLocalAudio({mute:this.data.isMute});
        },
        setLoud(){this.data.isLoud = !this.data.isLoud;},
        setRTC(){var tencentTRTC= api.require('tencentTRTC');
            if(this.data.isStart){if(this.data.meetStart == api.getPrefs({sync: true,key: 'userid'})){
                    // 发起人来到房间 会议完结
                    this.setRTCStatus('03');
                }
                tencentTRTC.exitRoom({});
                api.closeWin();}
            else{
                var data={
                    secret:'',
                    userid: api.getPrefs({sync: true,key: 'userid'})
                };
                api.showProgress();
                POST('Video/getQQrtcusersig',data,{}).then(ret =>{console.log(JSON.stringify(ret));
                    if(ret.flag=='Success'){                        
                        this.data.isStart = !this.data.isStart;
                        tencentTRTC.enterRoom({
                            appId:14*******272,
                            userId:api.getPrefs({sync: true,key: 'userid'}),
                            roomId:this.data.roomId,
                            userSig:ret.data,
                            scene:1
                        },(ret, err) => {//  console.log(JSON.stringify(ret));
                            //  console.log(JSON.stringify(err));
                            if(ret.result<0){
                                api.toast({
                                    msg: '网络谬误',
                                    duration: 2000,
                                    location: 'bottom'
                                });
                            }
                        });
                        // 设置会议状态为开始
                        this.setRTCStatus('02');
                    }
                    api.hideProgress();}).catch(err =>{
                    api.toast({msg:JSON.stringify(err)
                    })
                })
            }
        },
        // 视频设置最多 9 集体,自己画面占一行,其余 8 人每行 4 个共 2 行
        setUserRect(){for(var i=0;i<8;i++){if(i<4){this.data.rects[i]={x: (api.frameWidth/4)*i,
                    y: api.frameHeight/3+this.data.mTop,
                    w: api.frameWidth/4,
                    h: api.frameWidth/4
                    };
                }
                else{this.data.rects[i]={x: (api.frameWidth/4)*(i-4),
                    y: (api.frameHeight/3)+(api.frameWidth/4)+this.data.mTop,
                    w: api.frameWidth/4,
                    h: api.frameWidth/4
                    };
                }
            }
            // console.log(JSON.stringify(this.data.rects));
        },
        // 设置会议状态
        setRTCStatus(status){
            var data={
                    secret:'',
                    id: this.data.roomId,
                    flag:status
                };
                api.showProgress();
                POST('Video/setmeeting',data,{}).then(ret =>{console.log(JSON.stringify(ret));
                    if(ret.flag=='Success'){
                        // 在会议列表监听 刷新会议列表 已完结的不在显示                    
                        api.sendEvent({name: 'setmeeting'});
                    }
                    api.hideProgress();}).catch(err =>{
                    api.toast({msg:JSON.stringify(err)
                    })
                })
        }
    }
}

</script>
<style>

.page {
    height: 100%;
    justify-content: space-between;
    background-color: #ffffff;
}
.video-bk{
    height: 100%;
    background-color: #000000;
}
.footer{
    height: 70px;
    background-color: #ffffff;
    flex-flow: row nowrap;
    justify-content: space-around;
    margin-bottom: 30px;
    align-items: center;
    margin-top: 20px;
}
.footer-item{
    align-items: center;
    justify-content: center;
}
.footer-item-ico{width: 45px;}
.footer-item-label{font-size: 13px;}

</style>
通讯录
基于 scroll-view 进行开发实现通讯录性能,可间接拨打电话
<template>

<view>
    <scroll-view class="page" scroll-y show-scrollbar="false" id="book">
        <safe-area></safe-area>
        <view class="item" v-for="(item, index) in list" v-show="item.children.length>0">
            <view class="nav" id={item.zkey}>
                <text class="nav-title">{item.zkey}</text>
            </view>
            <view class="box" v-for="(it, pindex) in item.children" data-phone={it.phone}  @click="takePhone">
                <image class="avator" src='../../image/avator.png' mode="widthFix"></image>
                <view class="right">
                    <text class="name">{it.remark}</text>
                    <view class="bt">
                        <text class="bt-position">{it.position}</text>
                        <text class="bt-part">{it.dept_name}</text>
                    </view>
                </view>
            </view>
        </view>        
    </scroll-view>
    <scroll-view class="right-nav" scroll-y show-scrollbar="false">
        <view class="right-nav-item" data-id={item.zkey} @click="scrollToE" v-for="(item, index) in list">
            <text class={item.zkey==zIndex?'right-nav-item-on':'right-nav-item-off'}>{item.zkey}</text>
        </view>
    </scroll-view>
</view>  

</template>
<script>

import {POST} from '../../script/req.js'
export default {
    name: 'tellbook',
    apiready(){this.loadData();
    },
    data() {
        return{list:[],
            zIndex:''
        }
    },
    methods: {loadData(){
            var data={
                secret:'',
                userid:api.getPrefs({sync: true,key: 'userid'})
            };
            api.showProgress();
            POST('Index/gettellbook',data,{}).then(ret =>{if(ret.flag=='Success'){this.toTree(ret.data);
                }
                api.hideProgress();}).catch(err =>{
                api.toast({msg:JSON.stringify(err)
                })
            })
        },
        // 解决数据
        toTree(data){var book=[];
            var  zm= 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z'.split(',');
            zm.forEach(element => {var arrz = data.filter((item) => {return item.zkey == element})
                book.push({'zkey':element,children:arrz});
            });
            this.data.list = book;
            //console.log(JSON.stringify(book));
        },
        scrollToE(e){
            var id = e.target.dataset.id;
            var book = document.getElementById('book');
            book.scrollTo({view:id})
            this.data.zIndex = id;
        },
        takePhone(e){
            var phone = e.target.dataset.phone;
            api.call({
                type: 'tel',
                number: phone
            });
        }
    }
}

</script>
<style>

.page {
    height: 100%;
    background-color: #ffffff;
}
.nav{
    margin: 0 10px;
    padding: 0 10px;
}
.nav-title{font-size: 20px;}
.box{
    flex-flow: row nowrap;
    justify-content: flex-start;
    align-items: center;
    margin: 10px;
    border-bottom: 1px solid #ccc;
    padding-bottom: 10px;
}
.avator{padding: 5px;}
.right{padding-left: 20px;}
.bt{
    flex-flow: row nowrap;
    justify-content: flex-start;
    align-items: center;
}
.bt-position{
    font-size: 14px;
    color: #666666;
}
.bt-part{
    font-size: 14px;
    color: #666666;
    padding-left: 20px;
}
.right-nav{
    position: absolute;
    right: 10px;
    width: 30px;
    padding: 30px 0;
    height: 100%;
    align-items: center;
}
.right-nav-item{padding-bottom: 5px;}
.right-nav-item-on{color: #035dff;}
.right-nav-item-off{color: #666666;}
.avator{width: 50px;}

</style>
echarts 图表
因为 AVM 无奈解析 cavans,所有图表相干的页面采纳的是 html,页面增加在 html 文件夹中,通过 open.frame()进行关上。采纳的 Echarts.js. 可去 echarts 官网下载,如果图标不简单,倡议应用自定义下载只抉择用到的控件,减小文件体积;款式也能够自定义而后下载转述 js 文件,下载连贯

文件目录

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0" />
    <title> 统计 - 客户 </title>
    <link rel="stylesheet" type="text/css" href="../css/api.css" />
    <style>
      body{background:#efefef;padding: 10px 10px 50px 10px;}
      .chart-box{
          background-color: #ffffff;
          border-radius: 5px;
          margin-top: 10px;
      }
    </style>
</head>

<body>
  <div class="chart-box">
    <div id="Chart1" style="height:200px;"></div>
  </div>
  <div class="chart-box">
    <div id="Chart2" style="height:200px;"></div>
  </div>
  <div class="chart-box">
    <div id="Chart3" style="height:200px;"></div>
  </div>
  <div class="chart-box">
    <div id="Chart4" style="height:200px;"></div>
  </div>
</body>
<script type="text/javascript" src="../script/api.js"></script>
<script type="text/javascript" src="../script/echarts.min.js"></script>
<script type="text/javascript" src="../script/customed.js"></script>
<script type="text/javascript" src="../script/remotedb.js"></script>
<script>
    apiready = function() {loaddemo1();
        loaddemo2();};

// 客户统计
    function loaddemo1(){
        var path='http://192.168.1.5/api.php/Home/Statistic/querykhzzl';
        var data={values:{
            secret:'776eca99-****-11e9-******00163e008b45',
            year:'2021'
        }};
        fnPost(path, data, function(ret, err) {// console.log(JSON.stringify(ret));
            // console.log(JSON.stringify(err));
            if(ret['flag']=='Success'){var data=ret['data'];
            var arryaxis=[],arrzzl=[],arrall=[];
            for(var i=0;i<data.length;i++){arryaxis[i]=MonthToZhcn(data[i]['mon']);
                arrzzl[i]=data[i]['num'];
                arrall[i]=data[i]['monall'];
            }
            var myChart = echarts.init(document.getElementById('Chart1'),'customed');
            var option = {
                title:{text:'2021 年客户全年增长量和保有量'},
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {type: 'shadow'}
                },
                legend: {data: ['增长客户','客户总量'],
                    orient:'vertical',
                    right:10,
                    top:120
                },
                grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    containLabel: true
                },
                xAxis: {
                    type: 'value',
                    boundaryGap: [0, 0.01]
                },
                yAxis: {
                    type: 'category',
                    data: arryaxis
                },
                series: [
                    {
                        name: '增长客户',
                        type: 'bar',
                        data: arrzzl
                    },
                    {
                        name: '客户总量',
                        type: 'bar',
                        data: arrall
                    }
                ]
            };
            myChart.setOption(option);
            }
        })
    }

    // 客户统计
    function loaddemo2(){var datayear=[];
    var path='http://192.168.1.5/api.php/Home/Statistic/querynvbl';
    var data={values:{secret:'776eca99-a1e5-11e9-9897-00163e008b45'}};
    fnPost(path, data, function(ret, err) {// console.log(JSON.stringify(ret));
        // console.log(JSON.stringify(err));
        if(ret['flag']=='Success'){var data=ret['data'];
        if(data['year']){var year=data['year'];
            datayear.push({value:year['num1'],name:'18-20 岁'});
            datayear.push({value:year['num2'],name:'21-30 岁'});
            datayear.push({value:year['num3'],name:'31-40 岁'});
            datayear.push({value:year['num4'],name:'41-50 岁'});
            datayear.push({value:year['num5'],name:'51 岁以上'});
        }
        var myChart2 = echarts.init(document.getElementById('Chart2'),'customed');
        var myChart3 = echarts.init(document.getElementById('Chart3'),'customed');
        var myChart4 = echarts.init(document.getElementById('Chart4'),'customed');
        option2 = {
            title:{text:'客户等级剖析'},
            tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            series : [
                {
                    name: '客户等级占比',
                    type: 'pie',
                    radius : '55%',
                    center: ['50%', '60%'],
                    data:data['csd'],
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        };
        option3 = {
            title:{text:'客户性别剖析'},
            tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            series : [
                {
                    name: '客户性别占比',
                    type: 'pie',
                    radius : '55%',
                    center: ['50%', '60%'],
                    data:data['sex'],
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        };
        option4 = {
            title:{text:'客户年龄剖析'},
            tooltip : {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            series : [
                {
                    name: '客户年龄占比',
                    type: 'pie',
                    radius : '55%',
                    center: ['50%', '60%'],
                    data:datayear,
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        };
        myChart2.setOption(option2);
        myChart3.setOption(option3);
        myChart4.setOption(option4);
        }
    })
}
</script>

</html>

扫描二维码 模块文档中举荐了 2 种形式,如没非凡需要,举荐应用第一种。

// 入口
<view class=”column-item” @click=”fnscanner”>

<image class="column-item-ico" src='../../image/co-ico5.png' mode="widthFix"></image>
<text class="column-item-title"> 扫码 </text>

</view>

// 应用

        fnscanner(){var FNScanner = api.require('FNScanner');
            FNScanner.open({autorotation: true}, (ret, err) => {console.log(JSON.stringify(ret));
                console.log(JSON.stringify(err));
                if(ret){if(ret.eventType=='success'){
                        api.toast({msg:'扫码胜利,行将跳转详情页面'})            
                    }
                }
                else{
                    api.toast({msg:'扫码失败,请再次尝试!'})
                }
            });
        }

数据列表及分页
数据列表的分页查问,次要应用到的是上拉刷新和下拉刷新动作,在动作的回调中解决须要的参数,来实现数据的加载和刷新。其中解决的参数须要配个后盾提供的接口进行重定义。
<template>

<scroll-view scroll-y class="page" enable-back-to-top refresher-enabled refresher-triggered={refresherTriggered} onrefresherrefresh={this.onrefresherrefresh} onscrolltolower={this.onscrolltolower}>
    <view>
        <view class="item-box" v-for="(item, index) in list" data-id={item.id}>
            <view class="top">
                <image class="top-ico" src='../../image/flag01.png' mode="widthFix" v-if="item.flag=='01'"></image>
                <image class="top-ico" src='../../image/flag02.png' mode="widthFix" v-else-if="item.flag=='02'"></image>
                <image class="top-ico" src='../../image/flag03.png' mode="widthFix" v-else-if="item.flag=='03'"></image>
                <image class="top-ico" src='../../image/flag04.png' mode="widthFix" v-else-if="item.flag=='04'"></image>
                <image class="top-ico" src='../../image/flag05.png' mode="widthFix" v-else-if="item.flag=='05'"></image>
                <image class="top-ico" src='../../image/flag06.png' mode="widthFix" v-else></image>
                <text class="top-name">{item.name}</text>
                <text class="top-level">★{item.dengji}</text>
            </view>
            <view class="mid">
                <view>
                    <text class="mid-tip"> 录入工夫 </text>
                    <text>{item.lrsj}</text>
                </view>
                <view>
                    <text class="mid-tip"> 生日 </text>
                    <text>{item.birthday}</text>
                </view>
            </view>
            <view class="btm">
                <view class="btm-item" data-phone={item.phone} @click="call">
                    <image class="btm-ico" src='../../image/TELL.png' mode="widthFix"></image>
                    <text> 打电话 </text>
                </view>
                <view class="btm-item" data-id={item.id} @click="followRecords">
                    <image class="btm-ico" src='../../image/GJ.png' mode="widthFix"></image>
                    <text> 跟进 </text>
                </view>
                <view class="btm-item" data-id={item.id} @click="saleRecords">
                    <image class="btm-ico" src='../../image/XS.png' mode="widthFix"></image>
                    <text> 销售 </text>
                </view>
            </view>
        </view>
    </view>
    <view class="footer">
        <text class="loadDesc">{loadStateDesc}</text>
    </view>    
    <safe-area></safe-area>
</scroll-view>

</template>
<script>

import $util from '../../utils/utils.js'
import {POST} from '../../script/req.js'
export default {
    name: 'customer',
    apiready(){
        // 设置筛选按钮
        api.setNavBarAttr({
            rightButtons: [{
                text:'筛选',
                color:'#ffffff'
            }]
        });            
        api.addEventListener({name:'navitembtn'}, (ret)=>{if(ret.type=='right'){
                api.openFrame({
                    name: 'customer-select',
                    url: 'customer-select.stml',
                    rect: {
                        x: 0,
                        y: 0,
                        w: 'auto',
                        h: 'auto'
                    },
                    pageParam: {name: 'test'}
                });
            }
        })
        api.addEventListener({name:'doSearchCustomer'}, (ret)=>{
            // 重置 key
            this.data.key = '';
            // console.log(JSON.stringify(ret));
            this.data.status = ret.value.status;
            this.data.level = ret.value.level;
            this.data.sex = ret.value.sex;
            this.loadData();})
        this.data.key = api.pageParam.key;
        //console.log(this.data.key);
        this.loadData();},
    data() {
        return{
            key:'',
            list:[],
            skip: 0,
            refresherTriggered: false,
            haveMoreData: true,
            loading: false,
            status:'',
            level:'',
            sex:''
        }
    },
    computed: {loadStateDesc(){if (this.data.loading || this.data.haveMoreData) {return '加载中...';} else if (this.list.length > 0) {return '没有更多啦';} else {return '临时没有内容';}
        }
    },
    methods: {loadData(loadMore) {
            this.data.loading = true;
            var limit = 10;
            var skip = loadMore?this.data.skip+limit:0;
            var data={            
                secret:'',
                   key:this.data.key,
                limit:limit,
                skip:skip,            
                userid:api.getPrefs({sync: true,key: 'userid'}),
                roleid:api.getPrefs({sync: true,key: 'roleid'}),
                organid:api.getPrefs({sync: true,key: 'organid'}),
            };
            api.showProgress();
            POST('Customer/querycustomerlist',data,{}).then(ret =>{// console.log(JSON.stringify(ret));
                if(ret.flag=='Success'){
                    let noticedata = ret.data;
                    this.data.haveMoreData = noticedata.length == limit;
                    if (loadMore) {this.data.list = this.data.list.concat(noticedata);
                    } else {this.data.list = noticedata;}
                    this.data.skip = skip;
                }
                else{
                    this.data.haveMoreData = false;
                    this.data.list=[];}
                this.data.loading = false;
                this.data.refresherTriggered = false;
                api.hideProgress();})
        },
        /* 下拉刷新页面 */
        onrefresherrefresh(){
            this.data.refresherTriggered = true;
            this.loadData(false);
        },
        onscrolltolower() {if (this.data.haveMoreData) {this.loadData(true);
            }
        },
        call(e){
            var phone = e.target.dataset.phone;
            api.call({
                type: 'tel',
                number: phone
            });
        },
        followRecords(e){
            var id = e.target.dataset.id;
            $util.openWin({
                name: 'followRecords',
                url: 'followRecords.stml',
                title: '客户跟进记录',
                pageParam:{id:id}
            });
        },
        saleRecords(e){
            var id = e.target.dataset.id;
            $util.openWin({
                name: 'saleRecords',
                url: 'saleRecords.stml',
                title: '客户销售记录',
                pageParam:{id:id}
            });
        }
    }
}

</script>
<style>

.page {
    height: 100%;
    background-color: #f0f0f0;
}
.item-box{
    margin: 10px;
    background-color: #ffffff;
    border-radius: 5px;
    padding: 10px;
}
.top{
    flex-flow: row nowrap;
    align-items: center;
    justify-content: space-between;
}
.mid{
    flex-flow: row nowrap;
    align-items: center;
    justify-content: space-between;
    padding: 10px 0;
}
.mid-tip{
    font-size: 13px;
    color: #666666;
}
.top-level{color: #ffd700;}
.top-ico{width: 30px;}
.top-name{font-size: 20px;}
.btm{
    flex-flow: row nowrap;
    align-items: center;
    justify-content: space-between;
    padding-top: 10px;
    border-top: 1px solid #ccc;
}
.btm-item{
    flex-flow: row nowrap;
    align-items: center;
    justify-content: center;
}
.btm-ico{
    width: 20px;
    padding-right: 5px;
}
.footer {
    height: 44px;
    justify-content: center;
    align-items: center;
}
.loadDesc {
    width: 200px;
    text-align: center;
}

</style>
导航栏底部呈现“白边”问题解决

如果 navigationBar 的背景设置了其余色彩,shadow 应用默认的色彩,如果页面背景设置成彩色,会有条显著的“白边”成果,这时须要通过设置 shadow 的色彩来打消“白边”。

1. 可在须要的页面通过 setNavBarAttr 进行设置,具体色彩值依据状况自行抉择。
api.setNavBarAttr({

shadow:'#000000'

});

2. 如果全局应用,则可在 index.json 中 设置。

后盾代码
<?php
namespace Home\Controller;
require ‘vendor/autoload.php’; // 留神地位肯定要在 引入 ThinkPHP 入口文件 之前
use Think\Controller;
use JPush\Client as JPushClient;
class VideoController extends Controller {

  // 查问视频会议列表
  public function queryvideomeetinglist(){checkscret('secret');// 验证受权码
    checkdataPost('limit');// 下一次加载多少条
    checkdataPost('userid');// 用户 ID

    $limit=$_POST['limit'];
    $skip=$_POST['skip'];
    if(empty($skip)){$skip=0;}
    
    $userid=$_POST['userid'];

    $map['_string']='(find_in_set('.$userid.',getvideomeetingusers(id)) and flag<>\'03\') or (userid='.$userid.'and flag<>\'03\')';

    $releaseInfo=M()->table('crm_video_audio_meeting')->field('id,title,getcode_value(\' 音视频类型 \',type) lx,type,flag,getusername(userid) fqr,userid,estimatetime,type,note')->where($map)->limit($skip,$limit)->order('estimatetime desc')->select();
    
    if($releaseInfo){returnApiSuccess('查问胜利',$releaseInfo);
    }
    else{returnApiError( '查问失败!');
      exit();}
  }

  // 查问加入音视频会议人员列表
  public function queryvideomeetingpersonlist(){checkscret('secret');// 验证受权码

    $releaseInfo=M()->table('crm_user')->field('id as employee_id,name,getorganname(organid) remark,getrolename(roleid) position,pinyin as phonetic')->where($map)->order('organid')->select();
    
    if($releaseInfo){returnApiSuccess('查问胜利',$releaseInfo);
    }
    else{returnApiError( '查问失败!');
      exit();}
  }


  // 减少视频会议
  public function addvideomeeting(){checkscret('secret');// 验证受权码
    checkdataPost('userid');// 用户 ID

    $userid=$_POST['userid'];
    $title=$_POST['title'];
    $note=$_POST['note'];
    $shijian=$_POST['shijian'];
    $ids=$_POST['ids'];

    $arrids=explode(',',$ids);

    $data['userid']=$userid;
    $data['title']=$title;
    $data['note']=$note;    
    $data['estimatetime']=$shijian;
    $data['estimatenum']=count($arrids);
    $data['type']=count($arrids)>9?'01':'02';//01 音频  02 视频
    $data['flag']='01';    

    $releaseInfo=M()->table('crm_video_audio_meeting')->data($data)->add();

    if($releaseInfo){
      // 增加人员加入会议记录
      foreach ($arrids as $v) {$datap['video_meeting_id']=$releaseInfo;
        $datap['userid']=$v;
        $res=M()->table('crm_video_audio_meeting_users')->data($datap)->add();
        // 推送视频会议音讯
        try{
            // 增加音讯记录
            $content='有一个视频会议须要您的加入,工夫:'.$shijian;
            $datam['title']='视频会议告诉';
            $datam['content']=$content;
            $datam['shijian']=time();
            $datam['flag']='01';// 未读
            $datam['type']='03';// 会议揭示
            $datam['fqr']=$userid;
            $datam['jsr']=$v;
            $resm=M()->table('crm_message')->data($datam)->add();
            $jpush = new JPushClient(C('JPUSH_APP_KEY'), C('JPUSH_MASTER_SECRET'));
            $response = $jpush->push()
                ->setPlatform('all')  // 机型 IOS ANDROID
                ->addAlias($v)
                ->androidNotification($content)
                ->iosNotification($content,'',0,true)
                ->options(array('apns_production' => true,))
                ->send();}
        catch(\Exception $e){returnApiSuccess('增加胜利',$releaseInfo);
        }   
      } 
      returnApiSuccess('增加胜利',$releaseInfo);
    }
    else{returnApiError( '增加失败!');
      exit();}

  }

  // 设置会议状态
  public function setmeeting(){checkscret('secret');// 验证受权码
    checkdataPost('id');// 会议 ID
    checkdataPost('flag');// 会议状态

    $flag=$_POST['flag'];
    $map['id']=$_POST['id'];
    $data['flag']=$flag;
    if($flag=='02'){$data['starttime']=time();}
    else if($flag=='03'){$data['endtime']=time();}

    $releaseInfo=M()->table('crm_video_audio_meeting')->where($map)->save($data);

    if($releaseInfo){returnApiSuccess('设置胜利',$releaseInfo);
    }
    else{returnApiError( '设置失败!');
      exit();}
  }

  // 获取会议信息
  public function GetMeetingInfo(){checkscret('secret');// 验证受权码
    checkdataPost('id');// 会议 ID

    $id=$_POST['id'];
    $map['id']=$id;

    $releaseInfo=M()->table('crm_video_audio_meeting')->field('title,note,estimatetime,getusername(userid) fqr')->where($map)->find();

    if($releaseInfo){
      // 获取与会人员
      $mapu['video_meeting_id']=$id;
      $datau=M()->table('crm_video_audio_meeting_users')->field('userid,getusername(userid) username')->where($mapu)->select();
      if($datau){$releaseInfo['users']=$datau;
      }
      returnApiSuccess('查问胜利',$releaseInfo);
    }
    else{returnApiError( '查问失败!');
      exit();}
  }


// 获取腾讯视频 RTC usersig
public function getQQrtcusersig(){checkscret('secret');// 验证受权码
  checkdataPost('userid');// 用户 ID

  $sdkappid=C('sdkappid');
  $key=C('usersig_key');

  $userid=$_POST['userid'];
  
  require 'vendor/autoload.php';
  
  $api = new \Tencent\TLSSigAPIv2($sdkappid, $key);
  $sig = $api->genSig($userid);
  
  if($sig){returnApiSuccess('查问胜利',$sig);
  }
  else{returnApiError( '查问失败,请稍后再试');
    exit();}
}

}

退出移动版