用友log(用友logo设计理念)_期货基金_转赚网

用友log(用友logo设计理念)

小福 0 0

CRM即“客户关系管理”,其载体是一种存储客户联系信息以及追踪客户活动的软件。在移动互联时代,CRM客户管理app更具实际价值,可以帮助企业摆脱PC的束缚、以更加灵活的方式开展业务,同时妥善地存储、更新全部客户信息,吸引新客户、保留老客户以及将已有客户转为忠实客户,实现业务增长。

本文案例来自开发者实战,讲解如何采用YonBuilder移动开发平台(APICloud)构建CRM客户管理app。

一、思维导图

如何开发CRM客户管理App

二、功能介绍

1. 客户管理:录入客户信息、客户跟进、客户销售记录、直接拨打客户电话、条件筛选查询、公共客户;

2. 申请、收、发货管理;

3. 文档库、知识库;

4. 工作日志、日程管理;

5. 产品管理、库存管理;

6. 门店管理、员工管理;

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

8. 通讯录、消息提醒;

9. 即时通讯、视频会议。

三、应用模块


如何开发CRM客户管理App

四、项目目录

如何开发CRM客户管理App

五、开发过程

1. 首页导航

系统首页使用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      }]    }  }


2. 动态权限

在首页的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) => {                      });        }            }

3. 消息事件

通过sendEvent把事件广播出去,然后在其他页面通过addEventListener监听事件,通过事件名和附带的参数进行其他操作。

举例:登录成功之后,需要在个人中心加载个人信息,在首页加载相关个人的数据;添加某项数据之后,需要进行刷新列表等等。

如何开发CRM客户管理App

如何开发CRM客户管理App

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

4. 接口调用

封装了req.js进行接口调用,采用了ES6语法中的Promise是异步编程的一种解决方案(比传统的回调函数更加合理、强大),用同步操作将异步流程表达出来,避免层层嵌套回调。Promise 对象提供统一接口,使得控制异步操作更加容易。有兴趣的同学可以多研究一下Promise。

const config = {    schema: 'http',    host: '192.168.1.5',    path: 'api.php/Home',    secret:'776eca99-******-11e9-9897-*******'} function req(options) {    const baseUrl = `${config.schema}://${config.host}/${config.path}/`;    options.url = baseUrl + options.url;    return new Promise((resolve, reject) => {        api.ajax(options,  (ret, err) => {            // console.log('[' + options.method + '] ' + options.url + ' [' + api.winName + '/' + api.frameName + ']\n' + JSON.stringify({            //     ...options, ret, err            // }))            if (ret) {                resolve(ret);                api.hideProgress();            } else {                reject(err);                 api.hideProgress();            }        });    })}/** * GET请求快捷方法 * @constructor * @param url {string} 地址 * @param options {Object} 附加参数 */function GET(url, options = {}) {    return req({        ...options, url, method: 'GET'    });} /** * POST 请求快捷方法 * @param url * @param data * @param options {Object} 附加参数 * @returns {Promise<Object>} * @constructor */function POST(url, data, options = {}) {    data.secret = config.secret;    return req({        ...options, url, method: 'POST', data: {            values: data        }    });} export {    req, GET, POST, config}

在页面中调用的时候首先需要引入js文件。

//引入 import {POST, GET} from '../../script/req.js' //使用      methods: {    loadDaily(){        var data={          secret:'',          userid: api.getPrefs({sync: true,key: 'userid'})        };        api.showProgress();        POST('Index/queryleastremind',data,{}).then(ret =>{          // console.log(JSON.stringify(ret));          if(ret.flag=='Success'){            this.data.dailyList = ret.data;            this.data.isDaily = false;          }          else{            this.data.isDaily = true;          }          api.hideProgress();        }).catch(err =>{          this.data.isDaily = true;          api.toast({            msg:JSON.stringify(err)          })        })      }        }

5. 双击退出程序

在首页、登录页或其他需要双击退出程序的页面,在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          });        }      });          }

6. 清空缓存

官方自带的API clearCache,可清空全部缓存,也可选择清除多少天前的缓存。

如何开发CRM客户管理App

7. 消息推送

采用极光推送,需要集成ajpush模块。

如何开发CRM客户管理App

具体使用方法可详细阅读官方模块文档。

如何开发CRM客户管理App

推送功能初始化需要在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的时候需要,重新登陆之后可能存在切换账号的情况,也需要重新登陆。

如何开发CRM客户管理App

8. 定位功能

因为系统中的定位只需要确定当前位置即可,所有定位功能使用的是aMapLBS模块,此模块没有打开地图的功能,只需要在用到的页面直接调用获取定位即可。

如何开发CRM客户管理App

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

如何开发CRM客户管理App

            //获取当前位置信息和经纬度                       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;          }        });      }

9. 视频、语音通话

采用tencentTRTC开发音视频通话功能。需要先去腾讯云平台创建应用申请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>

10. 通讯录

基于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>

11. echarts图表

由于AVM无法解析cavans,所有图表相关的页面采用的是HTML,页面添加在HTML文件夹中,通过open.frame()进行打开。采用的Echarts.js.可去echarts官方下载,如果图标不复杂,建议使用自定义下载只选择用到的控件,减小文件体积;样式也可以自定义然后下载转述js文件。

文件目录:

如何开发CRM客户管理App

如何开发CRM客户管理App

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

12. 扫描二维码

模块文档中推荐了2种方式,如没特殊需求,推荐使用第一种。

如何开发CRM客户管理App

如何开发CRM客户管理App

//入口<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:'扫码失败,请再次尝试!'            })          }        });      }

13. 数据列表及分页

数据列表的分页查询,主要使用到的是上拉刷新和下拉刷新动作,在动作的回调中处理需要的参数,来实现数据的加载和刷新。其中处理的参数需要配合后台提供的接口进行重定义。

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

14. 导航栏底部出现“白边”问题处理

如何开发CRM客户管理App

如果navigationBar的背景设置了其他颜色,shadow使用默认的颜色,如果页面背景设置成黑色,会有条明显的“白边”效果,这时需要通过设置shadow的颜色来消除“白边”。

(1)可在需要的页面通过setNavBarAttr进行设置,具体颜色值根据情况自行选择。

api.setNavBarAttr({    shadow:'#000000'});

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

如何开发CRM客户管理App

六、后台代码

<?phpnamespace 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();      }    }}

相关内容

标签: var gt api

用友log(用友logo设计理念)文档下载: PDF DOC TXT