You need to sign in or sign up before continuing.
Commit b0da70ed by crushh

update: dist

parents 48ae6e08 524fb43e
......@@ -40,6 +40,7 @@ export const getMemberCount = params => requests(PREFIX + '/get-member-count', p
// 获取提交并执行日志、提交未执行日志
export const getLogs = params => requests('/api-marketing/page-activity-event-exec-log', params, true, false, 'get');
// 获取AI数据统计详情
export const getCustomDetail = params => requests('/api-marketing/statistics/page-data-statistics-custom-detail', params, true, false, 'get');
//标签库-分页列表数据
......@@ -47,3 +48,11 @@ export const platformTagPageListV2 = params => requests('/gic-member-tag-web/mem
//标签库-标签层级
export const platformHomePageV2 = params => requests('/gic-member-tag-web/memberTag/platformHomePageV2', params, true, false, 'get');
// 获取AI数据统计活动信息
export const getActivityInfo = params => requests('/api-marketing/statistics/get-activity-info', params, true, false, 'get');
// 获取AI数据统计外呼数据
export const getOutBound = params => requests('/api-marketing/statistics/out-bound', params, true, false, 'get');
// 人群规则回显
export const getMemberCrowd = params => requests('/api-plug/query-member-crowd-new', params, true);
......@@ -3,7 +3,7 @@
<dm-sub-title style="margin-bottom: 5px">客户明细</dm-sub-title>
<div class="page-tip">消费金额字段每天更新 1 次</div>
<el-input v-model="search.search" @change="handleCurrentChange" type="text" placeholder="请输入姓名/昵称/手机号/会员卡号" prefix-icon="el-icon-search" clearable class="search-bar"></el-input>
<el-table :data="tableData" v-loading="loading">
<el-table :data="tableData" v-loading="loading" tooltipEffect="light">
<el-table-column label="基本信息" min-width="200px" show-overflow-tooltip>
<div slot-scope="{ row }" class="member-info">
<img class="member-logo" v-if="row.memberImage" :src="row.memberImage" alt="" />
......
<template>
<div>数据统计</div>
<div class="ai-data-report">
<div class="report-module">
<dm-sub-title>活动信息</dm-sub-title>
<activity-info @flag="getFlag"></activity-info>
</div>
<div class="report-module">
<dm-sub-title> AI外呼数据<span class="title-tip">数据实时更新</span> </dm-sub-title>
<ai-data :ai-data-show="aiDataShow"></ai-data>
</div>
<div class="report-module">
<dm-sub-title> 活动转化数据<span class="title-tip">数据每天更新 1 次</span> </dm-sub-title>
<conversion></conversion>
</div>
</div>
</template>
<script>
// import G2 from '@antv/g2';
import ActivityInfo from './ai-data-report/activity-info.vue';
import AiData from './ai-data-report/ai-data.vue';
import Conversion from './ai-data-report/conversion.vue';
export default {
name: 'AiDataReport',
components: { ActivityInfo, AiData, Conversion },
data() {
return {
aiDataShow: {
analyseFlag: 0,
smsFlag: 0
}
};
},
methods: {
getFlag(flag) {
this.aiDataShow = flag;
}
}
};
</script>
<style lang="scss" scoped>
.ai-data-report {
background-color: #f7f8fa;
.report-module {
padding: 20px;
background-color: #fff;
+ .report-module {
margin-top: 16px;
}
.title-tip {
margin-left: 16px;
font-size: 12px;
font-weight: 400;
color: #606266;
line-height: 17px;
}
}
}
</style>
<template>
<div>
<div class="activity-info">
<div class="activity-info-item">
<span class="activity-info-label"> 活动名称: </span>
{{ activityInfo.activityName || '--' }}
</div>
<div class="activity-info-item">
<span class="activity-info-label">活动状态:</span>
<i
:class="{
'dm-status--info': activityInfo.status == 1 || activityInfo.status == 4,
'dm-status--primary--flash': activityInfo.status == 2,
'dm-status--error': activityInfo.status == 3,
'dm-status--warning': activityInfo.status == 5
}"
></i>
{{ activityInfo.status | formatStatus }}
</div>
<div class="activity-info-item">
<span class="activity-info-label">活动有效期:</span>
{{ formatDateTimeByType(activityInfo.startDate, 'yyyy-MM-dd') || '--' }}
{{ formatDateTimeByType(activityInfo.endDate, 'yyyy-MM-dd') || '--' }}
</div>
<div class="activity-info-item">
<span class="activity-info-label">活动分析天数:</span>
触达之日起 {{ activityInfo.analyseDays || '--' }}
</div>
</div>
<div class="member-rule">
<div class="member-rule-title">人群规则:</div>
<div class="member-rule-list" v-if="activityInfo.memberType == 0">
<div class="member-rule-item">{{ memberRule || '--' }}</div>
</div>
<div class="member-rule-list" v-if="activityInfo.memberType == 1">
<div class="member-rule-item">生日范围:3月1日-3月31日</div>
<div class="member-rule-item">会员等级:银卡、金卡</div>
<div class="member-rule-item">金字塔会员分层:核心会员31-90天、潜力会员31-90天</div>
<div class="member-rule-item">会员服务门店:会员分组(华南直营、华北直营)</div>
</div>
</div>
</div>
</template>
<script>
import { formatDateTimeByType } from '@/utils/index.js';
import { getActivityInfo, getMemberCrowd } from '@/service/api/aiApi.js';
import gicNewMemberGroup from '@/components/dm-new-member-group/index.vue';
export default {
name: 'ActivityInfo',
components: { gicNewMemberGroup },
data() {
return {
activityInfo: {},
memberRule: ''
};
},
filters: {
formatStatus(state) {
const status = {
1: '未开始',
2: '进行中',
3: '已终止',
4: '已结束',
5: '欠费暂停'
};
return status[state] || '--';
}
},
created() {
this.getActivityInfo();
},
methods: {
formatDateTimeByType,
getActivityInfo() {
const activityId = this.$route.params.id;
const planId = this.$route.query.planId;
getActivityInfo({ activityId, planId }).then(res => {
this.activityInfo = res.result;
this.$emit('flag', {
analyseFlag: this.activityInfo.analyseFlag, //1开0关 开启活动分析开关
smsFlag: this.activityInfo.smsFlag //挂机短信1是0否
});
this.getMemberCrowd();
});
},
getMemberCrowd() {
getMemberCrowd({ memberCrowdWidgetId: this.activityInfo.filterJson }).then(res => {
if (res.result) {
this.memberRule = res.result.filterFrontShow;
}
});
}
}
};
</script>
<style lang="scss" scoped>
.activity-info {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 16px;
font-size: 14px;
font-weight: 400;
color: #303133;
line-height: 20px;
.activity-info-item {
+ .activity-info-item {
margin-left: 90px;
}
.activity-info-label {
margin-right: 10px;
}
}
}
.member-rule {
margin-top: 20px;
.member-rule-title {
font-size: 14px;
font-weight: 400;
color: #303133;
line-height: 20px;
}
.member-rule-list {
margin-top: 10px;
padding: 16px;
background: #f7f8fa;
border-radius: 4px;
.member-rule-item {
font-size: 14px;
font-weight: 400;
color: #303133;
line-height: 20px;
+ .member-rule-item {
margin-top: 10px;
}
}
}
}
</style>
<template>
<div>
<el-row :gutter="20">
<el-col :span="6" v-for="(el, index) in targetList" :key="index">
<target-group :data-list="el"></target-group>
</el-col>
</el-row>
<el-row class="chart-group">
<el-col :span="8">
<div class="chart-title">意向标签</div>
<div class="chart-box" id="chart-tag"></div>
</el-col>
<el-col :span="8">
<div class="chart-title">话单质量分析</div>
<div class="chart-box" id="chart-record"></div>
</el-col>
<el-col :span="8">
<div class="chart-title">通话时长</div>
<div class="chart-box">
<div id="chart-time"></div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { getOutBound } from '@/service/api/aiApi.js';
import TargetGroup from './target-group.vue';
export default {
name: 'AiData',
components: { TargetGroup },
props: {
aiDataShow: {
type: Object,
default: () => ({})
}
},
data() {
return {
activityId: '',
planId: '',
originTargetList: [
[
{
label: '营销人数',
tips: '根据每天外呼人数累加',
value: '',
key: 'marketingNumber'
},
{
label: '已外呼数',
value: '',
key: 'outboundNumber'
}
],
[
{
label: '总接通数',
value: '',
key: 'totalConnectionNumber'
},
{
label: '电话接通率',
tips: '电话接通率:表示任务中接通号码的占比<br/>计算公式为:接通的电话通数/已经外呼的电话通数',
value: '',
key: 'telephoneConnectionRate'
}
],
[
{
label: 'A/B类客户意向',
value: '',
key: 'customerIntentionsNumber'
},
{
label: '接通意向率',
tips: '接通意向率:表示所有已接通的通话中意向客户占比,其中A类和B类客户为意向客户<br/>计算公式为:A/B类客户意向数/已接通的电话数',
value: '',
key: 'connectionIntentionRate'
}
],
[
{
label: '总挂机数',
value: '',
key: 'totalHangUps'
},
{
label: '挂机率',
tips: '挂机率表示接通后5s之内挂断的电话<br/>计算公式为:总挂机数/接通的电话通数',
value: '',
key: 'hangUpRate'
}
],
[
{
label: '短信发送总数',
value: '',
key: 'sentMessagesNumber'
},
{
label: '发送成功数',
value: '',
key: 'sentSuccessfullyNumber'
}
],
[
{
label: '总通话时长',
value: '',
key: 'totalCallDuration'
},
{
label: '平均通话时长',
value: '',
key: 'averageCallDuration'
}
],
[
{
label: '活动费用',
tips: '不包含短信发送失败退回金额',
value: '',
unit: '元',
key: 'activityCost'
}
]
],
targetData: {}
};
},
computed: {
targetList() {
return this.originTargetList
.map(el => {
return el.filter(item => {
// 活动不发送挂机短信时,数据指标不展示短信发送总数的字段
// 活动未开启活动分析时,数据指标不展示活动费用
item.value = this.targetData[item.key] == null ? '--' : this.targetData[item.key];
return !((this.aiDataShow.analyseFlag == 0 && item.key == 'sentMessagesNumber') || (this.aiDataShow.smsFlag == 0 && item.key == 'activityCost'));
});
})
.filter(el => el.length > 0);
}
},
created() {
this.activityId = this.$route.params.id;
this.planId = this.$route.query.planId;
this.getOutBound();
},
methods: {
getOutBound() {
getOutBound({ activityId: this.activityId, planId: this.planId }).then(res => {
res.result = {
marketingNumber: 1,
outboundNumber: 2,
totalConnectionNumber: 3,
telephoneConnectionRate: 4,
customerIntentionsNumber: 5,
connectionIntentionRate: 6,
totalHangUps: 7,
hangUpRate: 8,
sentMessagesNumber: 9,
sentSuccessfullyNumber: 10,
totalCallDuration: 11,
averageCallDuration: 12,
activityCost: 13
};
this.targetData = res.result;
});
}
}
};
</script>
<style lang="scss" scoped>
.chart-group {
width: 100%;
.chart-title {
margin-top: 30px;
font-size: 14px;
font-weight: 600;
color: #303133;
line-height: 20px;
}
.chart-box {
width: 100%;
height: 220px;
}
}
</style>
<template>
<div>
<el-select class="contrast" placeholder="选择对比项" clearable>
<el-option v-for="el in contrast" :key="el.value" :value="el.value" :label="el.label"></el-option>
</el-select>
<div class="chart-box">
<div class="chart-member-count" id="chart-member-count"></div>
<el-row :gutter="20" class="target-list">
<el-col :span="index == 0 ? 24 : 12" v-for="(el, index) in targetList" :key="index">
<target-group :data-list="el"></target-group>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import TargetGroup from './target-group.vue';
export default {
name: 'Conversion',
components: { TargetGroup },
data() {
return {
contrast: [
{ label: '会员类型', value: 'type' },
{ label: '会员等级', value: 'level' },
{ label: '金字塔会员分层', value: 'fixed' },
{ label: '客户分组', value: 'group' }
],
targetList: [
[
{
label: '活动目标(销售额)',
unit: '元',
value: '200,000'
},
{
label: '实际达成(销售额)',
unit: '元',
value: '200,000'
},
{
label: '实际达成率',
value: '200.00%'
}
],
[
{
label: '活动费用',
tips: '不包含短信发送失败退回金额',
unit: '元',
value: '2,276.1'
},
{
label: 'ROI',
value: '1:88'
}
],
[
{
label: '客单价',
unit: '元',
value: '196.39'
},
{
label: '连带率',
value: '1.2'
}
]
]
};
}
};
</script>
<style lang="scss" scoped>
.contrast {
margin-top: 16px;
width: 160px;
}
.chart-box {
display: flex;
justify-content: flex-start;
align-items: center;
.chart-member-count,
.target-list {
flex-shrink: 0;
width: 50%;
min-height: 332px;
}
}
</style>
<template>
<div class="target-group">
<div class="target" :style="{ width: `${100 / dataList.length}%` }" v-for="(el, index) in dataList" :key="index">
<div class="target-label">
{{ el.label }}
<el-tooltip v-if="el.tips" effect="light" placement="top" popper-class="target-tooltip">
<div slot="content" v-html="el.tips" style="max-width: 450px"></div>
<i class="iconfont icon-QuestionCircleOutlined target-label-icon"></i>
</el-tooltip>
</div>
<div class="target-value">
{{ el.value }}<span v-if="el.unit" class="target-value-unit">{{ el.unit }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'TargetGroup',
props: {
dataList: {
type: Array,
default: () => []
}
}
};
</script>
<style lang="scss" scoped>
.target-group {
display: flex;
justify-content: flex-start;
align-items: flex-start;
margin-top: 16px;
padding: 20px;
width: 100%;
min-width: 265px;
background: #f7f8fa;
border-radius: 4px;
box-sizing: border-box;
.target {
flex-shrink: 0;
.target-label {
font-size: 14px;
font-weight: 400;
color: #606266;
line-height: 20px;
.target-label-icon {
font-size: inherit;
}
}
.target-value {
display: flex;
justify-content: flex-start;
align-items: flex-end;
margin-top: 8px;
font-size: 24px;
font-weight: bold;
color: #303133;
line-height: 28px;
.target-value-unit {
font-size: 14px;
line-height: 25px;
}
}
}
}
</style>
<style lang="scss">
.el-tooltip__popper.target-tooltip {
max-width: 475px !important;
}
</style>
......@@ -12,7 +12,7 @@
<div class="temp-title">{{ el.name }}</div>
<div class="temp-info">
<p class="temp-info-text">话术ID:{{ el.aiTemplateId }}</p>
<p class="temp-info-text">更新时间:{{ el.updateTime | formatDateTimeByType }}</p>
<p class="temp-info-text">更新时间:{{ formatDateTimeByType(el.updateTime) || '--' }}</p>
<el-button class="temp-check-btn" type="text" @click="showTempDetail(el)">查看</el-button>
</div>
<div :class="['temp-status', { publish: el.status == -1 || el.status == 0 || el.status == 1 || el.status == 2 || el.status == 3 }, { reject: el.status == 4 }, { published: el.status == 5 }]">
......@@ -27,30 +27,30 @@
</svg>
<div class="no-data-text">还未创建话术,请联系运营经理创建话术</div>
</div>
<el-dialog :visible.sync="tempDetail.show" @closed="onClosed" width="760px" title="查看话术">
<el-dialog :visible.sync="tempDetail.show" @closed="onClosed" custom-class="temp-detail" width="760px" title="查看话术">
<div v-if="!Array.isArray(tempDetail.tempData) || tempDetail.tempData.length == 0" class="no-temp-data">
<svg class="no-temp-icon" aria-hidden="true">
<use xlink:href="#icon-zanwuhuashu"></use>
</svg>
暂无话术内容
</div>
<template v-else>
<div class="temp-detail-list" v-else>
<div class="temp-detail-item" v-for="el in tempDetail.tempData" :key="el.decisionId">
<div class="temp-detail-content">
<div class="temp-detail-title">{{ el.decisionNodeType | formatNodeType }}</div>
<div v-if="!Array.isArray(el.list) || el.list.length == 0" class="no-temp-data">暂无录音</div>
<div v-if="!Array.isArray(el.decisionItemRecordDTOList) || el.decisionItemRecordDTOList.length == 0" class="no-temp-data">暂无录音</div>
<template v-else>
<div v-for="item in el.list" :key="item.id" class="sound-record">
<div class="temp-detail-desc">{{ item.knowledge }}</div>
<div v-for="item in el.decisionItemRecordDTOList" :key="item.id" class="sound-record">
<div class="temp-detail-desc">{{ item.knowledge || '--' }}</div>
<template v-if="item.realKnowledgeOssUrl">
<el-button type="text" @click="onPlay">播放</el-button>
<audio id="player" :src="item.realKnowledgeOssUrl"></audio>
<el-button type="text" @click="onPlay(item, `player-${el.decisionId}-${item.id}`)" :loading="item.playing">{{ item.playing ? '播放中...' : '播放' }}</el-button>
<audio :id="`player-${el.decisionId}-${item.id}`" :src="item.ttsKnowledgeOssUrl"></audio>
</template>
</div>
</template>
</div>
</div>
</template>
</div>
</el-dialog>
</div>
</template>
......@@ -62,6 +62,7 @@ export default {
name: 'Message',
data() {
return {
formatDateTimeByType,
tempList: [],
total: 0,
search: {
......@@ -88,7 +89,6 @@ export default {
};
},
filters: {
formatDateTimeByType,
formatStatus(status, statusList) {
const list = [{ label: '三方删除', value: -1 }, ...statusList];
const state = list.find(el => el.value == status);
......@@ -136,8 +136,21 @@ export default {
this.tempDetail.aiTemplateId = '';
this.tempDetail.tempData = [];
},
onPlay() {
document.getElementById('player').play();
onPlay(data, playerId) {
const handleError = () => {
this.$message.error('资源错误');
handleEnded(this);
};
const handleEnded = () => {
this.$set(data, 'playing', false);
player.removeEventListener('error', handleError);
player.removeEventListener('ended', handleEnded);
};
this.$set(data, 'playing', true);
const player = document.getElementById(playerId);
player.addEventListener('error', handleError);
player.addEventListener('ended', handleEnded);
player.play().catch(handleError);
}
}
};
......@@ -284,3 +297,16 @@ export default {
}
}
</style>
<style lang="scss">
.temp-detail {
.el-dialog__body {
padding-right: 0;
padding-left: 0;
}
.temp-detail-list {
padding: 0 20px;
max-height: 50vh;
overflow-y: auto;
}
}
</style>
......@@ -183,7 +183,12 @@ export default {
return row.log_flag === 1;
},
handler: row => {
console.log('数据统计');
this.$router.push({
path: '/ai/ai-data-report/' + row.activityId,
query: {
planId: row.planId
}
});
}
}
],
......
......@@ -74,8 +74,8 @@
<div class="card-item_foot clearfix">
<div class="fl" v-if="item.gicCouponType != 3 && item.gicCouponType != 4">
<!-- 由外部api创建 仅展示详情、删除、报表按钮 -->
<span v-if="$getButtonLimit($buttonCode.marketingEditCardStock) && item.auditingStatus !== -1" :limit-code="$buttonCode.marketingEditCardStock"
>剩余库存:{{ item.couponStock }} <a title="编辑库存" v-if="!shelfFlag && item.useCustomCode === 0 && item.canEdit !== false && !item.isApiCreate" @click="preAdjustStock(item)"><i class="el-icon-edit"></i></a
<span v-if="item.auditingStatus !== -1"
>剩余库存:{{ item.couponStock }} <a title="编辑库存" v-if="$getButtonLimit($buttonCode.marketingEditCardStock) && !shelfFlag && item.useCustomCode === 0 && item.canEdit !== false && !item.isApiCreate" :limit-code="$buttonCode.marketingEditCardStock" @click="preAdjustStock(item)"><i class="el-icon-edit"></i></a
></span>
</div>
<render-temp v-if="!shelfFlag" :item="item" @handler="handler"></render-temp>
......
......@@ -308,7 +308,7 @@
<el-dialog title="导出数据" :visible.sync="exportDialog.dialogVisible" width="500">
<div class="export-time text-center">
<h2>选择导出日期范围</h2>
<p class="tip mb10">日期最大可选择范围为3个月,不支持导出当天的数据</p>
<p class="tip mb10">日期最大可选择范围为3个月{{ $route.params.type == 'marketing' ? ',不支持导出当天的数据' : '' }}</p>
<el-date-picker :clearable="false" :pickerOptions="exportDialog.pickerOptions" v-model="exportDialog.time" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" @click.native="exportDialog.minDate = null"></el-date-picker>
<div class="mt40 mb40">
<el-button type="primary" @click="createRepoert">去生成报告</el-button>
......@@ -404,7 +404,7 @@ export default {
dialogVisible: false,
pickerOptions: {
disabledDate: val => {
const isSms = this.$route.params.type == 'marketing' || this.$route.params.type == 'ai-call'; // 是否为营销类型
const isSms = this.$route.params.type == 'marketing'; // 是否为营销类型
const beforeDay = Date.now() - 24 * 60 * 60 * 1000; // 营销类型最大选择时间为当天的前一天
// 只能筛选一年之内得数据,并且最大跨度为三个月
const oneYearBefore = Date.now() - 365 * 24 * 60 * 60 * 1000;
......@@ -728,9 +728,9 @@ export default {
const depart = this.deparment;
depart['departId'] = id;
this.listParams['currentPage'] = 1;
if (this.$route.params.type == 'ai-call') {
this.dateTime = [Date.now(), Date.now()];
}
// if (this.$route.params.type == 'ai-call') {
// this.dateTime = [Date.now(), Date.now()];
// }
this.loadAll();
},
// 下拉远程搜索
......@@ -801,7 +801,7 @@ export default {
'exportDialog.dialogVisible': {
handler: function(newVal) {
if (newVal) {
const isSms = this.$route.params.type == 'marketing' || this.$route.params.type == 'ai-call'; // 是否为短信营销
const isSms = this.$route.params.type == 'marketing'; // 是否为短信营销
const beforeDay = Date.now() - 24 * 60 * 60 * 1000;
const begenTime = isSms && this.dateTime[0] > beforeDay ? beforeDay : this.dateTime[0];
const endTime = isSms && this.dateTime[1] > beforeDay ? beforeDay : this.dateTime[1];
......
......@@ -31,7 +31,7 @@
<p class="fz12 gray line-height2">1、我们提供在线充值服务(目前仅支持微信)</p>
<p class="fz12 gray line-height2">2、请尽量保障账户余额大于您日常使用的额度,避免因余额不足导致业务中断</p>
<p class="fz12 gray line-height2">3、若未能及时充值或其他问题,请联系客户经理或客服人员</p>
<p class="fz12 gray line-height2">4、各项服务单价:国内短信验证码0.050元/条,国际短信验证码0.500元/条,双向呼叫0.420元/分钟,AI电话0.020元/分钟</p>
<p class="fz12 gray line-height2">4、各项服务单价:国内短信验证码0.050元/条,国际短信验证码0.500元/条,双向呼叫0.420元/分钟,AI电话0.450元/分钟</p>
<!-- <p class="fz12 gray line-height2">5、通话录音存储收费标准:「三个月{{ allUnitFee.storageThreeFee }}元/分钟」「六个月{{ allUnitFee.storageSixFee }}元/分钟」「十二个月{{ allUnitFee.storageTwelveFee }}元/分钟」,不满一分钟按一分钟收费</p> -->
</div>
</article>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment