提交 19a466a1 编写于 作者: huangwensu's avatar huangwensu

创建风控系统

上级 3d2a4329
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>数据服务</title> <title>风控系统</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name ="viewport" content ="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> <meta name ="viewport" content ="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta content="" name="description"/> <meta content="" name="description"/>
......
<template> <template>
<div class="header"> <div class="header">
<div class="logo">数据服务</div> <div class="logo">风控系统</div>
<div class="user-info"> <div class="user-info">
<el-dropdown trigger="click" @command="handleCommand"> <el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
......
...@@ -18,37 +18,18 @@ export default new Router({ ...@@ -18,37 +18,18 @@ export default new Router({
children: [ children: [
{ {
path: '/', path: '/',
redirect: '/data-view', redirect: '/doctor-black',
},{ // 数据导入 },{
path: '/data-in', path: '/doctor-black',
component: resolve => require(['../views/sensitive-control/data-in.vue'], resolve), component: resolve => require(['../views/black-list/doctor-black.vue'], resolve),
}, { // 数据统计 },{
path: '/data-statistic', path: '/empty-phone',
component: resolve => require(['../views/sensitive-control/data-statistic.vue'], resolve), component: resolve => require(['../views/black-list/empty-phone.vue'], resolve),
},{ // 数据查看
path: '/data-view',
component: resolve => require(['../views/sensitive-control/data-view.vue'], resolve)
},
{ // 用户路径分析
path: '/user-path',
component: resolve => require(['../views/user-path/funnel.vue'], resolve)
},
{ // 用户路径分析--新增转化漏斗
path: '/add-funnel',
component: resolve => require(['../views/user-path/add-funnel.vue'], resolve)
},
{ // 用户路径分析--查看数据
path: '/path-data',
component: resolve => require(['../views/user-path/funnel-data.vue'], resolve)
}, },
] ]
}, },
{ // 短信中的 数据查看页面
path: '/data-view-message',
component: resolve => require(['../views/sensitive-control/data-view-message.vue'], resolve)
},
] ]
......
<template>
<div class="black-container">
<el-row :gutter="30" class="row search" type="flex" style="margin-bottom:0;">
<el-form ref="form" :model="searchParam" label-suffix=":" style="width:100%;">
<el-col :span="18">
<el-form-item label="名称">
<el-input v-model="searchParam.searchName" maxlength="20" placeholder="请输入黑名单中的名称" style="width:288px;"></el-input>
</el-form-item>
</el-col>
<el-col :span="6" style="padding:0;text-align:right;padding-right:15px;">
<el-button type="primary" size="small" @click="search(1)">查询</el-button>
<el-button type="primary" size="small" @click="importBatch">批量导入</el-button>
<el-button type="primary" size="small" @click="addBlackList">新增黑名单</el-button>
</el-col>
</el-form>
</el-row>
<el-table
class="search-table"
:data="tableData"
style="width: 100%">
<el-table-column prop="searchName" label="名称" min-width="140" align="center"></el-table-column>
<el-table-column prop="time" label="添加时间" min-width="140" align="center"></el-table-column>
<el-table-column prop="remark" label="添加理由" min-width="150" align="center"></el-table-column>
<el-table-column label="操作" fixed="right" align="center" min-width="200">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="removeFromBlack(scope.row)">从黑名单移除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="searchParam.pageNo"
:page-sizes="[15, 30, 50, 100, 200, 500, 700, 1000, 1500, 2000]"
:page-size="searchParam.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalRows">
</el-pagination>
</div>
<!-- 将手机号加入黑名单 -->
<el-dialog
title="提示"
:visible.sync="addDialogVisible"
width="30%"
center>
<el-form ref="importForm" :model="addParam" label-suffix=":" label-width="80px">
<el-form-item label="手机号">
<el-input v-model="addParam.mobilePhone" style="width: 300px;"></el-input>
</el-form-item>
<el-form-item label="添加理由">
<el-input v-model="addParam.remark" style="width: 300px;"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmAdd">确 定</el-button>
</span>
</el-dialog>
<!-- 从黑名单移除二次确认 -->
<el-dialog
title="提示"
:visible.sync="removeDialogVisible"
width="30%"
center>
<span>您确定将{{removeTel}}移除黑名单?</span>
<span slot="footer" class="dialog-footer">
<el-button @click="removeDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmRemove">确 定</el-button>
</span>
</el-dialog>
<!-- 批量导入 -->
<el-dialog
title="提示"
:visible.sync="importDialogVisible"
width="30%"
center>
<el-form ref="importForm" :model="searchParam" label-suffix=":" style="width:100%;">
<el-form-item label="上传文件">
<el-upload
class="upload-demo"
accept=".xlsx"
action="#"
v-model="importFileName"
:before-upload="uploadImportFile">
<el-input class="upload-input" v-model="importFileName" autocomplete="off"></el-input>
</el-upload>
</el-form-item>
<div class="el-upload__tip" @click="download">下载导入模板</div>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="importDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmImport">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { doUpload, getFilePath } from "../../common/qiniuUtil";
import { openLoading, closeLoading } from "../../common/utils";
export default {
data() {
return {
addDialogVisible: false,
removeDialogVisible: false,
importDialogVisible: false,
searchParam: {
searchName: '',
pageSize: 15,
pageNo: 1
},
totalRows: 0,
tableData: [],
addParam: {
mobilePhone: '',
remark: ''
},
removeId: '',
removeTel: '',
acceptValue: '*/*',
importFileName: ''
}
},
mounted() {
this.search();
},
methods: {
// 封装一下请求通用的方法
getData(type, url, req, callback) {
openLoading(this);
this.$axios[type](localStorage.getItem("lectureUrl") + url, req)
.then(res => {
closeLoading(this);
let data = res.data;
if (data.code == "000000") {
if (callback) callback(data);
} else {
this.$message.error(data.message);
}
})
.catch(error => {
closeLoading(this);
this.$message.error("网络出现点问题");
});
},
search(param) {
if(param) this.searchParam.pageNo = 1;
this.getData(
"get", `/riskcontrol/blacklist/list?searchName=${this.searchParam.searchName}&pageNo=${this.searchParam.pageNo}&pageSize=${this.searchParam.pageSize}`, {},
data => {
this.tableData = data.data.list;
this.totalRows = data.data.totalRows;
}
);
},
// 批量导入
importBatch() {
this.importDialogVisible = true;
},
confirmImport() {
},
// 上传文件
uploadImportFile(file) {
console.log(file)
let self = this;
this.importFileName = file.name;
let arr = file.type.split('/');
let ext = "." + arr[1];
let reader = new FileReader();
reader.onload = function (e) {
let fileJson = {
fileName: file.name,
file: e.target.result.substr(e.target.result.indexOf("base64,") + 7),
ext: ext
};
self.base64 = fileJson;
console.log('fileJson',self.base64);
};
reader.readAsDataURL(file);
this.getData(
"post", `/riskcontrol/blacklist/import`, {base64Str: self.base64.file},
data => {
if(data.code == '000000') {
}
}
);
},
// 下载导入模板
download() {
console.log('下载模板');
setTimeout(() => {
window.open(''); // 后台给的导入Excel地址
}, 500);
},
// 新增黑名单
addBlackList() {
this.addDialogVisible = true;
},
confirmAdd() {
let req = this.addParam;
this.getData(
"post", `/riskcontrol/blacklist/create`, req,
data => {
if(data.code == '000000') {
this.addDialogVisible = false;
this.search();
}
}
);
},
// 从黑名单移除
removeFromBlack(row) {
this.removeId = row.id;
this.removeTel = row.mobilePhone;
this.removeDialogVisible = true;
},
confirmRemove() {
this.getData(
"delete", `/riskcontrol/blacklist/remove${this.removeId}`, {},
data => {
if(data.code == '000000') {
this.removeDialogVisible = false;
this.search();
}
}
);
},
handleSizeChange(value) {
this.searchParam.pageSize = value;
this.search();
},
handleCurrentChange(value) {
this.searchParam.pageNo = value;
this.search();
}
}
}
</script>
<style lang="less" scoped>
.black-container {
.upload-input {
width: 300px;
}
}
</style>
\ No newline at end of file
<template>
<div class="black-container">
<el-row :gutter="30" class="row search" type="flex" style="margin-bottom:0;">
<el-form ref="form" :model="searchParam" label-suffix=":" style="width:100%;">
<el-col :span="18">
<el-form-item label="名称">
<el-input v-model="searchParam.searchName" maxlength="20" placeholder="请输入黑名单中的名称" style="width:288px;"></el-input>
</el-form-item>
</el-col>
<el-col :span="6" style="padding:0;text-align:right;padding-right:15px;">
<el-button type="primary" size="small" @click="search(1)">查询</el-button>
<el-button type="primary" size="small" @click="importBatch">批量导入</el-button>
<el-button type="primary" size="small" @click="addBlackList">新增黑名单</el-button>
</el-col>
</el-form>
</el-row>
<el-table
class="search-table"
:data="tableData"
style="width: 100%">
<el-table-column prop="searchName" label="名称" min-width="140" align="center"></el-table-column>
<el-table-column prop="time" label="添加时间" min-width="140" align="center"></el-table-column>
<el-table-column prop="remark" label="添加理由" min-width="150" align="center"></el-table-column>
<el-table-column label="操作" fixed="right" align="center" min-width="200">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="removeFromBlack(scope.row)">从黑名单移除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="searchParam.pageNo"
:page-sizes="[15, 30, 50, 100, 200, 500, 700, 1000, 1500, 2000]"
:page-size="searchParam.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalRows">
</el-pagination>
</div>
<!-- 将手机号加入黑名单 -->
<el-dialog
title="提示"
:visible.sync="addDialogVisible"
width="30%"
center>
<el-form ref="importForm" :model="addParam" label-suffix=":" style="width:100%;">
<el-form-item label="手机号">
<el-input v-model="addParam.mobilePhone"></el-input>
</el-form-item>
<el-form-item label="添加理由">
<el-input v-model="addParam.remark"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmAdd">确 定</el-button>
</span>
</el-dialog>
<!-- 从黑名单移除二次确认 -->
<el-dialog
title="提示"
:visible.sync="removeDialogVisible"
width="30%"
center>
<span>您确定将{{removeTel}}移除黑名单?</span>
<span slot="footer" class="dialog-footer">
<el-button @click="removeDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmRemove">确 定</el-button>
</span>
</el-dialog>
<!-- 批量导入 -->
<el-dialog
title="提示"
:visible.sync="importDialogVisible"
width="30%"
center>
<el-form ref="importForm" :model="searchParam" label-suffix=":" style="width:100%;">
<el-form-item label="上传文件">
<el-upload
class="upload-demo"
accept=".xlsx"
action="#"
v-model="importFileName"
:before-upload="uploadImportFile">
<el-input class="update-input" v-model="importFileName" autocomplete="off"></el-input>
</el-upload>
</el-form-item>
<div class="el-upload__tip" @click="download">下载导入模板</div>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="importDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirmImport">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { doUpload, getFilePath } from "../../common/qiniuUtil";
import { openLoading, closeLoading } from "../../common/utils";
export default {
data() {
return {
addDialogVisible: false,
removeDialogVisible: false,
importDialogVisible: false,
searchParam: {
searchName: '',
pageSize: 15,
pageNo: 1
},
totalRows: 0,
tableData: [],
addParam: {
mobilePhone: '',
remark: ''
},
removeId: '',
removeTel: '',
acceptValue: '*/*',
importFileName: ''
}
},
mounted() {
this.search();
},
methods: {
// 封装一下请求通用的方法
getData(type, url, req, callback) {
openLoading(this);
this.$axios[type](localStorage.getItem("lectureUrl") + url, req)
.then(res => {
closeLoading(this);
let data = res.data;
if (data.code == "000000") {
if (callback) callback(data);
} else {
this.$message.error(data.message);
}
})
.catch(error => {
closeLoading(this);
this.$message.error("网络出现点问题");
});
},
search(param) {
if(param) this.searchParam.pageNo = 1;
this.getData(
"get", `/riskcontrol/blacklist/list?searchName=${this.searchParam.searchName}&pageNo=${this.searchParam.pageNo}&pageSize=${this.searchParam.pageSize}`, {},
data => {
this.tableData = data.data.list;
this.totalRows = data.data.totalRows;
}
);
},
// 批量导入
importBatch() {
this.importDialogVisible = true;
},
confirmImport() {
},
// 上传文件
uploadImportFile(file) {
console.log(file)
let self = this;
this.importFileName = file.name;
let arr = file.type.split('/');
let ext = "." + arr[1];
let reader = new FileReader();
reader.onload = function (e) {
let fileJson = {
fileName: file.name,
file: e.target.result.substr(e.target.result.indexOf("base64,") + 7),
ext: ext
};
self.base64 = fileJson;
console.log('fileJson',self.base64);
};
reader.readAsDataURL(file);
this.getData(
"post", `/riskcontrol/blacklist/import`, {base64Str: self.base64.file},
data => {
if(data.code == '000000') {
}
}
);
},
// 下载导入模板
download() {
console.log('下载模板');
setTimeout(() => {
window.open(''); // 后台给的导入Excel地址
}, 500);
},
// 新增黑名单
addBlackList() {
this.addDialogVisible = true;
},
confirmAdd() {
let req = this.addParam;
this.getData(
"post", `/riskcontrol/blacklist/create`, req,
data => {
if(data.code == '000000') {
this.addDialogVisible = false;
this.search();
}
}
);
},
// 从黑名单移除
removeFromBlack(row) {
this.removeId = row.id;
this.removeTel = row.mobilePhone;
this.removeDialogVisible = true;
},
confirmRemove() {
this.getData(
"delete", `/riskcontrol/blacklist/remove${this.removeId}`, {},
data => {
if(data.code == '000000') {
this.removeDialogVisible = false;
this.search();
}
}
);
},
handleSizeChange(value) {
this.searchParam.pageSize = value;
this.search();
},
handleCurrentChange(value) {
this.searchParam.pageNo = value;
this.search();
}
}
}
</script>
<style lang="less" scoped>
.black-container {
}
</style>
\ No newline at end of file
<template>
<div class="add-funnel-container">
<el-breadcrumb separator="/" class="add-title">
<el-breadcrumb-item :to="{ path: '/user-path' }">漏斗分析</el-breadcrumb-item>
<el-breadcrumb-item>新增转化漏斗</el-breadcrumb-item>
</el-breadcrumb>
<div class="add-content">
<div class="con">
<el-row :gutter="30" class="row save" type="flex" style="margin-bottom:0;">
<el-form ref="form" :model="searchParam" label-suffix=":" style="width:100%;">
<el-col :span="21">
<el-form-item label="漏斗名称">
<el-input
:class="{'red-b': disabledSave}"
v-model="searchParam.name"
maxlength="30"
placeholder="请输入漏斗名称"
style="width:430px;"
@blur="validValue"></el-input>
<span v-show="disabledSave" style="color: red; font-size: 12px;">{{nameTip}}</span>
</el-form-item>
</el-col>
<el-col :span="3" style="padding:0;text-align:right;padding-right:15px;">
<el-button type="primary" size="small" @click="saveAddAndEdit">保存</el-button>
</el-col>
</el-form>
</el-row>
<div class="add-step">
<p>请以用户逐步触发的事件为依据,构造漏斗、系统会自动计算整个过程的转化率。如果事件之间不连续,转化率为0!</p>
</div>
<div class="step-content" v-for="(item,index) in searchParam.actionModelList" :key="index">
<span class="st">步骤{{index + 1}}</span>
<el-form
ref="form"
class="step-form"
:model="item"
label-suffix=":"
style="width:100%;">
<el-col :span="11">
<el-form-item label="步骤名称">
<el-input
:class="{'red-b': item.nameCheck && item.actionName}"
v-model="item.actionName"
@input="nameChange(item, index)"
maxlength="30"
placeholder="请输入步骤名称"
style="width:70%"></el-input>
<p v-show="item.nameCheck && !item.actionName" style="color: red; font-size: 12px;">名称重复,请重新输入</p>
</el-form-item>
</el-col>
<el-col :span="11">
<el-form-item label="触发事件">
<el-input
v-show="!showEvent"
:class="{'red-b': item.unchecked}"
@focus="getEventData"
v-model="item.eventName"
style="width:75%;"
:title="item.eventName"
placeholder="请选择用户触发事件"></el-input>
<el-select
v-show="showEvent"
v-model="item.eventId"
filterable
placeholder="请选择用户触发事件"
:class="{'red-b': item.unchecked}"
@blur="selectValid(item, index)"
@change="eventChange(item, index)"
style="width:75%;"
>
<el-option
v-for="(eItem,eIndex) in eventSelect"
:key="eIndex"
:label="eItem.eventName"
:value="eItem.eventId"
>
</el-option>
</el-select>
<p v-show="item.unchecked" style="color: red; font-size: 12px;">请选择一个触发事件</p>
<p v-show="item.repChecked" style="color: red; font-size: 12px;">触发事件重复,请重新选择</p>
</el-form-item>
</el-col>
</el-form>
<i v-if="index == 0" class="el-icon-remove-outline disable-step"></i>
<i v-if="index > 0" class="el-icon-remove-outline delete-step" @click="deleteSteps(index)"></i>
<i class="el-icon-circle-plus-outline add-step" @click="addSteps"></i>
</div>
</div>
</div>
</div>
</template>
<script>
import { openLoading, closeLoading } from "../../common/utils";
export default {
data() {
return {
showEvent: false, // 编辑回显时不显示触发事件下拉数据
disabledSave: false,
funnelId: '',
searchParam: {
name: '',
actionModelList: [
{
actionName: '',
eventId: ''
}]
},
eventSelect: [],
tipText: '名称重复,请重新输入',
nameTip: '请输入漏斗名称',
repIndex: 0,
flag: false
}
},
created() {
this.funnelId = this.$route.query.id;
},
mounted() {
if(this.funnelId) { // 编辑
this.getFunnelDetail();
}else { // 新增
this.getEventList();
}
},
methods: {
// 封装一下请求通用的方法
getData(type, url, req, callback) {
openLoading(this);
this.$axios[type](localStorage.getItem("lectureUrl") + url, req)
.then(res => {
closeLoading(this);
let data = res.data;
if (data.code == "000000") {
if (callback) callback(data);
}else if(data.code == '227002' || data.code == '227003'){
this.disabledSave = true;
this.nameTip = '名称重复,请重新输入';
}
})
},
// 获取触发事件列表
getEventList(item) {
this.getData(
"get", `/session/funnel/eventList`, {},
res => {
this.eventSelect = res.data;
}
);
},
getEventData() {
this.showEvent = true;
this.getEventList();
},
// 编辑获取漏斗详情
getFunnelDetail() {
this.getData(
"get", `/session/funnel/detail/${this.funnelId}`, {},
res => {
if(res.code == '000000') {
this.searchParam = res.data;
}
}
);
},
// 漏斗名称是否有值判断
validValue() {
if(this.searchParam.name) {
this.disabledSave = false;
}
},
// 步骤名称不重复
nameChange(item, index) {
this.$set(this.searchParam.actionModelList[index], 'nameCheck', false);
if(!item.actionName) return;
for(let i = 0; i < this.searchParam.actionModelList.length; i++) {
if(i == index) continue;
if(item.actionName == this.searchParam.actionModelList[i].actionName) {
setTimeout(() => {
this.searchParam.actionModelList[index].actionName = '';
this.$set(this.searchParam.actionModelList[index], 'nameCheck', true);
},500)
}
}
},
// 触发事件选择时重复判断
eventChange(item, index) {
this.$set(this.searchParam.actionModelList[index], 'repChecked', false);
for(let i = 0; i < this.searchParam.actionModelList.length; i++) {
if(index != i) {
if(item.eventId == this.searchParam.actionModelList[i].eventId) {
this.$set(this.searchParam.actionModelList[index], 'repChecked', true);
this.searchParam.actionModelList[index].eventId = '';
}
}
}
},
// 触发事件是否为空样式
selectValid(item, index) {
this.$set(this.searchParam.actionModelList[index], 'unchecked', false);
// if(!item.eventId) return;
// for(let i = 0; i < this.searchParam.actionModelList.length; i++) {
// this.$set(this.searchParam.actionModelList[i], 'repChecked', false);
// if(index != i) {
// if(item.eventId == this.searchParam.actionModelList[i].eventId) {
// this.searchParam.actionModelList[index].eventId = '';
// this.$set(this.searchParam.actionModelList[index], 'repChecked', true);
// }
// }
// }
},
// 保存
saveAddAndEdit() {
if(!this.searchParam.name) {
this.disabledSave = true;
this.nameTip = '请输入漏斗名称';
return;
}
for(let i = 0; i < this.searchParam.actionModelList.length; i++) {
if(!this.searchParam.actionModelList[i].eventId) {
this.$set(this.searchParam.actionModelList[i], 'unchecked', true); // 事件名称为空
this.$set(this.searchParam.actionModelList[i], 'repChecked', false); //事件重复
return;
}
if((this.searchParam.actionModelList[i].nameCheck && this.searchParam.actionModelList[i].actionName) || this.searchParam.actionModelList[i].unchecked || this.searchParam.actionModelList[i].repChecked) { // 有步骤名称重复
return;
}
this.eventSelect.forEach(item => {
if(this.searchParam.actionModelList[i].eventId == item.eventId) {
this.$set(this.searchParam.actionModelList[i], 'eventName', item.eventName);
}
});
}
let req = this.searchParam;
for(let i = 0; i < req.actionModelList.length; i++) {
if(!req.actionModelList[i].actionName) {
req.actionModelList[i].actionName = req.actionModelList[i].eventName;
req.actionModelList[i].nameCheck = false;
}
}
this.getData(
"post", `/session/funnel/save`, req,
res => {
if(res.code == '000000') {
this.$message.success('转化漏斗保存成功');
this.$router.push({ path: 'path-data', query: {id: res.data.id, name: res.data.name} });
}
}
);
},
// 删除步骤
deleteSteps(i) {
this.$message.success('删除成功');
this.searchParam.actionModelList.splice(i,1);
},
// 增加步骤
addSteps() {
this.searchParam.actionModelList.push({
actionName: '',
eventId: ''
});
}
}
}
</script>
<style lang="less" scoped>
.add-funnel-container {
margin: -20px -40px;
.red-b {
border: 1px solid red;
}
.add-title {
height: 60px;
line-height: 60px;
padding-left: 40px;
}
.add-content {
background: #F2F2F4;
padding: 20px 0 20px 20px;
min-height: 83vh;
.con {
padding-bottom: 30px;
background: #fff;
min-height: 80vh;
.save {
padding: 20px 20px 0;
margin: 0 !important;
border-bottom: 1px solid #EDEDEE;
}
.add-step {
padding: 30px 40px 0;
font-size: 14px;
color: #4D4C4F;
}
.step-content {
overflow: hidden;
margin: 30px 40px 0px;
border: 1px solid #EDEDEE;
span.st {
float: left;
font-size: 14px;
color: #58ADE8;
padding: 5px;
margin: 20px 20px 0 20px;
border: 1px solid #58ADE8;
border-radius: 3px;
}
.step-form {
width: 80% !important;
padding: 15px 0 0 30px;
}
i {
padding: 10px 20px;
font-size: 20px;
}
.disable-step {
color: #D2D2D3;
}
.delete-step {
color: #58ADE8;
}
.add-step {
color: #58ADE8;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="data-container">
<el-breadcrumb separator="/" class="data-title">
<el-breadcrumb-item :to="{ path: '/user-path' }">漏斗分析</el-breadcrumb-item>
<el-breadcrumb-item>{{name}}详情</el-breadcrumb-item>
</el-breadcrumb>
<div class="data-content">
<div class="con">
<el-row :gutter="30" class="row search" type="flex" style="margin-bottom:0;">
<el-form ref="form" :model="searchParam" label-suffix=":" style="width:100%;">
<el-col :span="12">
<el-form-item label="时间" required>
<el-date-picker
type="date"
placeholder="开始时间"
v-model="searchParam.startDate"
style="width: 200px;"
:picker-options="endDateOpt"
value-format="yyyy-MM-dd"
>
</el-date-picker>
<span>-</span>
<el-date-picker
type="date"
placeholder="结束时间"
v-model="searchParam.endDate"
style="width: 200px;"
:picker-options="endDateOpt1"
value-format="yyyy-MM-dd"
>
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="版本">
<el-select v-model="searchParam.version" placeholder="请选择版本" style="width:200px">
<el-option
v-for="(eItem,eIndex) in versionSelect"
:key="eIndex"
:label="eItem"
:value="eItem">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4" style="padding:0;text-align:right;padding-right:15px;">
<el-button type="default" size="small" @click="reset">重置</el-button>
<el-button type="primary" size="small" @click="getAnalysisData(searchParam)">筛选</el-button>
</el-col>
</el-form>
</el-row>
<div class="data-num">
<div class="num-text first-num">
<span>总人数</span>
<p>{{funnelData.userCountTotal}}<span></span></p>
</div>
<div class="num-text">
<span>总转化率</span>
<p>{{funnelData.inversionRateTotal}}</p>
</div>
<div class="num-text">
<span>达成目标人数</span>
<p>{{funnelData.userCountLast}}<span></span></p>
</div>
<div class="num-text num">
<span>转化率最低步骤</span>
<p>{{funnelData.funnelNameLast}}</p>
</div>
</div>
<div class="data-analysis">
<div class="map-title">转化漏斗详情</div>
<div ref="chart" class="echart"></div>
<el-table
class="data-table"
:data="funnelData.funnelReportModels"
style="width: 100%">
<el-table-column prop="funnel" label="步骤" min-width="140" align="center"></el-table-column>
<el-table-column prop="funnelName" label="步骤名称" min-width="140" align="center"></el-table-column>
<el-table-column prop="userCount" label="用户数" min-width="150" align="center"></el-table-column>
<el-table-column prop="inversionRate" label="较上一步转化率" min-width="150" align="center"></el-table-column>
<el-table-column prop="inversionRateTotal" label="总转化率" min-width="150" align="center"></el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</template>
<script>
let url = 'image://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAARCAMAAACLgl7OAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAaVBMVEUAAADBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcEAAAD45xibAAAAInRSTlMAmT6WJYwSfBMGZAFHmEgtkBeCCW0KAlI1k5QeiA10A1tc7ah1owAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAB7SURBVCjPtZDZDoAgDAQXvA+8bwX1/3/SGKIBEd+cx07TdgtIiAF0/mygDvnAoYDr2b3nnjP8wOaDUG6J4ncfR9cdScpMzbJECZEXT1/kesyy0n1VPv6AulF908Kg6+9DWN/hjWGUfhpgYV5Ov8ywwgUhguODddtXvXIAjuUEs/70/t4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMTItMTZUMTU6MzM6MDkrMDg6MDCzL2BEAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTEyLTE2VDE1OjMzOjA5KzA4OjAwwnLY+AAAAABJRU5ErkJggg==';
import { openLoading, closeLoading } from "../../common/utils";
export default {
data() {
return {
funnelId: '',
name: '',
searchParam: {
endDate: '',
startDate: '',
version: ''
},
versionSelect: [], // 版本下拉数据
funnelData: {},
endDateOpt: {
disabledData: (time) => {
if ( this.searchParam.endDate != "" && this.searchParam.endDate != null) {
return (
time.getTime() > new Date(this.searchParam.endDate).getTime()
);
}
}
},
endDateOpt1: {
disabledDate: time => {
return (
time.getTime() < (new Date(this.searchParam.startDate).getTime() - 3600 * 1000 * 24 * 1) || time.getTime() > (new Date(this.searchParam.startDate).getTime() + 3600 * 1000 * 24 * 29)
);
}
},
beginTime: '',
endTime: ''
}
},
created() {
this.funnelId = this.$route.query.id;
this.name = this.$route.query.name;
},
mounted() {
//this.searchParam.endDate = new Date().format("yyyy-MM-dd");
let date1 = new Date();
let date2 = new Date(date1);
let date3 = new Date(date1);
date2.setDate(date1.getDate() - 7);
date3.setDate(date1.getDate() - 1);
let dt = date2.getDate() > 10 ? date2.getDate() : '0' + date2.getDate();
let dt1 = date3.getDate() > 10 ? date3.getDate() : '0' + date3.getDate();
this.searchParam.startDate = date2.getFullYear() + "-" + (date2.getMonth() + 1) + "-" + dt;
this.searchParam.endDate = date3.getFullYear() + "-" + (date3.getMonth() + 1) + "-" + dt1;
this.beginTime = date2.getFullYear() + "-" + (date2.getMonth() + 1) + "-" + dt;
this.endTime = date3.getFullYear() + "-" + (date3.getMonth() + 1) + "-" + dt1;
this.getVersionData();
},
methods: {
// 封装一下请求通用的方法
getData(type, url, req, callback) {
openLoading(this);
this.$axios[type](localStorage.getItem("lectureUrl") + url, req)
.then(res => {
closeLoading(this);
let data = res.data;
if (data.code == "000000") {
if (callback) callback(data);
} else {
this.$message.error(data.message);
}
})
},
// 获取版本号数据
getVersionData() {
this.getData(
"get", `/session/funnel/version/list`, {},
res => {
if(res.code == '000000') {
this.versionSelect = res.data;
this.searchParam.version = this.versionSelect[0];
this.getAnalysisData(this.searchParam);
}
}
);
},
// 获取漏斗分析数据
getAnalysisData(req) {
if(!req.startDate || !req.endDate) {
this.$message.info('请选择时间');
return;
}
this.getData(
"get", `/session/funnel/report?endDate=${req.endDate}&funnelId=${this.funnelId}&startDate=${req.startDate}&version=${req.version}`, {},
res => {
if(res.code == '000000') {
this.funnelData = res.data;
this.initCharts();
//this.initEchart();
}
}
);
},
// 初始化图标--漏斗图
initCharts() {
let lineargroup = this.funnelData.funnelReportModels;
let len = lineargroup.length;
let colorObj = {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: '#1cd389' // 0% 处的颜色
}, {
offset: 1,
color: '#668eff' // 100% 处的颜色
}],
global: false // 缺省为 false
};
let myChart = {}, data1 = [], dataArr = [], valueArr = [], lineArr = [], linksArr = [], arrowTop = 0, arrowH = 0, lineTop = 0, lineLeft = 0, lineHeight = 0, blackH;
if(len == 2) {
blackH = 300;
arrowTop = 105;
arrowH = 130;
lineTop = 50;
lineLeft = '4%';
lineHeight = 306;
}else if(len == 3) {
blackH = 300;
arrowTop = 115;
arrowH = 130;
lineTop = 30;
lineLeft = '30%';
lineHeight = 306;
}else if(len == 4) {
blackH = 300;
arrowTop = 88;
arrowH = 210;
lineTop = 30;
lineLeft = '45%';
lineHeight = 306;
}else if(len == 5) {
blackH = 300;
arrowTop = 70;
arrowH = 260;
lineTop = 20;
lineLeft = '42%';
lineHeight = 346;
}else if(len == 6) {
blackH = 300;
arrowTop = 60;
arrowH = 300;
lineTop = 20;
lineLeft = '40%';
lineHeight = 366;
}else if(len == 7) {
blackH = 360;
arrowTop = 60;
arrowH = 390;
lineTop = 20;
lineLeft = '35%';
lineHeight = 456;
}else {
blackH = 340;
arrowTop = 50;
arrowH = 400;
lineTop = 10;
lineLeft = '35%';
lineHeight = 466;
}
for (let i = 0; i < lineargroup.length; i++) {
let obj1 = {
value: 200 - i * 40,
num: lineargroup[i].userCount,
name: lineargroup[i].funnelName
};
data1.push(obj1);
if(i != (lineargroup.length - 1)) {
dataArr.push('转化率');
valueArr.push({value: '100'}); // 步骤箭头
linksArr.push({ // 转化率线
source: lineargroup[i].funnelName,
target: lineargroup[i + 1].funnelName,
value: lineargroup[i + 1].inversionRate,
lineStyle: {
normal: {
curveness: parseInt(len - i) + 4,
color: colorObj
}
}
})
}
lineArr.push({
name: lineargroup[i].funnelName,
x: 400,
y: 0 + i*15
});
}
let option = {
backgroundColor: '#ffffff',
grid: {
top: arrowTop, // 箭头距离顶部高度 3-115 4-
left: "-44%",
right: 20,
height: arrowH, // 箭头之间的距离 3-145 4-
bottom: '0'
},
xAxis: {
show: false
},
yAxis: [{
show: false,
boundaryGap: false,
type: "category",
data: dataArr
}],
series: [{ // 内容块
top: 0,
type: 'funnel',
height: blackH + len * 20, // 块高度
gap: 40, // 块间距
minSize: 300, // 块两边斜度
left: '5%', // 块左边距离
width: '45%', // 块宽度
label: {
show: true,
position: 'inside',
fontSize: '14',
formatter: function(d) {
var newParamsName = "";
var paramsNameNumber = d.name.length;
var provideNumber = 30; //一行显示几个字
var rowNumber = Math.ceil(paramsNameNumber / provideNumber);
if (paramsNameNumber > provideNumber) {
for (var p = 0; p < rowNumber; p++) {
var tempStr = "";
var start = p * provideNumber;
var end = start + provideNumber;
if (p == rowNumber - 1) {
tempStr = d.name.substring(start, paramsNameNumber);
} else {
tempStr = d.name.substring(start, end) + "\n";
}
newParamsName += tempStr;
}
} else {
newParamsName = d.name;
}
return newParamsName + '{aa|}\n' + d.data.num
},
rich: {
aa: {
padding: [8, 0, 6, 0]
}
}
},
data: data1
},
{
type: 'pictorialBar', // 步骤箭头
name: '',
symbolSize: ['32', '17'],
symbolPosition: 'center',
symbol: url,
animation: true,
symbolClip: true,
z: 10,
data: valueArr
},
{ // 转化率线
z: 1,
top: lineTop,
left: lineLeft,
height: lineHeight,
type: 'graph',
layout: 'none',
symbolSize: 0,
roam: false,
edgeSymbol: ['circle', 'arrow'],
lineStyle: {
normal: {
width: 4,
}
},
edgeLabel: { // 转化率数字
normal: {
show: true,
rotate: 90,
position: 'middle',
borderRadius: 4,
color: '#333',
verticalAlign: 'middle',
fontSize: 14,
legendHoverLink: true,
padding: [30, 20, 5, 10],
formatter: function(d) {
if (d.value) {
var ins = '{img1|} ' + '{words|' + d.value + '}';
return ins;
}
},
rich: {
img1: {
width: 18,
height: 16
},
words: {
color: '#333',
position: 'right',
fontSize: 14,
lineHeight: 20,
padding: [0, 0, 5, 0],
}
}
}
},
data: lineArr,
links: linksArr
}
]
};
myChart = this.$echarts.init(this.$refs.chart);
myChart.setOption(option);
},
// 初始化图--柱状图
initEchart() {
let dataX = [], dataY = [];
let lineargroup = this.funnelData.funnelReportModels;
for(let i = 0; i < lineargroup.length; i++) {
dataX.push(lineargroup[i].funnelName);
dataY.push(lineargroup[i].inversionRate);
}
let option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
xAxis: [
{
type: 'category',
data: dataX,
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
name: '',
min: 0,
max: 100,
interval: 25,
axisLabel: {
formatter: '{value} %'
}
}
],
series: [
{
type: 'bar',
barWidth: 30,
data: [80,60,20], // dataY
itemStyle: {
normal: {
label: {
show: true, //开启显示
position: 'top', //在上方显示
textStyle: { //数值样式
color: 'black',
fontSize: 16
}
},
color: function(params) {
return '#58ADE8'
}
}
}
},
{
name:'折线',
type:'line',
itemStyle : { /*设置折线颜色*/
normal : {
color:'#58ADE8'
}
},
data: [80,60,20] // dataY
}
]
};
let myChart = this.$echarts.init(this.$refs.chart);
myChart.setOption(option);
},
reset() {
this.searchParam = {
endDate: this.endTime,
startDate: this.beginTime,
version: this.versionSelect[0]
};
this.getAnalysisData(this.searchParam);
}
}
}
</script>
<style lang="less" scoped>
.data-container {
margin: -20px -40px;
.data-title {
height: 60px;
line-height: 60px;
padding-left: 40px;
}
.data-content {
background: #F2F2F4;
padding: 20px 0 20px 20px;
.con {
background: #fff;
padding-bottom: 50px;
.search {
padding: 20px 20px 0;
margin: 0 !important;
border-bottom: 1px solid #EDEDEE;
}
.data-num {
display: flex;
padding: 20px 20px 0 30px;
.num-text {
width: 15%;
border-right: 1px solid #EDEDEE;
text-align: center;
span {
font-size: 12px;
color: #b2b2b3;
}
p {
padding-top: 8px;
}
}
.first-num {
width: 10%;
text-align: left;
}
.num {
width: 20%;
border: none;
}
}
.data-analysis {
// overflow: hidden;
margin: 20px 20px 0 30px;
padding: 0px 20px 30px;
border: 1px solid #EDEDEE;
box-shadow: 5px 0px 30px #EDEDEE;
.map-title {
height: 60px;
line-height: 60px;
border-bottom: 1px solid #EDEDEE;
}
.echart {
padding-top: 20px;
width: 100%;
min-height: 550px;
}
.data-table {
margin-top: 20px;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="funnel-container">
<div class="funnel-title">漏斗分析</div>
<div class="funnel-content">
<div class="con">
<el-row :gutter="30" class="row search" type="flex" style="margin-bottom:0;">
<el-form ref="form" :model="searchParam" label-suffix=":" style="width:100%;">
<el-col :span="21">
<el-form-item label="漏斗名称">
<el-input v-model="searchParam.searchName" maxlength="20" placeholder="请输入漏斗名称" style="width:288px;"></el-input>
</el-form-item>
</el-col>
<el-col :span="3" style="padding:0;text-align:right;padding-right:15px;">
<el-button type="primary" size="small" @click="search(1)">搜索</el-button>
</el-col>
</el-form>
</el-row>
<div class="search-table-con">
<el-row :gutter="10" class="row create-button" style="margin-right:0;">
<el-button type="primary" size="small" @click="addData">+新增转化漏斗</el-button>
</el-row>
<!-- 表格 -->
<el-table
class="search-table"
:data="tableData"
style="width: 100%"
:empty-text="tableText">
<el-table-column prop="name" label="漏斗名称" min-width="140" align="center"></el-table-column>
<el-table-column prop="stepStartName" label="起始步骤" min-width="140" align="center"></el-table-column>
<el-table-column prop="stepEndName" label="转化目标" min-width="150" align="center"></el-table-column>
<el-table-column label="操作" fixed="right" align="center" min-width="200">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="visitData(scope.row)">查看数据</el-button>
<el-button type="primary" size="small" @click="editColumn(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="deleteConfirm(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="searchParam.pageNo"
:page-sizes="[15, 30, 50, 100, 200, 500, 700, 1000, 1500, 2000]"
:page-size="searchParam.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="totalRows">
</el-pagination>
</div>
<!-- 删除确认框 -->
<el-dialog
class="delete-dialog"
:visible.sync="dialogVisible"
width="30%">
<span><i class="el-icon-circle-close" style="color: red"></i>确认要删除这条转化漏斗吗?</span>
<span class="tip">删除该转化漏斗数据将不会保留</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="confirm">确 定</el-button>
</span>
</el-dialog>
</div>
</div>
</div>
</div>
</template>
<script>
import { openLoading, closeLoading } from "../../common/utils";
export default {
data() {
return {
tableText: '暂未创建漏斗',
searchParam: {
searchName: '',
pageNo: 1,
pageSize: 15
},
totalRows: 0,
tableData: [],
dialogVisible: false,
deleteId: ''
}
},
mounted() {
this.search();
},
methods: {
// 封装一下请求通用的方法
getData(type, url, req, callback) {
openLoading(this);
this.$axios[type](localStorage.getItem("lectureUrl") + url, req)
.then(res => {
closeLoading(this);
let data = res.data;
if (data.code == "000000") {
if (callback) callback(data);
} else {
this.$message.error(data.message);
}
})
},
search(param) {
if(param) this.searchParam.pageNo = 1;
let req = this.searchParam;
this.getData(
"get", `/session/funnel/list?searchName=${this.searchParam.searchName}&pageNo=${this.searchParam.pageNo}&pageSize=${this.searchParam.pageSize}`, {},
res => {
this.tableData = res.data.data;
this.totalRows = res.data.totalRows;
if(this.tableData.length == 0) {
this.tableText = param ? "暂无数据" : '暂未创建漏斗';
}
}
);
},
// 新增漏斗
addData() {
this.$router.push({ path: 'add-funnel' });
},
// 查看数据
visitData(row) {
this.$router.push({ path: 'path-data', query: {id: row.id, name: row.name} });
},
// 编辑
editColumn(row) {
this.$router.push({ path: 'add-funnel',query: {id: row.id}});
},
// 删除确认框
deleteConfirm(row) {
this.dialogVisible = true;
this.deleteId = row.id;
},
confirm() {
this.getData(
"delete", `/session/funnel/remove/${this.deleteId}`, {},
res => {
if(res.code == '000000') {
this.dialogVisible = false;
this.$message.success('删除成功');
this.search();
}
}
);
},
handleSizeChange(value) {
this.searchParam.pageSize = value;
this.search();
},
handleCurrentChange(value) {
this.searchParam.pageNo = value;
this.search();
}
}
}
</script>
<style lang="less" scoped>
.funnel-container {
margin: -20px -40px;
.funnel-title {
height: 60px;
line-height: 60px;
padding-left: 40px;
}
.funnel-content {
background: #F2F2F4;
padding: 20px 0 20px 20px;
min-height: 83vh;
.con {
padding-bottom: 30px;
background: #fff;
min-height: 80vh;
.search {
padding: 20px 20px 0;
margin: 0 !important;
border-bottom: 1px solid #EDEDEE;
}
.search-table-con {
padding: 0 40px;
.create-button {
margin-top: 20px;
}
}
.delete-dialog {
span{
display: block;
font-size: 16px;
font-weight: 700;
margin-left: 40px;
i {
display: inline-block;
font-size: 20px;
color: red;
margin-right: 10px;
}
}
span.tip {
font-size: 14px;
font-weight: 400;
color: #aaa;
margin: 15px 0 0 70px;
}
}
}
}
}
</style>
\ No newline at end of file
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册