CRM客户管理App实战开发教程(附代码)pngnavbar

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

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

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

3.文档库、知识库;

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

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

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

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

8.通讯录、消息提醒;

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

1.首页导航

{

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

"text":"通讯录",

"iconPath":"image/navbar/book-o.png",

"selectedIconPath":"image/navbar/book.png",

"text":"设置",

"iconPath":"image/navbar/set-o.png",

"selectedIconPath":"image/navbar/set.png",

}]

}

2.动态权限

apiready(){

letlimits=[];

//获取权限

varresultList=api.hasPermission({

list:['storage','location','camera','photos','phone']

});

if(resultList[0].granted){

}else{

limits.push(resultList[0].name);

if(resultList[1].granted){

limits.push(resultList[1].name);

if(resultList[2].granted){

limits.push(resultList[2].name);

if(resultList[3].granted){

limits.push(resultList[3].name);

if(resultList[4].granted){

limits.push(resultList[4].name);

if(limits.length>0){

api.requestPermission({

list:limits,

},(res)=>{

3.消息事件

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

methods:{

login(){

if(!this.data.username){

this.showToast("姓名不能为空");

return;

if(!this.data.password){

this.showToast("密码不能为空");

vardata={

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

})

api.hideProgress();

}).catch(err=>{

msg:JSON.stringify(err)

4.接口调用

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

constconfig={

host:'192.168.1.5',

path:'api.php/Home',

secret:'776eca99-******-11e9-9897-*******'

functionreq(options){

constbaseUrl=`${config.schema}://${config.host}/${config.path}/`;

options.url=baseUrl+options.url;

returnnewPromise((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);

reject(err);

/**

*GET请求快捷方法

*@constructor

*@paramurl{string}地址

*@paramoptions{Object}附加参数

*/

functionGET(url,options={}){

returnreq({

...options,url,method:'GET'

*POST请求快捷方法

*@paramurl

*@paramdata

*@returns{Promise}

functionPOST(url,data,options={}){

data.secret=config.secret;

...options,url,method:'POST',data:{

values:data

export{

req,GET,POST,config

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

//引入

import{POST,GET}from'../../script/req.js'

//使用

loadDaily(){

userid:api.getPrefs({sync:true,key:'userid'})

POST('Index/queryleastremind',data,{}).then(ret=>{

this.data.dailyList=ret.data;

this.data.isDaily=false;

this.data.isDaily=true;

5.双击退出程序

//监听返回双击退出程序

api.setPrefs({

key:'time_last',

value:'0'

api.addEventListener({

name:'keyback'

},(ret,err)=>{

vartime_last=api.getPrefs({sync:true,key:'time_last'});

vartime_now=Date.parse(newDate());

if(time_now-time_last>2000){

api.setPrefs({key:'time_last',value:time_now});

msg:'再按一次退出APP',

duration:2000,

location:'bottom'

api.closeWidget({

silent:true

6.清空缓存

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

7.消息推送

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

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

推送功能初始化需要在app每次启动的时候进行集成,将初始化极光推送的方法集成在util工具类中,在首页进行初始化。

fnReadyAJpush(){

varjpush=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){

varali=$api.getStorage('userid');

vartag=$api.getStorage('roleid');

//绑定别名

if($api.getStorage('userid')){

jpush.bindAliasAndTags({

alias:ali,

tags:[tag]

},function(ret,err){

if(ret.statusCode==0){

api.toast({msg:'推送初始化成功'});

api.toast({msg:'绑定别名失败'});

//监听消息

jpush.setListener(function(ret){

varcontent=ret.content;

alert(content);

api.toast({msg:'推送服务初始化失败'});

初始化使用,每次启动app的时候需要,重新登陆之后可能存在切换账号的情况,也需要重新登陆。

8.定位功能

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

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

//获取当前位置信息和经纬度

setLocation(){

varaMapLBS=api.require('aMapLBS');

aMap.updateLocationPrivacy({

privacyAgree:'didAgree',

privacyShow:'didShow',

containStatus:'didContain'

aMapLBS.configManager({

accuracy:'hundredMeters',

filter:1

if(ret.status){

aMapLBS.singleLocation({

timeout:2

this.data.lon=ret.lon;

this.data.lat=ret.lat;

aMapLBS.singleAddress({

this.data.address=ret.formattedAddress;

msg:'定位初始化失败,请开启手机定位。'

returnfalse;

9.视频、语音通话

采用tencentTRTC开发音视频通话功能。需要先去腾讯云平台创建应用申请key,在通过官方提供的方法生成userSig。

生成userSig代码:

//获取腾讯视频RTCusersig

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

returnApiError('查询失败,请稍后再试');

exit();

用户视频画面需要根据当前视频用户数,进行计算调整。

10.通讯录

0">

{item.zkey}

{it.remark}

{it.position}

{it.dept_name}

{item.zkey}

name:'tellbook',

this.loadData();

list:[],

zIndex:''

loadData(){

POST('Index/gettellbook',data,{}).then(ret=>{

this.toTree(ret.data);

//处理数据

toTree(data){

varbook=[];

varzm='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=>{

vararrz=data.filter((item)=>{

returnitem.zkey==element

book.push({'zkey':element,children:arrz});

this.data.list=book;

//console.log(JSON.stringify(book));

scrollToE(e){

varid=e.target.dataset.id;

varbook=document.getElementById('book');

book.scrollTo({

view:id

this.data.zIndex=id;

takePhone(e){

varphone=e.target.dataset.phone;

api.call({

type:'tel',

number:phone

.nav{

margin:010px;

padding:010px;

.nav-title{

font-size:20px;

.box{

justify-content:flex-start;

margin:10px;

border-bottom:1pxsolid#ccc;

padding-bottom:10px;

.avator{

padding:5px;

.right{

padding-left:20px;

.bt{

.bt-position{

font-size:14px;

color:#666666;

.bt-part{

.right-nav{

position:absolute;

right:10px;

width:30px;

padding:30px0;

.right-nav-item{

padding-bottom:5px;

.right-nav-item-on{

color:#035dff;

.right-nav-item-off{

width:50px;

11.echarts图表

文件目录:

统计-客户

body{background:#efefef;padding:10px10px50px10px;}

.chart-box{

border-radius:5px;

margin-top:10px;

apiready=function(){

loaddemo1();

loaddemo2();

//客户统计

functionloaddemo1(){

vardata={values:{

secret:'776eca99-****-11e9-******00163e008b45',

year:'2021'

}};

fnPost(path,data,function(ret,err){

if(ret['flag']=='Success'){

vardata=ret['data'];

vararryaxis=[],arrzzl=[],arrall=[];

for(vari=0;i

arryaxis[i]=MonthToZhcn(data[i]['mon']);

arrzzl[i]=data[i]['num'];

arrall[i]=data[i]['monall'];

varmyChart=echarts.init(document.getElementById('Chart1'),'customed');

varoption={

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:'客户总量',

data:arrall

]

myChart.setOption(option);

functionloaddemo2(){

vardatayear=[];

secret:'776eca99-a1e5-11e9-9897-00163e008b45'

if(data['year']){

varyear=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岁以上'});

varmyChart2=echarts.init(document.getElementById('Chart2'),'customed');

varmyChart3=echarts.init(document.getElementById('Chart3'),'customed');

varmyChart4=echarts.init(document.getElementById('Chart4'),'customed');

option2={

text:'客户等级分析'

trigger:'item',

formatter:"{a}
{b}:{c}({d}%)"

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

text:'客户性别分析'

name:'客户性别占比',

data:data['sex'],

option4={

text:'客户年龄分析'

name:'客户年龄占比',

data:datayear,

myChart2.setOption(option2);

myChart3.setOption(option3);

myChart4.setOption(option4);

12.扫描二维码

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

//入口

扫码

fnscanner(){

varFNScanner=api.require('FNScanner');

FNScanner.open({

autorotation:true

console.log(JSON.stringify(err));

if(ret.eventType=='success'){

msg:'扫码成功,即将跳转详情页面'

msg:'扫码失败,请再次尝试!'

13.数据列表及分页

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

{item.name}

★{item.dengji}

{item.lrsj}

生日

{item.birthday}

跟进

销售

{loadStateDesc}

name:'customer',

//设置筛选按钮

text:'筛选',

color:'#ffffff'

api.openFrame({

name:'customer-select',

url:'customer-select.stml',

y:0,

w:'auto',

h:'auto'

pageParam:{

name:'test'

name:'doSearchCustomer'

//重置key

this.data.key='';

this.data.status=ret.value.status;

this.data.level=ret.value.level;

this.data.sex=ret.value.sex;

this.data.key=api.pageParam.key;

//console.log(this.data.key);

key:'',

skip:0,

refresherTriggered:false,

haveMoreData:true,

loading:false,

status:'',

level:'',

sex:''

computed:{

loadStateDesc(){

if(this.data.loading||this.data.haveMoreData){

return'加载中...';

}elseif(this.list.length>0){

return'没有更多啦';

return'暂时没有内容';

loadData(loadMore){

this.data.loading=true;

varlimit=10;

varskip=loadMorethis.data.skip+limit:0;

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

POST('Customer/querycustomerlist',data,{}).then(ret=>{

letnoticedata=ret.data;

this.data.haveMoreData=noticedata.length==limit;

if(loadMore){

this.data.list=this.data.list.concat(noticedata);

this.data.list=noticedata;

this.data.skip=skip;

this.data.haveMoreData=false;

this.data.list=[];

this.data.loading=false;

this.data.refresherTriggered=false;

/*下拉刷新页面*/

onrefresherrefresh(){

this.data.refresherTriggered=true;

this.loadData(false);

onscrolltolower(){

if(this.data.haveMoreData){

this.loadData(true);

call(e){

followRecords(e){

$util.openWin({

name:'followRecords',

url:'followRecords.stml',

title:'客户跟进记录',

id:id

saleRecords(e){

name:'saleRecords',

url:'saleRecords.stml',

title:'客户销售记录',

background-color:#f0f0f0;

.item-box{

padding:10px;

.top{

.mid{

padding:10px0;

.mid-tip{

.top-level{

color:#ffd700;

.top-ico{

.top-name{

.btm{

padding-top:10px;

border-top:1pxsolid#ccc;

.btm-item{

.btm-ico{

width:20px;

padding-right:5px;

height:44px;

.loadDesc{

width:200px;

text-align:center;

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

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

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

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

六、后台代码

namespaceHome\Controller;

require'vendor/autoload.php';//注意位置一定要在引入ThinkPHP入口文件之前

useThink\Controller;

useJPush\ClientasJPushClient;

classVideoControllerextendsController{

//查询视频会议列表

publicfunctionqueryvideomeetinglist(){

checkdataPost('limit');//下一次加载多少条

$limit=$_POST['limit'];

$skip=$_POST['skip'];

if(empty($skip)){

$skip=0;

$map['_string']='(find_in_set('.$userid.',getvideomeetingusers(id))andflag<>\'03\')or(userid='.$userid.'andflag<>\'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('estimatetimedesc')->select();

if($releaseInfo){

returnApiSuccess('查询成功',$releaseInfo);

returnApiError('查询失败!');

//查询参加音视频会议人员列表

publicfunctionqueryvideomeetingpersonlist(){

$releaseInfo=M()->table('crm_user')->field('idasemployee_id,name,getorganname(organid)remark,getrolename(roleid)position,pinyinasphonetic')->where($map)->order('organid')->select();

//增加视频会议

publicfunctionaddvideomeeting(){

$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();

//添加人员参加会议记录

foreach($arridsas$v){

$datap['video_meeting_id']=$releaseInfo;

$datap['userid']=$v;

$res=M()->table('crm_video_audio_meeting_users')->data($datap)->add();

//推送视频会议消息

try{

//添加消息记录

$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=newJPushClient(C('JPUSH_APP_KEY'),C('JPUSH_MASTER_SECRET'));

$response=$jpush->push()

->setPlatform('all')//机型IOSANDROID

->addAlias($v)

->androidNotification($content)

->iosNotification($content,'',0,true)

->options(array(

'apns_production'=>true,

->send();

catch(\Exception$e){

returnApiSuccess('添加成功',$releaseInfo);

returnApiError('添加失败!');

publicfunctionsetmeeting(){

checkdataPost('id');//会议ID

checkdataPost('flag');//会议状态

$flag=$_POST['flag'];

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

$data['flag']=$flag;

if($flag=='02'){

$data['starttime']=time();

elseif($flag=='03'){

$data['endtime']=time();

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

returnApiSuccess('设置成功',$releaseInfo);

returnApiError('设置失败!');

//获取会议信息

publicfunctionGetMeetingInfo(){

$id=$_POST['id'];

$map['id']=$id;

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

//获取与会人员

$mapu['video_meeting_id']=$id;

$datau=M()->table('crm_video_audio_meeting_users')->field('userid,getusername(userid)username')->where($mapu)->select();

THE END
1.java版CRM客户关系管理系统crm管理系统CRM客户关系管理系统 一、待办事项 1、今日需联系客户:客户列表(名称、来源、手机、电话、邮箱等)、客户查询(状态:今日需联系、已逾期、已联系;归属:我负责、我参与的、下属负责的) 2、分配给我的线索:线索列表(名称、来源、手机、电话、邮箱等)、线索查询(状态:待跟进、已跟进) https://blog.csdn.net/m0_46413639/article/details/144375394
2.CRM客户关系管理系统,完整系统(附源码)11350654的技术博客CRM客户关系管理系统,完整系统(附源码) 一、前言 利用软件、硬件和网络技术,为企业建立一个客户信息收集、管理、分析和利用的信息系统。以客户数据的管理为核心,记录企业在市场营销和销售过程中和客户发生的各种交互行为,以及各类有关活动的状态,提供各类数据模型,为后期的分析和决策提供支持。。https://blog.51cto.com/mashangyouqian/10824035
3.第七模块:项目实战一第1章项目实战:CRM客户关系管理系统开发07-销售管理系统权限信息录入 08-快速实现简单的权限控制的设计思路 09-快速实现简单权限之初始化用户权限 10-快速实现权限控制之权限校验 01-crm介绍 1、CRM系统初识; 2、CRM三大模块初识(权限、stark组、CRM); 02-权限系统介绍 1、权限开发的重要性; https://www.cnblogs.com/tqtl911/p/9131609.html
4.31天学会CRM项目开发:C#编程入门及项目实战pdf扫描版[86MB]附随书31天学会CRM项目开发将用31天的时间,带领读者共同完成一套企业级客户关系管理系统(CRM)。本书共包括五大部分, 一部分为需求篇,讨论程序员职业需求、企业信息化需求、CRM需求及解决方案; 二部分为基础篇,带领读者熟悉软件开发环境、WinForm、C#及数据库编程; 三部分为提高篇,带领读者熟悉ASP.NET Web Service,开发Chttps://www.jb51.net/books/586570.html
5.企业自己如何快速开发一个简单实用的CRM客户管理系统?一、企业可以自己开发一套CRM系统吗? CRM软件用于客户关系管理,市面上有很多现成的CRM管理软件,是专门的CRM软件厂商提供的,那么企业可以自行开发CRM软件吗? 实际上,CRM软件的本质是管理软件,设立好架构是可以自行开发的,市面上就有一些免费的开源CRM软件,它只具有CRM软件的基本架构,企业可以通过it人员自行开发属于本https://cloud.tencent.com/developer/article/2043939