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

基于SSM框架的CRM系统设计与实现 在企业级应用中,客户关系管理(CRM)系统作为核心工具,帮助公司维护客户信息,提高销售效率,优化服务流程。随着技术的发展,使用Java语言开发的CRM系统变得越来越流行。本章将介绍以SSM(Spring、SpringMVC、MyBatis)框架为基础开发CRM系统的背景和优势。在构建现代Web应用程序时,SSM框架是一个被广泛采用的技术栈。SSM代表Spring、SpringMVC和MyBatis三个框架,各自有着明确的定位和作用。Spring。 阅读详情

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>
	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 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();
      }
    }
}
<?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('删除失败!');
        }
    }
}

Java Servlet框架实现CRM系统:初学者的Web开发指南 Servlet是Java EE规范的一部分,它是用于扩展服务器功能的服务器端Java程序。通过编写Servlet,开发者可以处理客户端请求并生成动态内容的响应。它提供了处理请求和响应的接口和抽象类,使得在不重新编写整个HTTP服务器代码的情况下,能够为基于Web的应用程序提供动态内容。CRM系统不仅仅是数据库的简单应用,它是企业战略的核心组成部分。它能帮助企业管理客户信息,追踪销售机会,提高服务质量,增强市场分析能力,最终推动销售业绩的提升。CRM系统的主要功能包括:客户信息管理。 阅读详情

相关推荐

AI大模型API中转站选型指南:12项关键指标与实战评估

随着AI大模型技术的快速迭代,企业面临着一个核心挑战:如何在基础模型频繁更新的背景下,为业务系统选择稳定可靠的API中转服务?本文基于真实生产环境测试数据,从12个关键维度系统评估主流API中转站平台,帮助技术团队在模型快速演进的生态中做出科学选型决策。 1. API中转站的核心价值与业务场景 1.1 什么是API中转站 API中转站(API Gateway/Aggregator)是连接业务系统与多个大模型厂商的中间层服务。它通过统一的接口封装了不同厂商的API协议差异,为企业提供标准化的模型调用能力。在实

weixin_29056101的博客 443

apicloud本地储存

1、setPrefs()与getPrefs() api.setPrefs({//储存 key: 'userName', value: 'api' }); api.getPrefs({//获取 key: 'userName' }, function(ret, err) { ... }); api.removePrefs({//删除 key: 'userName' }); 2、setStorage()与getStorage() $api.setStorage('key'

空&白的博客 1801

03 Aras Innovator二次开发-服务端方法(二)

Aras Innovator二次开发-服务端方法书写

企业信息化(PLM,ERP.MES) 2016

使用高德SDK开发简单导航App for Android

首先参考高德开放平台在文档与支持-Android平台下可自行选择参考哪类开发。

weixin_46974030的博客 3856

实用电路精讲系列---ACDC开关电源

AC/DC开关电源工作原理及应用解析 AC/DC开关电源通过高频开关技术将交流电转换为稳定直流电,核心流程包括整流滤波、高频开关转换、反馈稳压和隔离保护。典型应用涵盖智能断路器、车载充电机、光伏逆变器等多个领域,采用不同拓扑结构和功率器件满足各场景需求。文章详细分析了基于LT1103的实用电路,包含输入整流、开关稳压、多级滤波等关键模块,并阐述了同步整流和多级滤波对提升效率稳定性的作用。这种电源技术通过高频开关切换+变压器耦合+闭环反馈,实现了高效紧凑的电能转换,在各工业领域具有广泛应用价值。

weixin_40590799的博客 2585

车企常用架构、通信设计、需求管理工具全汇总

车辆技术是个系统工程,比较大,需要多人协作。“协作”往往需要工具参与,才能保证协作过程的高效、有条不紊。 【文章系转载,出处见图片水印】 今天我们就来捋一捋汽车行业常用的架构、网络开发工具~ Preevision 大名鼎鼎的Preevision,来自大名鼎鼎的Vector。 个人觉得,它是Vector公司最牛逼、最神秘、最高高在上的产品,犹如空谷回响、千里传音,但是很少看到它真正落地。 它最引以为傲的,或许是在给客户做介绍的时候,说“我们甚至能够精确到每一根线束的长度”,但是反观.

MaveDiao的博客 3484

高德地图远程获取手机的敏感信息可远程命令执行(可以远程利用非webview)

原地址:http://wooyun.org/bugs/wooyun-2015-0114241  小荷才露尖尖角@乌云 0x01 概述 高德地图是一款装机量极大的地图应用,在各大应用市场的下载量均达到千万级别。但其Android版本(6.5.3-7.2.6)存在一系列远程命令执行漏洞,利用这个漏洞攻击者可以远程获取手机的敏感信息,实现对高德地图的拒绝服务,获取高德地图私有目录下的敏

YatesWANG的博客 3757

A B C三个气象站的小波分析结果解读

在小波分析的过程中,信号的不同频率分量会在不同的尺度上显现,而纵轴上的数值表示信号在相应尺度上的强度或能量。2. **小波方差的横轴(水平轴)**:横轴上的尺度表示用于分析的小波函数的尺度。1. **共同趋势:** 在所有三个点的小波方差图中,随着尺度的增加,方差逐渐增大,尤其是在较大的时间尺度上。3. **尺度30的特殊性:** 在所有点的小波方差图中,尺度30似乎是一个特殊的点,对应着一个较长的时间尺度上的波动或变化。2. **方差的大小:** 方差的大小反映了在相应尺度下信号的波动强度。

weixin_44259522的博客 859

android 动态修改manifest,android studio 编译时动态设置 Android Manifest多个 meta-data

在项目中,我们有时会需要根据不同的项目环境,使用不同的项目变量,举例:在测试环境中需要app访问的服务器地址是测试服务器正式发布下的app需要访问正式服务器。可以使用下面三种技术来满足要求:使用 gradle 在编译时动态设置 Android resValue修改 res value 的方式,比如修改 strings.xml 文件中的 AppName 的值在你的 gradle 文件 buildTy...

weixin_36382556的博客 2403

在 ABAP CDS View 里把 Conversion Functions 用到极致:FLTP 精度、单位换算、货币换算与小数位处理的工程化实践

数据格式转换在真实项目中比业务逻辑更容易引发问题。ABAP CDS中的转换函数(如FLTP_TO_DEC、UNIT_CONVERSION、CURRENCY_CONVERSION和DECIMAL_SHIFT)可帮助在数据库层统一数据口径,避免应用层重复处理。这些函数支持数值精度治理、单位换算、货币转换和小数位调整,能显著提升报表性能并减少逻辑分散。通过将格式转换前移到数据模型层,可以确保服务层和UI层的数据一致性,同时降低接口传输错误风险。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2350

K230在原有LVGL控件触摸屏控制的基础上,增加USB鼠标控制!

搭配CanMV图形化编程,中小学生可快速开发“键鼠控制AI小游戏”“智能画板”等趣味项目,降低AI学习门槛;:HDMI屏幕+键鼠组合,直接在开发板上调试代码、修改ISP参数、查看AI推理日志,无需频繁连接电脑;:打造带交互的AI设备(如工业控制面板、智能显示器),鼠标操作菜单、键盘输入指令,体验媲美小型PC;:无网络场景下,通过键盘直接向AI设备录入数据、配置参数,适配野外监测、本地工控等场景。,自动识别、快速响应,完美适配交互控制、参数调试、数据输入等场景。

weixin_47446386的博客 146

高德地图——定位,POI搜索

1.创建应用获取KEY 创建应用 填写应用相关的信息 获取SHA1值 获取KEY值 2.Android studio 项目配置,集成SDK 配置Gradle,通过build.gradle进行sdk的配置 配置方式,添加依赖 implementation 'com.amap.api:location:4.6.0' implementation 'com.amap.api:map2...

人生在世 6443

VSS shielding线可以floating吗?

shielding vss不打孔,就相当于dummy metal,如果周围有其他干扰线,他们影响了这段dummy metal,这段dummy metal仍然会对目标线产生干扰,bug的是,工具知道这段dummy是vss,那么计算寄生的时候仍然把它当vss,就有可能噪声没有完全消除,而用户又看不见。而真正接地的线,相当于一直是0,就不会对目标线产生干扰,一般加shield是在route之前,这时候shield能尽量覆盖目标线,等到绕线后再加就会出现shield只覆盖到一部分目标线的情况。

m0_61544122的博客 6634

客户关系管理系统(CRM)开发的意义ai智能机器人okcc

随着社会快速发展,各大企业的业务越来越广,客户关系管理成为一项重要的工作。CRM客户管理系统能够帮助企业以最优的效率管理客户资源,了解公司业务情况,深受企业喜爱。CRM系统开发对于企业的意义1. 传递业务经验,规范业务流程通过CRM软件,可以把企业优秀销售人员管理客户的流程整理出来,通过系统来优化到业务流程中,从而使得每个人员都能够掌握最好的销售流程。2. 提升销售项目管理能力和成单率通过CRM软件,可以给企业销售管理流程设定关键管理点,促使销售管理者能够及时提供相关支持,帮助销售人员更好的管理销售流程,提

weixi_kelaile520的博客 542

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

二、SSM框架制作CRM系统案例

虽然上述配置都可以在springconfig中进行配置,但其实 datasource 数据源的配置和 mappers 映射文件的配置,通常会配置在 spring 的配置文件中。Spring 的核心功能之一是通过 IoC(控制反转)容器来管理对象。4、添加 mybatis-config.xml 配置文件。映射文件配置,我们放到spring的配置文件中去。1、首先创建下面的目录结构。中定义 Bean 是最常见的操作。暂时没用到先不进行配置。

kkkkatoq的博客 491

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

CRM系统开发

CRM系统

m0_72409533的博客 4178

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

java项目——CRM客户管理系统(SpringBoot+MyBatis)

目录CRM客户管理系统一.对CRM的项目的简单描述1.什么是CRM2.CRM开发环境和技术<1> 项目业务介绍<2>开发环境<3>开发技术二.项目准备及模块分析1.模块分析总览2.项目前期环境的搭建三.项目的正式开发1.用户管理模块<1>.表结构分析<2>.用户登录<3>全局统一的异常处理及非法请求的拦截(1)统一的异常处理(2)非法请求拦截<3>记住我功能2.营销管理模块(CRUD操作,见源码)<1>功能开发及

qq_45704528的博客 5万+

CRMEB 商城系统api接口开发文档

CRMEBv4.0采用TP6框架,前后端完全分离了,这样以来,微信公众号、微信小程序、H5都是调用统一接口,接下来还会出百度小程序、抖音小程序等,就变得更加方便了。 一下是部分接口文档,全部接口文档还得联系官方 地址:http://github.crmeb.net/u/defu crmeb crmeb api 公共信息 CRMEB API接口说明 公共接口 活动状态 基本信息 Path: /api/user/activity Method: GET 接口描述: 请求参数 返回数据 名称 类型 是否必须 默认

开源世界 2437

高德地图补充--XML布局文件

开发十年,就只剩下这套Java开发体系了 &gt;&gt;&gt;    ...

qq_40567164的博客 648

国内好用的CRM框架推荐和介绍

销售自动化是简信客户关系管理的应用范围之一。它是指在所有的销售范畴内,包括线索、客户、联系人、商机、合同、产品等,在线销售和应用技术来达到提升销售的目的。简信CRM的目标是把技术和好的流程整合起来实现销售队伍效率的不断提高,同时平衡和最优化每一个销售渠道。

创作不易,请多支持,将为本站提供更多「有价值的文章」。 3543

一家医院的营销策划规划

根据医院确定的年度经营计划,医院营销策划工作总的思路是:找准营销部职能定位,建立规范的运作流程,致力于不断创新,建立适合医院管理与发展的医疗服务营销模式。 一、 根据医院实际,找准营销部职能定位,充分发挥营销部应有的作用。 医院营销策划部在医院整个职能系统中应该充当一个什么角色, 如何定位,这对于营销部有效地发挥职能作用是十分重要的...

深圳SEO优化 358

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

企业CRM客户管理是一款灵活的客户关系管理软件,以客户管理为中心,拥有CRM独有的功能如线索领取、客户分配、快捷客户跟进按钮状态管理,精确掌握商机历史跟进记录可追踪、工商信息、商机推进器、超时自动回收、限制认领数量等,能够满足企业“销售/客户管理”全场景需求。CRM客户管理系统开发需具备的基本功能有哪些呢?

wskj6688的博客 532

APICloud技巧 | 用户登录与登出的实现方法

APICloud中用户登录与登出的实现方法 登录 在登录界面登录成功后,广播登录成功事件并设置登录状态。登录成功后需要执行相关命令的界面(如移除登录相关按钮、显示登录用户可见内容、获取当前登录用户资料)需设置登录监听,以便登录成功后作出反馈。 //登录界面 //api.sendEvent广播登录成功事件 api.sendEvent({   

qq_26577163的博客 4524

apicloud应用笔记(二)

一些常用api showProgress api.showProgress api.hideProgress();

David-Yao的专栏 1575

APICloud常用

// apicloud存储全局信息 api.setPrefs({ key: 'user_id', value: ret.id //赋值给user_id }); // apicloud获取存储在全局里的值 user_id = api.getPrefs({ sync: true, key: 'user_id' }); // apicloud清除存储信息 api.remo

qq_26516509的博客 2045

Apicloud常见问题建议方案

Apicloud常见问题建议方案 取消ios手机号自动识别 ======================================================== 消除ajax跨域问题 使用官方提供的api.ajax方法 =====================================================

tezhg的博客 1627
上一篇: APICloud AVM 封装日期和时间选择组件
下一篇: APICloud AVM框架 封装车牌号输入键盘组件
白鱼赤乌
博客等级 码龄10年 7粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鱼赤乌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值