0
点赞
收藏
分享

微信扫一扫

APICloud AVM框架 开发CRM客户管理系统

芥子书屋 2022-04-26 阅读 35

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 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>
	export default {
		name: 'rtcvideo',
		apiready(){
			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){
						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: 0,
								w: api.frameWidth,
								h: api.frameHeight/3
							}
						});
					}
				}
				//有用户加入房间
				else if(ret.action=='remoteUserEnterRoom'){
					rectindex++;
					tencentTRTC.startRemoteView({
						rect:this.data.rects[this.data.rectindex-1],
						remoteUid:ret.remoteUserEnterRoom.userId,
					});
				}
				//有用户离开房间
				else if(ret.action=='remoteUserLeaveRoom'){
					tencentTRTC.stopRemoteView({
						remoteUid:ret.remoteUserLeaveRoom.userId,
					});
				}
			});
			this.setUserRect();
		},
		data() {
			return{
				isMute:false,
				isLoud:false,
				isStart:false,
				rects:[],
				rectindex:0
			}
		},
		methods: {
			setMute(){
				this.data.isMute = !this.data.isMute;
				tencentTRTC.muteLocalAudio({
					mute:this.data.isMute
				});
			},
			setLoud(){
				this.data.isLoud = !this.data.isLoud;
			},
			setRTC(){
				this.data.isStart = !this.data.isStart;
				 tencentTRTC.enterRoom({
					appId:14******43,
					userId:$api.getStorage('userid'),
					roomId:id,
					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'
							});
						}
					});
			},
			//视频设置最多9个人,本人画面占一行,其他8人每行4个共2行
			setUserRect(){
				for(var i=0;i<8;i++){
					if(i<4){
						rects[i]={
						x: (api.frameWidth/4)*i,
						y: api.frameHeight/3,
						w: api.frameWidth/4,
						h: api.frameWidth/4
						};
					}
					else{
						rects[i]={
						x: (api.frameWidth/4)*(i-4),
						y: (api.frameHeight/3)+(api.frameWidth/4),
						w: api.frameWidth/4,
						h: api.frameWidth/4
						};
					}
				}
			}
		}
	}
</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官方下载,下载连接

文件目录

 

<!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" />
        <link rel="stylesheet" type="text/css" href="../css/common.css" />
        <link rel="stylesheet" type="text/css" href="../Aui/css/aui.css" />
        <link rel="stylesheet" type="text/css" href="../FontAwesome/css/font-awesome.min.css" />
        <link rel="stylesheet" type="text/css" href="../css/common.css" />
        <style>
          body{background:#efefef;}
        </style>
    </head>

    <body>
      <div class="blanks"></div>
      <!-- chat1 -->
      <section class="pdd">
          <div class="aui-row pddinner">
              <div class="aui-col-xs-12 clearfix">
                  <div class="htitle clearfix">
                      <div class="pull-left"><span></span>客户统计</div>
                      <!-- <div class="pull-right aui-font-size-12 aui-text-default">&nbsp;月<i class="fa fa-angle-down" style="color:#333"></i></div> -->
                  </div>
              </div>
              <div id="Chart1" style="height:200px;">
              </div>
          </div>
      </section>
      <div class="blanks"></div>
      <!-- chat1 -->
      <section class="pdd">
          <div class="aui-row pddinner">
              <div class="aui-col-xs-12 clearfix">
                  <div class="htitle clearfix">
                      <div class="pull-left"><span></span>等级占比</div>
                  </div>
              </div>
              <div id="Chart2" style="height:200px;">
              </div>
          </div>
      </section>
      <div class="blanks"></div>
      <!-- chat1 -->
      <section class="pdd">
          <div class="aui-row pddinner">
              <div class="aui-col-xs-12 clearfix">
                  <div class="htitle clearfix">
                      <div class="pull-left"><span></span>性别占比</div>
                  </div>
              </div>
              <div id="Chart3" style="height:200px;">
              </div>
          </div>
      </section>
      <div class="blanks"></div>
      <!-- chat1 -->
      <section class="pdd">
          <div class="aui-row pddinner">
              <div class="aui-col-xs-12 clearfix">
                  <div class="htitle clearfix">
                      <div class="pull-left"><span></span>年龄占比</div>
                  </div>
              </div>
              <div id="Chart4" style="height:200px;">
              </div>
          </div>
      </section>
      <div class="blanks"></div>
    </body>
    <script type="text/javascript" src="../script/api.js"></script>
    <script type="text/javascript" src="../script/zepto.min.js"></script>
    <script type="text/javascript" src="../script/common.js"></script>
    <script type="text/javascript" src="../echarts/echarts.js"></script>
    <script type="text/javascript" src="../echarts/roma.js"></script>

   <script>
   apiready = function() {
     //ready
     fnReady();
     loaddemo1();
     loaddemo2();
   };

   //客户统计
   function loaddemo1(){
     var path=fnReadyapiaddr() +'Statistic/querykhzzl';
      var data={values:{
          secret:fnReadyapikey(),
          year:'2019'
      }};
      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'),'roma');
           var option = {
               tooltip: {
                   trigger: 'axis',
                   axisPointer: {
                       type: 'shadow'
                   }
               },
               legend: {
                   data: ['增长客户','客户总量']
               },
               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=fnReadyapiaddr() +'Statistic/querynvbl';
       var data={values:{
          secret:fnReadyapikey()
      }};
      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'),'roma');
           var myChart3 = echarts.init(document.getElementById('Chart3'),'roma');
           var myChart4 = echarts.init(document.getElementById('Chart4'),'roma');
           option2 = {
               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 = {
               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 = {
               tooltip : {
                   trigger: 'item',
                   formatter: "{a} <br/>{b} : {c} ({d}%)"
               },
               series : [
                   {
                       name: '客户年龄占比',
                       type: 'pie',
                       radius : '55%',
                       center: ['50%', '60%'],
                       data:datayear,
                       itemStyle: {
                           emphasis: {
                               shadowBlur: 10,
                               shadowOffsetX: 0,
                               shadowColor: 'rgba(0, 0, 0, 0.5)'
                           }
                       }
                   }
               ]
           };
           myChart2.setOption(option2);
           myChart3.setOption(option3);
           myChart4.setOption(option4);
        }
      })
   }
   </script>
</html>

扫描二维码 

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

//入口
<view class="column-item" @click="fnscanner">
	<image class="column-item-ico" src='../../image/co-ico5.png' mode="widthFix"></image>
	<text class="column-item-title">扫码</text>
</view>

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

数据列表及分页

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

<template>
    <scroll-view scroll-y class="page" enable-back-to-top refresher-enabled refresher-triggered={refresherTriggered} onrefresherrefresh={this.onrefresherrefresh} onscrolltolower={this.onscrolltolower}>
		<view>
			<view class="item-box" v-for="(item, index) in list" data-id={item.id}>
				<view class="top">
					<image class="top-ico" src='../../image/flag01.png' mode="widthFix" v-if="item.flag=='01'"></image>
					<image class="top-ico" src='../../image/flag02.png' mode="widthFix" v-else-if="item.flag=='02'"></image>
					<image class="top-ico" src='../../image/flag03.png' mode="widthFix" v-else-if="item.flag=='03'"></image>
					<image class="top-ico" src='../../image/flag04.png' mode="widthFix" v-else-if="item.flag=='04'"></image>
					<image class="top-ico" src='../../image/flag05.png' mode="widthFix" v-else-if="item.flag=='05'"></image>
					<image class="top-ico" src='../../image/flag06.png' mode="widthFix" v-else></image>
					<text class="top-name">{item.name}</text>
					<text class="top-level">★{item.dengji}</text>
				</view>
				<view class="mid">
					<view>
						<text class="mid-tip">录入时间</text>
						<text>{item.lrsj}</text>
					</view>
					<view>
						<text class="mid-tip">生日</text>
						<text>{item.birthday}</text>
					</view>
				</view>
				<view class="btm">
					<view class="btm-item" data-phone={item.phone} @click="call">
						<image class="btm-ico" src='../../image/TELL.png' mode="widthFix"></image>
						<text>打电话</text>
					</view>
					<view class="btm-item" data-id={item.id} @click="followRecords">
						<image class="btm-ico" src='../../image/GJ.png' mode="widthFix"></image>
						<text>跟进</text>
					</view>
					<view class="btm-item" data-id={item.id} @click="saleRecords">
						<image class="btm-ico" src='../../image/XS.png' mode="widthFix"></image>
						<text>销售</text>
					</view>
				</view>
			</view>
		</view>
		<view class="footer">
			<text class="loadDesc">{loadStateDesc}</text>
		</view>	
		<safe-area></safe-area>
    </scroll-view>
</template>
<script>
	import $util from '../../utils/utils.js'
	import {POST} from '../../script/req.js'
	export default {
		name: 'customer',
		apiready(){
			//设置筛选按钮
			api.setNavBarAttr({
				rightButtons: [{
					text:'筛选',
					color:'#ffffff'
				}]
			});			
			api.addEventListener({
				name:'navitembtn'
			}, (ret)=>{
				if(ret.type=='right'){
					api.openFrame({
						name: 'customer-select',
						url: 'customer-select.stml',
						rect: {
							x: 0,
							y: 0,
							w: 'auto',
							h: 'auto'
						},
						pageParam: {
							name: 'test'
						}
					});
				}
			})
			api.addEventListener({
				name:'doSearchCustomer'
			}, (ret)=>{
				//重置key
				this.data.key = '';
				// console.log(JSON.stringify(ret));
				this.data.status = ret.value.status;
				this.data.level = ret.value.level;
				this.data.sex = ret.value.sex;
				this.loadData();
			})
			this.data.key = api.pageParam.key;
			//console.log(this.data.key);
			this.loadData();
		},
		data() {
			return{
				key:'',
				list:[],
				skip: 0,
				refresherTriggered: false,
				haveMoreData: true,
				loading: false,
				status:'',
				level:'',
				sex:''
			}
		},
		computed: {			
			loadStateDesc(){
				if (this.data.loading || this.data.haveMoreData) {
					return '加载中...';
				} else if (this.list.length > 0) {
					return '没有更多啦';
				} else {
					return '暂时没有内容';
				}
			}
		},
		methods: {
			loadData(loadMore) {
				this.data.loading = true;
				var limit = 10;
				var skip = loadMore?this.data.skip+limit:0;
				var data={			
					secret:'',
           			key:this.data.key,
            		limit:limit,
            		skip:skip,			
					userid:api.getPrefs({sync: true,key: 'userid'}),
					roleid:api.getPrefs({sync: true,key: 'roleid'}),
					organid:api.getPrefs({sync: true,key: 'organid'}),
				};
				api.showProgress();
				POST('Customer/querycustomerlist',data,{}).then(ret =>{
					// console.log(JSON.stringify(ret));
					if(ret.flag=='Success'){
						let noticedata = ret.data;
						this.data.haveMoreData = noticedata.length == limit;
						if (loadMore) {
							this.data.list = this.data.list.concat(noticedata);
						} else {
							this.data.list = noticedata;
						}
						this.data.skip = skip;
					}
					else{
						this.data.haveMoreData = false;
						this.data.list=[];
					}
					this.data.loading = false;
					this.data.refresherTriggered = false;
					api.hideProgress();
				})
			},
			/*下拉刷新页面*/
			onrefresherrefresh(){
				this.data.refresherTriggered = true;
				this.loadData(false);
			},
			onscrolltolower() {
				if (this.data.haveMoreData) {
					this.loadData(true);
				}
			},
			call(e){
				var phone = e.target.dataset.phone;
				api.call({
					type: 'tel',
					number: phone
				});
			},
			followRecords(e){
				var id = e.target.dataset.id;
				$util.openWin({
					name: 'followRecords',
					url: 'followRecords.stml',
					title: '客户跟进记录',
					pageParam:{
						id:id
					}
				});
			},
			saleRecords(e){
				var id = e.target.dataset.id;
				$util.openWin({
					name: 'saleRecords',
					url: 'saleRecords.stml',
					title: '客户销售记录',
					pageParam:{
						id:id
					}
				});
			}
		}
	}
</script>
<style>
    .page {
        height: 100%;
		background-color: #f0f0f0;
    }
	.item-box{
		margin: 10px;
		background-color: #ffffff;
		border-radius: 5px;
		padding: 10px;
	}
	.top{
		flex-flow: row nowrap;
		align-items: center;
		justify-content: space-between;
	}
	.mid{
		flex-flow: row nowrap;
		align-items: center;
		justify-content: space-between;
		padding: 10px 0;
	}
	.mid-tip{
		font-size: 13px;
		color: #666666;
	}
	.top-level{
		color: #ffd700;
	}
	.top-ico{
		width: 30px;
	}
	.top-name{
		font-size: 20px;
	}
	.btm{
		flex-flow: row nowrap;
		align-items: center;
		justify-content: space-between;
		padding-top: 10px;
		border-top: 1px solid #ccc;
	}
	.btm-item{
		flex-flow: row nowrap;
		align-items: center;
		justify-content: center;
	}
	.btm-ico{
		width: 20px;
		padding-right: 5px;
	}
	.footer {
        height: 44px;
        justify-content: center;
        align-items: center;
    }
    .loadDesc {
        width: 200px;
        text-align: center;
    }
</style>

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

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

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

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

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

举报

相关推荐

0 条评论