CRM客户管理系统,通过信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而晋升其治理形式,向客户提供翻新式的个性化的客户交互和服务的过程。其最终目标是吸引新客户、保留老客户以及将已有客户转为忠诚客户,减少市场。
APP 开发采纳APICloud AVM框架,后盾采纳PHP。
思维导图
性能介绍
1.客户治理:录入客户信息、客户跟进、客户销售记录、间接拨打客户电话、条件筛选查问、公共客户
2.申请、收、发货治理
3.文档库、知识库
4.工作日志、日程治理
5.产品治理、库存治理
6.门店治理、员工治理
7.统计分析:客户统计分析、门店统计分析、员工统计分析、销售统计分析
8.通讯录、音讯揭示
9.即时通讯、视频会议
利用模块
我的项目目录
开发介绍
首页导航
零碎首页应用tabLayout,能够将相干参数配置在JSON文件中,再在config.xml中将content的值设置成该JSON文件的门路。如果底部导航没有非凡需要这里强烈建议大家应用tabLayout为APP进行布局,官网曾经将各类手机屏幕及不同的分辨率进行了适配,免去了很多对于适配方面的问题。
{
"name": "root","hideNavigationBar": true,"navigationBar": { "background": "#035dff", "color": "#fff", "shadow": "#035dff", "hideBackButton": true},"tabBar": { "scrollEnabled": false, "background": "#fff", "shadow": "#f1f1f1", "color": "#8a8a8a", "selectedColor": "#000000", "index":0, "preload": 0, "frames": [{ "name": "home", "url": "pages/main/home.stml", "title": "主页" }, { "name": "notice", "url": "pages/notice/notice-index.stml", "title": "音讯告诉" }, { "name": "tellbook", "url": "pages/main/tellbook.stml", "title": "通讯录" }, { "name": "my", "url": "pages/seeting/my.stml", "title": "集体核心" }], "list": [{ "text": "主页", "iconPath": "image/navbar/home-o.png", "selectedIconPath": "image/navbar/home.png", "scale":3 }, { "text": "揭示", "iconPath": "image/navbar/notice-o.png", "selectedIconPath": "image/navbar/notice.png", "scale":3 }, { "text": "通讯录", "iconPath": "image/navbar/book-o.png", "selectedIconPath": "image/navbar/book.png", "scale":3 }, { "text": "设置", "iconPath": "image/navbar/set-o.png", "selectedIconPath": "image/navbar/set.png", "scale":3 }]}
}
动静权限
在首页的apiready中依据提醒受权须要获取的权限,APP每次启动的时候就会判断是否已受权,如果未受权就是提醒进行受权。
apiready(){ let limits=[]; //获取权限 var resultList = api.hasPermission({ list: ['storage', 'location', 'camera', 'photos', 'phone'] }); if (resultList[0].granted) { // 已受权,能够持续下一步操作 } else { limits.push(resultList[0].name); } if (resultList[1].granted) { // 已受权,能够持续下一步操作 } else { limits.push(resultList[1].name); } if (resultList[2].granted) { // 已受权,能够持续下一步操作 } else { limits.push(resultList[2].name); } if (resultList[3].granted) { // 已受权,能够持续下一步操作 } else { limits.push(resultList[3].name); } if (resultList[4].granted) { // 已受权,能够持续下一步操作 } else { limits.push(resultList[4].name); } if(limits.length>0){ api.requestPermission({ list: limits, }, (res) => { }); } }
音讯事件通过sendEvent把事件播送进来,而后在其余页面通过addEventListener监听事件,通过事件名和附带的参数进行其余操作。举例:登录胜利之后,须要在集体核心加载个人信息,在首页加载相干集体的数据;增加某项数据之后,须要进行刷新列表等等。
methods: {
login(){ if (!this.data.username) { this.showToast("姓名不能为空"); return; } if (!this.data.password) { this.showToast("明码不能为空"); return; } var data={ secret:'', user:this.data.username, psw:this.data.password }; api.showProgress(); POST('Index/queryuserinfo',data,{}).then(ret =>{ // console.log(JSON.stringify(ret)); if(ret.flag=='Success'){ api.setPrefs({key:'username',value:ret.data.username}); //api.setPrefs({key:'password',value:ret.data.password}); api.setPrefs({key:'userid',value:ret.data.id}); api.setPrefs({key:'roleid',value:ret.data.roleid}); api.setPrefs({key:'rolename',value:ret.data.rolename}); api.setPrefs({key:'organid',value:ret.data.organid}); api.setPrefs({key:'organname',value:ret.data.organname}); api.setPrefs({key:'organtype',value:ret.data.organtype}); api.setPrefs({key:'phone',value:ret.data.phone}); api.setPrefs({key:'name',value:ret.data.name}); api.sendEvent({ name: 'loginsuccess', }); api.closeWin(); } else{ api.toast({ msg:'登录失败!请稍后再试。' }) } api.hideProgress(); }).catch(err =>{ api.toast({ msg:JSON.stringify(err) }) }) } }
双击退出程序
在首页、登录页或其余须要双击退出程序的页面,在apiready中增加。
apiready(){ //监听返回 双击退出程序 api.setPrefs({ key: 'time_last', value: '0' }); api.addEventListener({ name : 'keyback' }, (ret, err) => { var time_last = api.getPrefs({sync: true,key: 'time_last'}); var time_now = Date.parse(new Date()); if (time_now - time_last > 2000) { api.setPrefs({key:'time_last',value:time_now}); api.toast({ msg : '再按一次退出APP', duration : 2000, location : 'bottom' }); } else { api.closeWidget({ silent : true }); } }); }
清空缓存
官网自带的API clearCache,可状况全副缓存,也可抉择革除多少天前的缓存。
音讯推送采纳极光推送,须要集成ajpush模块。
具体应用办法可具体浏览官网模块文档。
推送性能初始化须要在APP每次启动的时候进行集成,将初始化极光推送的办法集成在util工具类中,在首页进行初始化。
fnReadyAJpush(){
var jpush = api.require('ajpush'); api.addEventListener({name:'pause'}, function(ret,err) { onPause();//监听利用进入后盾,告诉jpush暂停事件 }) api.addEventListener({name:'resume'}, function(ret,err) { onResume();//监听利用复原到前台,告诉jpush复原事件 }) //设置初始化 jpush.init(function(ret, err){ if(ret && ret.status){ var ali=$api.getStorage('userid'); var tag=$api.getStorage('roleid'); //绑定别名 if($api.getStorage('userid')){ jpush.bindAliasAndTags({ alias:ali, tags:[tag] }, function(ret, err){ if(ret.statusCode==0){ api.toast({ msg: '推送初始化胜利'}); } else{ api.toast({ msg: '绑定别名失败'}); } }); } //监听音讯 jpush.setListener(function(ret) { var content = ret.content; alert(content); }); } else{ api.toast({ msg: '推送服务初始化失败'}); } });}
初始化应用,每次启动APP的时候须要,从新登陆之后可能存在切换账号的状况,也须要从新登陆。
定位性能
因为零碎中的定位只须要确定以后地位即可,所有定位性能应用的是aMapLBS模块,此模块没有关上地图的性能,只须要在用到的页面间接调用获取定位即可。
应用前须要在config.xml中进行配置,具体参数须要去高德开放平台去申请。
//获取以后地位信息和经纬度 setLocation(){ var aMapLBS = api.require('aMapLBS'); aMap.updateLocationPrivacy({ privacyAgree:'didAgree', privacyShow:'didShow', containStatus:'didContain' }); aMapLBS.configManager({ accuracy: 'hundredMeters', filter: 1 }, (ret, err) => { if (ret.status) { aMapLBS.singleLocation({ timeout: 2 }, (ret, err) => { if (ret.status) { this.data.lon = ret.lon; this.data.lat = ret.lat; } }); aMapLBS.singleAddress({ timeout: 2 }, (ret, err) => { if (ret.status) { this.data.address = ret.formattedAddress; } }); } else{ api.toast({ msg:'定位初始化失败,请开启手机定位。' }) return false; } }); }
视频、语音通话
采纳tecnetRTC开发音视频通话性能。须要先去腾讯云平台创立利用申请key,在通过官网提供的办法生成userSig。
生成userSig代码
//获取腾讯视频RTC usersig
public function getQQrtcusersig(){ checkscret('secret');//验证受权码 checkdataPost('userid');//用户ID $sdkappid=C('sdkappid'); $key=C('usersig_key'); $userid=$_POST['userid']; require 'vendor/autoload.php'; $api = new \Tencent\TLSSigAPIv2($sdkappid, $key); $sig = $api->genSig($userid); if($sig){ returnApiSuccess('查问胜利',$sig); } else{ returnApiError( '查问失败,请稍后再试'); exit(); }}
用户视频画面须要依据以后视频用户数,进行计算调整。
<template>
<view class="page"> <view class="video-bk"></view> <view class="footer"> <view class="footer-item" @click="setLoud"> <image class="footer-item-ico" src='../../image/loud-on.png' mode="widthFix" v-if="isLoud"></image> <image class="footer-item-ico" src='../../image/loud-off.png' mode="widthFix" v-else></image> <text class="footer-item-label">免提</text> </view> <view class="footer-item" @click="setRTC"> <image class="footer-item-ico" src='../../image/stop.png' mode="widthFix" v-if="isStart"></image> <image class="footer-item-ico" src='../../image/start.png' mode="widthFix" v-else></image> <text class="footer-item-label" v-if="isStart">完结</text> <text class="footer-item-label" v-else>开始</text> </view> <view class="footer-item" @click="setMute"> <image class="footer-item-ico" src='../../image/mute-on.png' mode="widthFix" v-if="isMute"></image> <image class="footer-item-ico" src='../../image/mute-off.png' mode="widthFix" v-else></image> <text class="footer-item-label">静音</text> </view> </view></view>
</template>
<script>
import $util from '../../utils/utils.js'import {POST} from '../../script/req.js'export default { name: 'rtcvideo', apiready(){ this.data.roomId = api.pageParam.id; this.data.meetStart = api.pageParam.userid; var tencentTRTC= api.require('tencentTRTC'); api.setNavBarAttr({ shadow:'#000000' }); //IOS禁用左右滑动 api.setWinAttr({ slidBackEnabled: false }); api.addEventListener({ name:'keyback' }, (ret) =>{ //禁用返回按钮 }) api.addEventListener({ name: 'navbackbtn' }, (ret, err) => { api.confirm({ title: '揭示', msg: '你确定要来到本次会议吗?', buttons: ['确定', '持续'] },(ret)=>{ var index = ret.buttonIndex; if(index==1){ tencentTRTC.exitRoom({ }); api.closeWin(); } }) }); //增加右键切换摄像头 api.setNavBarAttr({ rightButtons: [{ iconPath:'widget://image/switch.png' }] }); api.addEventListener({ name:'navitembtn' }, (ret)=>{ if(ret.type=='right'){ //切换前后摄像头 tencentTRTC.switchCamera({ }); api.toast({ msg:'切换胜利' }) } }) //视频模块监听事件 var tencentTRTC= api.require('tencentTRTC'); //view disappear 监听用户间接敞开APP的状况 默认把用户本人退出房间 api.addEventListener({name:'viewdisappear'},function(ret,err){ tencentTRTC.exitRoom({ }); }); //监听RTC事件 tencentTRTC.setTRTCListener({},(ret, err) => { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if(ret.status){ if(ret.action=='enterRoom'){ //开启语音 tencentTRTC.startLocalAudio({ }); tencentTRTC.startLocalPreview({ rect:{ x: 0, y: api.safeArea.top+45, w: api.frameWidth, h: api.frameHeight/3 } }); } //有用户退出房间 else if(ret.action=='remoteUserEnterRoom'){ // console.log(this.data.rectindex); if(this.data.index>4) var thisRect = { x: (api.frameWidth/4)*this.data.rectindex, y: api.frameHeight/3+api.safeArea.top+45, w: api.frameWidth/4, h: api.frameWidth/4 } tencentTRTC.startRemoteView({ rect:thisRect, remoteUid:ret.remoteUserEnterRoom.userId, }); this.data.rectindex++; } //有用户来到房间 else if(ret.action=='remoteUserLeaveRoom'){ tencentTRTC.stopRemoteView({ remoteUid:ret.remoteUserLeaveRoom.userId, }); } } else{ api.toast({ msg:JSON.stringify(err) }) } }); }, data() { return{ isMute:false, isLoud:false, isStart:false, rects:[], rectindex:0, roomId:'', meetStart:'', mTop:api.safeArea.top+45 } }, methods: { setMute(){ var tencentTRTC= api.require('tencentTRTC'); this.data.isMute = !this.data.isMute; tencentTRTC.muteLocalAudio({ mute:this.data.isMute }); }, setLoud(){ this.data.isLoud = !this.data.isLoud; }, setRTC(){ var tencentTRTC= api.require('tencentTRTC'); if(this.data.isStart){ if(this.data.meetStart == api.getPrefs({sync: true,key: 'userid'})){ //发起人来到房间 会议完结 this.setRTCStatus('03'); } tencentTRTC.exitRoom({ }); api.closeWin(); } else{ var data={ secret:'', userid: api.getPrefs({sync: true,key: 'userid'}) }; api.showProgress(); POST('Video/getQQrtcusersig',data,{}).then(ret =>{ console.log(JSON.stringify(ret)); if(ret.flag=='Success'){ this.data.isStart = !this.data.isStart; tencentTRTC.enterRoom({ appId:14*******272, userId:api.getPrefs({sync: true,key: 'userid'}), roomId:this.data.roomId, userSig:ret.data, scene:1 },(ret, err) => { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if(ret.result<0){ api.toast({ msg: '网络谬误', duration: 2000, location: 'bottom' }); } }); //设置会议状态为开始 this.setRTCStatus('02'); } api.hideProgress(); }).catch(err =>{ api.toast({ msg:JSON.stringify(err) }) }) } }, //视频设置最多9集体,自己画面占一行,其余8人每行4个共2行 setUserRect(){ for(var i=0;i<8;i++){ if(i<4){ this.data.rects[i]={ x: (api.frameWidth/4)*i, y: api.frameHeight/3+this.data.mTop, w: api.frameWidth/4, h: api.frameWidth/4 }; } else{ this.data.rects[i]={ x: (api.frameWidth/4)*(i-4), y: (api.frameHeight/3)+(api.frameWidth/4)+this.data.mTop, w: api.frameWidth/4, h: api.frameWidth/4 }; } } // console.log(JSON.stringify(this.data.rects)); }, //设置会议状态 setRTCStatus(status){ var data={ secret:'', id: this.data.roomId, flag:status }; api.showProgress(); POST('Video/setmeeting',data,{}).then(ret =>{ console.log(JSON.stringify(ret)); if(ret.flag=='Success'){ //在会议列表监听 刷新会议列表 已完结的不在显示 api.sendEvent({ name: 'setmeeting' }); } api.hideProgress(); }).catch(err =>{ api.toast({ msg:JSON.stringify(err) }) }) } }}
</script>
<style>
.page { height: 100%; justify-content: space-between; background-color: #ffffff;}.video-bk{ height: 100%; background-color: #000000;}.footer{ height: 70px; background-color: #ffffff; flex-flow: row nowrap; justify-content: space-around; margin-bottom: 30px; align-items: center; margin-top: 20px;}.footer-item{ align-items: center; justify-content: center;}.footer-item-ico{ width: 45px;}.footer-item-label{ font-size: 13px;}
</style>
通讯录
基于scroll-view进行开发实现通讯录性能,可间接拨打电话
<template>
<view> <scroll-view class="page" scroll-y show-scrollbar="false" id="book"> <safe-area></safe-area> <view class="item" v-for="(item, index) in list" v-show="item.children.length>0"> <view class="nav" id={item.zkey}> <text class="nav-title">{item.zkey}</text> </view> <view class="box" v-for="(it, pindex) in item.children" data-phone={it.phone} @click="takePhone"> <image class="avator" src='../../image/avator.png' mode="widthFix"></image> <view class="right"> <text class="name">{it.remark}</text> <view class="bt"> <text class="bt-position">{it.position}</text> <text class="bt-part">{it.dept_name}</text> </view> </view> </view> </view> </scroll-view> <scroll-view class="right-nav" scroll-y show-scrollbar="false"> <view class="right-nav-item" data-id={item.zkey} @click="scrollToE" v-for="(item, index) in list"> <text class={item.zkey==zIndex?'right-nav-item-on':'right-nav-item-off'}>{item.zkey}</text> </view> </scroll-view></view>
</template>
<script>
import {POST} from '../../script/req.js'export default { name: 'tellbook', apiready(){ this.loadData(); }, data() { return{ list:[], zIndex:'' } }, methods: { loadData(){ var data={ secret:'', userid:api.getPrefs({sync: true,key: 'userid'}) }; api.showProgress(); POST('Index/gettellbook',data,{}).then(ret =>{ if(ret.flag=='Success'){ this.toTree(ret.data); } api.hideProgress(); }).catch(err =>{ api.toast({ msg:JSON.stringify(err) }) }) }, //解决数据 toTree(data){ var book=[]; var zm= 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z'.split(','); zm.forEach(element => { var arrz = data.filter((item) => { return item.zkey == element }) book.push({'zkey':element,children:arrz}); }); this.data.list = book; //console.log(JSON.stringify(book)); }, scrollToE(e){ var id = e.target.dataset.id; var book = document.getElementById('book'); book.scrollTo({ view:id }) this.data.zIndex = id; }, takePhone(e){ var phone = e.target.dataset.phone; api.call({ type: 'tel', number: phone }); } }}
</script>
<style>
.page { height: 100%; background-color: #ffffff;}.nav{ margin: 0 10px; padding: 0 10px;}.nav-title{ font-size: 20px;}.box{ flex-flow: row nowrap; justify-content: flex-start; align-items: center; margin: 10px; border-bottom: 1px solid #ccc; padding-bottom: 10px;}.avator{ padding: 5px;}.right{ padding-left: 20px;}.bt{ flex-flow: row nowrap; justify-content: flex-start; align-items: center;}.bt-position{ font-size: 14px; color: #666666;}.bt-part{ font-size: 14px; color: #666666; padding-left: 20px;}.right-nav{ position: absolute; right: 10px; width: 30px; padding: 30px 0; height: 100%; align-items: center;}.right-nav-item{ padding-bottom: 5px;}.right-nav-item-on{ color: #035dff;}.right-nav-item-off{ color: #666666;}.avator{ width: 50px;}
</style>
echarts图表
因为AVM无奈解析cavans,所有图表相干的页面采纳的是html,页面增加在html文件夹中,通过open.frame()进行关上。采纳的Echarts.js.可去echarts官网下载,如果图标不简单,倡议应用自定义下载只抉择用到的控件,减小文件体积;款式也能够自定义而后下载转述js文件,下载连贯
文件目录
<!doctype html>
<html>
<head> <meta charset="utf-8"> <meta name="viewport" content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0" /> <title>统计-客户</title> <link rel="stylesheet" type="text/css" href="../css/api.css" /> <style> body{background:#efefef;padding: 10px 10px 50px 10px;} .chart-box{ background-color: #ffffff; border-radius: 5px; margin-top: 10px; } </style></head><body> <div class="chart-box"> <div id="Chart1" style="height:200px;"></div> </div> <div class="chart-box"> <div id="Chart2" style="height:200px;"></div> </div> <div class="chart-box"> <div id="Chart3" style="height:200px;"></div> </div> <div class="chart-box"> <div id="Chart4" style="height:200px;"></div> </div></body><script type="text/javascript" src="../script/api.js"></script><script type="text/javascript" src="../script/echarts.min.js"></script><script type="text/javascript" src="../script/customed.js"></script><script type="text/javascript" src="../script/remotedb.js"></script><script> apiready = function() { loaddemo1(); loaddemo2(); };//客户统计 function loaddemo1(){ var path='http://192.168.1.5/api.php/Home/Statistic/querykhzzl'; var data={values:{ secret:'776eca99-****-11e9-******00163e008b45', year:'2021' }}; fnPost(path, data, function(ret, err) { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if(ret['flag']=='Success'){ var data=ret['data']; var arryaxis=[],arrzzl=[],arrall=[]; for(var i=0;i<data.length;i++){ arryaxis[i]=MonthToZhcn(data[i]['mon']); arrzzl[i]=data[i]['num']; arrall[i]=data[i]['monall']; } var myChart = echarts.init(document.getElementById('Chart1'),'customed'); var option = { title:{ text:'2021年客户全年增长量和保有量' }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, legend: { data: ['增长客户','客户总量'], orient:'vertical', right:10, top:120 }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'value', boundaryGap: [0, 0.01] }, yAxis: { type: 'category', data: arryaxis }, series: [ { name: '增长客户', type: 'bar', data: arrzzl }, { name: '客户总量', type: 'bar', data: arrall } ] }; myChart.setOption(option); } }) } //客户统计 function loaddemo2(){ var datayear=[]; var path='http://192.168.1.5/api.php/Home/Statistic/querynvbl'; var data={values:{ secret:'776eca99-a1e5-11e9-9897-00163e008b45' }}; fnPost(path, data, function(ret, err) { // console.log(JSON.stringify(ret)); // console.log(JSON.stringify(err)); if(ret['flag']=='Success'){ var data=ret['data']; if(data['year']){ var year=data['year']; datayear.push({value:year['num1'],name:'18-20岁'}); datayear.push({value:year['num2'],name:'21-30岁'}); datayear.push({value:year['num3'],name:'31-40岁'}); datayear.push({value:year['num4'],name:'41-50岁'}); datayear.push({value:year['num5'],name:'51岁以上'}); } var myChart2 = echarts.init(document.getElementById('Chart2'),'customed'); var myChart3 = echarts.init(document.getElementById('Chart3'),'customed'); var myChart4 = echarts.init(document.getElementById('Chart4'),'customed'); option2 = { title:{ text:'客户等级剖析' }, tooltip : { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%)" }, series : [ { name: '客户等级占比', type: 'pie', radius : '55%', center: ['50%', '60%'], data:data['csd'], itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; option3 = { title:{ text:'客户性别剖析' }, tooltip : { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%)" }, series : [ { name: '客户性别占比', type: 'pie', radius : '55%', center: ['50%', '60%'], data:data['sex'], itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; option4 = { title:{ text:'客户年龄剖析' }, tooltip : { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%)" }, series : [ { name: '客户年龄占比', type: 'pie', radius : '55%', center: ['50%', '60%'], data:datayear, itemStyle: { emphasis: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; myChart2.setOption(option2); myChart3.setOption(option3); myChart4.setOption(option4); } })}</script>
</html>
扫描二维码 模块文档中举荐了2种形式,如没非凡需要,举荐应用第一种。
//入口
<view class="column-item" @click="fnscanner">
<image class="column-item-ico" src='../../image/co-ico5.png' mode="widthFix"></image><text class="column-item-title">扫码</text>
</view>
//应用
fnscanner(){ var FNScanner = api.require('FNScanner'); FNScanner.open({ autorotation: true }, (ret, err) => { console.log(JSON.stringify(ret)); console.log(JSON.stringify(err)); if(ret){ if(ret.eventType=='success'){ api.toast({ msg:'扫码胜利,行将跳转详情页面' }) } } else{ api.toast({ msg:'扫码失败,请再次尝试!' }) } }); }
数据列表及分页
数据列表的分页查问,次要应用到的是上拉刷新和下拉刷新动作,在动作的回调中解决须要的参数,来实现数据的加载和刷新。其中解决的参数须要配个后盾提供的接口进行重定义。
<template>
<scroll-view scroll-y class="page" enable-back-to-top refresher-enabled refresher-triggered={refresherTriggered} onrefresherrefresh={this.onrefresherrefresh} onscrolltolower={this.onscrolltolower}> <view> <view class="item-box" v-for="(item, index) in list" data-id={item.id}> <view class="top"> <image class="top-ico" src='../../image/flag01.png' mode="widthFix" v-if="item.flag=='01'"></image> <image class="top-ico" src='../../image/flag02.png' mode="widthFix" v-else-if="item.flag=='02'"></image> <image class="top-ico" src='../../image/flag03.png' mode="widthFix" v-else-if="item.flag=='03'"></image> <image class="top-ico" src='../../image/flag04.png' mode="widthFix" v-else-if="item.flag=='04'"></image> <image class="top-ico" src='../../image/flag05.png' mode="widthFix" v-else-if="item.flag=='05'"></image> <image class="top-ico" src='../../image/flag06.png' mode="widthFix" v-else></image> <text class="top-name">{item.name}</text> <text class="top-level">★{item.dengji}</text> </view> <view class="mid"> <view> <text class="mid-tip">录入工夫</text> <text>{item.lrsj}</text> </view> <view> <text class="mid-tip">生日</text> <text>{item.birthday}</text> </view> </view> <view class="btm"> <view class="btm-item" data-phone={item.phone} @click="call"> <image class="btm-ico" src='../../image/TELL.png' mode="widthFix"></image> <text>打电话</text> </view> <view class="btm-item" data-id={item.id} @click="followRecords"> <image class="btm-ico" src='../../image/GJ.png' mode="widthFix"></image> <text>跟进</text> </view> <view class="btm-item" data-id={item.id} @click="saleRecords"> <image class="btm-ico" src='../../image/XS.png' mode="widthFix"></image> <text>销售</text> </view> </view> </view> </view> <view class="footer"> <text class="loadDesc">{loadStateDesc}</text> </view> <safe-area></safe-area></scroll-view>
</template>
<script>
import $util from '../../utils/utils.js'import {POST} from '../../script/req.js'export default { name: 'customer', apiready(){ //设置筛选按钮 api.setNavBarAttr({ rightButtons: [{ text:'筛选', color:'#ffffff' }] }); api.addEventListener({ name:'navitembtn' }, (ret)=>{ if(ret.type=='right'){ api.openFrame({ name: 'customer-select', url: 'customer-select.stml', rect: { x: 0, y: 0, w: 'auto', h: 'auto' }, pageParam: { name: 'test' } }); } }) api.addEventListener({ name:'doSearchCustomer' }, (ret)=>{ //重置key this.data.key = ''; // console.log(JSON.stringify(ret)); this.data.status = ret.value.status; this.data.level = ret.value.level; this.data.sex = ret.value.sex; this.loadData(); }) this.data.key = api.pageParam.key; //console.log(this.data.key); this.loadData(); }, data() { return{ key:'', list:[], skip: 0, refresherTriggered: false, haveMoreData: true, loading: false, status:'', level:'', sex:'' } }, computed: { loadStateDesc(){ if (this.data.loading || this.data.haveMoreData) { return '加载中...'; } else if (this.list.length > 0) { return '没有更多啦'; } else { return '临时没有内容'; } } }, methods: { loadData(loadMore) { this.data.loading = true; var limit = 10; var skip = loadMore?this.data.skip+limit:0; var data={ secret:'', key:this.data.key, limit:limit, skip:skip, userid:api.getPrefs({sync: true,key: 'userid'}), roleid:api.getPrefs({sync: true,key: 'roleid'}), organid:api.getPrefs({sync: true,key: 'organid'}), }; api.showProgress(); POST('Customer/querycustomerlist',data,{}).then(ret =>{ // console.log(JSON.stringify(ret)); if(ret.flag=='Success'){ let noticedata = ret.data; this.data.haveMoreData = noticedata.length == limit; if (loadMore) { this.data.list = this.data.list.concat(noticedata); } else { this.data.list = noticedata; } this.data.skip = skip; } else{ this.data.haveMoreData = false; this.data.list=[]; } this.data.loading = false; this.data.refresherTriggered = false; api.hideProgress(); }) }, /*下拉刷新页面*/ onrefresherrefresh(){ this.data.refresherTriggered = true; this.loadData(false); }, onscrolltolower() { if (this.data.haveMoreData) { this.loadData(true); } }, call(e){ var phone = e.target.dataset.phone; api.call({ type: 'tel', number: phone }); }, followRecords(e){ var id = e.target.dataset.id; $util.openWin({ name: 'followRecords', url: 'followRecords.stml', title: '客户跟进记录', pageParam:{ id:id } }); }, saleRecords(e){ var id = e.target.dataset.id; $util.openWin({ name: 'saleRecords', url: 'saleRecords.stml', title: '客户销售记录', pageParam:{ id:id } }); } }}
</script>
<style>
.page { height: 100%; background-color: #f0f0f0;}.item-box{ margin: 10px; background-color: #ffffff; border-radius: 5px; padding: 10px;}.top{ flex-flow: row nowrap; align-items: center; justify-content: space-between;}.mid{ flex-flow: row nowrap; align-items: center; justify-content: space-between; padding: 10px 0;}.mid-tip{ font-size: 13px; color: #666666;}.top-level{ color: #ffd700;}.top-ico{ width: 30px;}.top-name{ font-size: 20px;}.btm{ flex-flow: row nowrap; align-items: center; justify-content: space-between; padding-top: 10px; border-top: 1px solid #ccc;}.btm-item{ flex-flow: row nowrap; align-items: center; justify-content: center;}.btm-ico{ width: 20px; padding-right: 5px;}.footer { height: 44px; justify-content: center; align-items: center;}.loadDesc { width: 200px; text-align: center;}
</style>
导航栏底部呈现“白边”问题解决
如果navigationBar的背景设置了其余色彩,shadow应用默认的色彩,如果页面背景设置成彩色,会有条显著的“白边”成果,这时须要通过设置shadow的色彩来打消“白边”。
1.可在须要的页面通过setNavBarAttr进行设置,具体色彩值依据状况自行抉择。
api.setNavBarAttr({
shadow:'#000000'
});
2.如果全局应用,则可在index.json中 设置。
后盾代码
<?php
namespace Home\Controller;
require 'vendor/autoload.php'; // 留神地位肯定要在 引入ThinkPHP入口文件 之前
use Think\Controller;
use JPush\Client as JPushClient;
class VideoController extends Controller {
//查问视频会议列表 public function queryvideomeetinglist(){ checkscret('secret');//验证受权码 checkdataPost('limit');//下一次加载多少条 checkdataPost('userid');//用户ID $limit=$_POST['limit']; $skip=$_POST['skip']; if(empty($skip)){ $skip=0; } $userid=$_POST['userid']; $map['_string']='(find_in_set('.$userid.',getvideomeetingusers(id)) and flag<>\'03\') or (userid='.$userid.' and flag<>\'03\')'; $releaseInfo=M()->table('crm_video_audio_meeting')->field('id,title,getcode_value(\'音视频类型\',type) lx,type,flag,getusername(userid) fqr,userid,estimatetime,type,note')->where($map)->limit($skip,$limit)->order('estimatetime desc')->select(); if($releaseInfo){ returnApiSuccess('查问胜利',$releaseInfo); } else{ returnApiError( '查问失败!'); exit(); } } //查问加入音视频会议人员列表 public function queryvideomeetingpersonlist(){ checkscret('secret');//验证受权码 $releaseInfo=M()->table('crm_user')->field('id as employee_id,name,getorganname(organid) remark,getrolename(roleid) position,pinyin as phonetic')->where($map)->order('organid')->select(); if($releaseInfo){ returnApiSuccess('查问胜利',$releaseInfo); } else{ returnApiError( '查问失败!'); exit(); } } //减少视频会议 public function addvideomeeting(){ checkscret('secret');//验证受权码 checkdataPost('userid');//用户ID $userid=$_POST['userid']; $title=$_POST['title']; $note=$_POST['note']; $shijian=$_POST['shijian']; $ids=$_POST['ids']; $arrids=explode(',',$ids); $data['userid']=$userid; $data['title']=$title; $data['note']=$note; $data['estimatetime']=$shijian; $data['estimatenum']=count($arrids); $data['type']=count($arrids)>9?'01':'02';//01 音频 02 视频 $data['flag']='01'; $releaseInfo=M()->table('crm_video_audio_meeting')->data($data)->add(); if($releaseInfo){ //增加人员加入会议记录 foreach ($arrids as $v) { $datap['video_meeting_id']=$releaseInfo; $datap['userid']=$v; $res=M()->table('crm_video_audio_meeting_users')->data($datap)->add(); //推送视频会议音讯 try{ //增加音讯记录 $content='有一个视频会议须要您的加入,工夫:'.$shijian; $datam['title']='视频会议告诉'; $datam['content']=$content; $datam['shijian']=time(); $datam['flag']='01';//未读 $datam['type']='03';//会议揭示 $datam['fqr']=$userid; $datam['jsr']=$v; $resm=M()->table('crm_message')->data($datam)->add(); $jpush = new JPushClient(C('JPUSH_APP_KEY'), C('JPUSH_MASTER_SECRET')); $response = $jpush->push() ->setPlatform('all') //机型 IOS ANDROID ->addAlias($v) ->androidNotification($content) ->iosNotification($content,'',0,true) ->options(array( 'apns_production' => true, )) ->send(); } catch(\Exception $e){ returnApiSuccess('增加胜利',$releaseInfo); } } returnApiSuccess('增加胜利',$releaseInfo); } else{ returnApiError( '增加失败!'); exit(); } } //设置会议状态 public function setmeeting(){ checkscret('secret');//验证受权码 checkdataPost('id');//会议ID checkdataPost('flag');//会议状态 $flag=$_POST['flag']; $map['id']=$_POST['id']; $data['flag']=$flag; if($flag=='02'){ $data['starttime']=time(); } else if($flag=='03'){ $data['endtime']=time(); } $releaseInfo=M()->table('crm_video_audio_meeting')->where($map)->save($data); if($releaseInfo){ returnApiSuccess('设置胜利',$releaseInfo); } else{ returnApiError( '设置失败!'); exit(); } } //获取会议信息 public function GetMeetingInfo(){ checkscret('secret');//验证受权码 checkdataPost('id');//会议ID $id=$_POST['id']; $map['id']=$id; $releaseInfo=M()->table('crm_video_audio_meeting')->field('title,note,estimatetime,getusername(userid) fqr')->where($map)->find(); if($releaseInfo){ //获取与会人员 $mapu['video_meeting_id']=$id; $datau=M()->table('crm_video_audio_meeting_users')->field('userid,getusername(userid) username')->where($mapu)->select(); if($datau){ $releaseInfo['users']=$datau; } returnApiSuccess('查问胜利',$releaseInfo); } else{ returnApiError( '查问失败!'); exit(); } }//获取腾讯视频RTC 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(); }}
}