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(){api.addEventListener({name: 'loginsuccess'}, (ret, err) => {this.data.username = api.getPrefs({sync: true,key: 'name'});this.data.rolename = api.getPrefs({sync: true,key: 'rolename'});this.data.organname = api.getPrefs({sync: true,key: 'organname'});});}

接口调用

封装了 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)})})}}

双击退出程序

在首页、登录页或其他需要双击退出程序的页面,在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 usersigpublic 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)=>{//重置keythis.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');//会议IDcheckdataPost('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 usersigpublic 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();}}
}
<?php
namespace Home\Controller;
use Think\Controller;
class KnowController extends Controller {public function index(){$this->show('');}//查询知识public function QueryArticles(){$title=$_POST['title'];$type=$_POST['type'];if(!empty($title)){$map['title']=array('like','%'.$title.'%');}$map['type']=$type;//计算总共有多少页$page=$_POST['page'];$rows=$_POST['rows'];$data=M()->table('crm_article')->field('id,title,author,riqi,type,content')->where($map)->order('riqi desc,id desc')->limit($rows*($page-1),$rows)->select();$total=M('crm_article')->where($map)->count();$count=array();$count['total']=$total;$count['rows']=$data;$this->ajaxreturn($count);}//增加public function AddArticles(){$title=$_POST['title'];$author=$_POST['author'];$riqi=$_POST['fbsj'];$type=$_POST['type'];$content=$_POST['content'];$data['title']=$title;$data['author']=$author;$data['riqi']=$riqi;$data['type']=$type;$data['content']=$content;$info=M()->table('crm_article')->data($data)->add();if($info>0){$this->ajaxreturn('添加成功!');}else{$this->ajaxreturn('添加失败!');}}//修改public function EditArticles(){$id=$_POST['hidid'];$title=$_POST['title'];$author=$_POST['author'];$riqi=$_POST['fbsj'];$content=$_POST['content'];$map['id']=$id;$data['title']=$title;$data['author']=$author;$data['riqi']=$riqi;$data['content']=$content;$info=M()->table('crm_article')->where($map)->save($data);if($info>0){$this->ajaxreturn('修改成功!');}else{$this->ajaxreturn('修改失败!');}}//删除public function DelArticle(){$id=$_POST['id'];$map['id']=$id;$info= M()->table('crm_article')->where($map)->delete();if($info>0){$this->ajaxreturn('删除成功!');}else{$this->ajaxreturn('删除失败!');}}
}

APICloud AVM框架 开发CRM客户管理系统相关推荐

  1. APICloud AVM框架开发消防检查助手APP

    把消防检查过程中,需要手写填报的文档,在APP端以表单填写进行实现.同时可以添加手写签名,关联照片,而且APP端表单填报很多项目进行下拉选择,极大的提高了工作效率:表单填报完成之后可通过系统后台生成w ...

  2. APICloud AVM框架 开发视频会议APP

    APP开发采用的APICloud平台的AVM 多端应用开发框架 使用 avm.js 一个技术栈可同时开发 Android & iOS 原生 App.小程序和 iOS 轻 App,且多端渲染效果 ...

  3. 使用APICloud AVM框架开发预约应用

    前段时间跟朋友一起搞了一个预约的项目,前端用的APICloud的AVM框架做的,后端用的php开发的,用的tp5框架,没几天就搞出来了.简单跟大家分享一下开发中的一些功能点的实现吧.也欢迎大家一起探讨 ...

  4. 使用APICloud AVM框架开发人事档案管理助手APP

    由于人事档案具有涉密性,所以本应用没有使用后台服务,全部功能都在APP本地实现. 数据库采用sqllite,没有使用UI框架,个人觉得AVM本身支持的flex布局配合自写CSS样式,完全可以实现市面上 ...

  5. 【超详细】SSM框架项目实战|Spring+Mybatis+Springmvc框架项目实战整合-【CRM客户管理系统】——课程笔记

    相关资料网盘链接: CRM客户管理系统资料 提取码 :0u04 P1--CRM阶段简介: web项目开发:如何分析,设计,编码,测试.        形成编程思想和编程习惯. P2--CRM的技术架构 ...

  6. JavaWeb实训项目:基于SSM框架的CRM客户关系管理系统(文章最后有源码)

    JavaWeb实训项目:基于SSM框架的CRM客户关系管理系统(附部分源码) 一.项目背景 项目演示 二.项目介绍 三.涉及技术 总结 源码地址 一.项目背景 "世上本来没有CRM,大家的生 ...

  7. js学习总结----crm客户管理系统之项目开发流程和api接口文档

    CRM ->客户管理系统 CMS ->内容发布管理系统 ERP ->企业战略信息管理系统 OA -> 企业办公管理系统 产品 / UI设计:需求分析,产品定位,市场调查...按 ...

  8. CRM客户管理系统开发 获客管理营销全搞定

    企业经营管理是有很大学问的,无论是生产经营.销售.服务还是客户管理.维护.营销都是需要付出一定的人力物力来管理的.传统的企业管理多是通过人工方式来完成,个中细节繁琐复杂,耗时耗力还很容易出现纰漏.所以 ...

  9. ssm项目——CRM客户管理系统开发准备

    目前项目放在github上,需要的同学可以直接取git clone下来,https://github.com/PAcee1/crm 这个项目是学习完spring,springmvc,mybatis后为 ...

  10. 基于 SSM 的 CRM 客户管理系统

    因为 CSDN 自己排版各种奇葩,导致代码格式可能会有问题,如果出现该情况请前往 github 查看 ---->跳转 [CRM客户管理系统] 调研 产品 需求 UI 开发 技术选型: SSM.M ...

最新文章

  1. springboot自定义配置文件
  2. OpenCASCADE绘制测试线束:图形命令之Axonometric观察器
  3. 基于easyui开发Web版Activiti流程定制器详解(二)——文件列表
  4. java游戏应龙女魃转世_应龙和女魃的凄美爱情,究竟是爱情,还是阴谋?
  5. python 遍历文件夹 文件
  6. 提取图像色彩主色调工具
  7. SQL Server中的Datediff移植到Oracle计算有误解决方案
  8. linux 生成excel格式的文件怎么打开乱码,导出的文件乱码怎么办(各位,汉字在excel表格中显示乱码,怎么办)...
  9. 四叶草clover配置工具Clover Configurator 5.4.4.0汉化版
  10. [NOI Online 2021 入门组] 切蛋糕
  11. 狂胜——Redis学习笔记
  12. Android动态更改TextView的字体大小
  13. 夜曲歌词 拼音_矢野真纪《夜曲》罗马拼音歌词
  14. 9.7 电机控制程序基础
  15. 香港银行账户主要用途
  16. mysql事务(详解)
  17. 鲲鹏微认证的 一些知识点
  18. 数据库(MySQL)学习笔记(六)
  19. 基于虚拟仪器的电磁阀综合特性系统设计
  20. 怎样制作微信小程序?

热门文章

  1. 【数据分析】销售案例——用户购买频次
  2. 163邮箱收不到邮件怎么办
  3. Buildroot笔记
  4. Word各级标题格式设置和自动排序(标题序号)设置
  5. mp-mtgsig 美团iOS 签名逆向工程分析
  6. 音视频基本概念:码率、比特率
  7. DynamoDB 如何做in查询
  8. 关于flutter出现Because flutter_app depends on flutter_screenutil >=3.0.0-beta.1 which requires Flutter S
  9. 重力对手表的走时精度有何影响?12:06:44
  10. 【网络编程--UDP、TCP】