NobleLeong 3 days ago
parent
commit
0bce107fec

+ 5 - 5
README.md

@@ -1,13 +1,13 @@
-## SCRM后台管理系统
+## AI外呼系统
 
 ```
  // 安装yarn包
  yarn install
 
- // 打包商家
- 本地开发:yarn dev:merchant
- 测试环境:yarn test:merchant
- 生产环境:yarn build:merchant
+ // 打包PC端
+ 本地开发:yarn dev:call
+ 测试环境:yarn test:call
+ 生产环境:yarn build:call
 
  //打包平台
  本地开发:yarn dev:platform

+ 1 - 25
public/call.html

@@ -14,34 +14,10 @@
     <!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> -->
     <script>
         function isPC () {
-            const ua = navigator.userAgent;
-            const platform = navigator.platform;
-
-            // 移动设备检测
-            const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i;
-            const isMobile = mobileRegex.test(ua);
-
-            // 平板设备检测
-            const tabletRegex = /iPad|Tablet|PlayBook|Silk|Kindle|(Android(?!.*Mobile))|(Windows(?!.*Phone))(.*Touch)/i;
-            const isTablet = tabletRegex.test(ua);
-
             // 屏幕尺寸检测
             const isSmallScreen = window.innerWidth < 768;
 
-            // 触摸支持检测
-            const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
-
-            // 如果是移动设备、平板设备、小屏幕设备,则不是PC
-            if (isMobile || isTablet || isSmallScreen) {
-                return false;
-            }
-
-            // 额外的PC特征检测
-            const isWindows = /Win/i.test(platform);
-            const isMac = /Mac/i.test(platform);
-            const isLinux = /Linux/i.test(platform);
-
-            return (isWindows || isMac || isLinux) && !hasTouch;
+            return !isSmallScreen;
         }
         if (!isPC()) {
             window.location.replace('<%= VUE_APP_H5_URL %>')

+ 2 - 0
public/index.html

@@ -16,6 +16,8 @@
     <script src="https://wfg-1631.oss.wefanbot.com/cdn/vue-router.min.js"></script>
     <script src="https://wfg-1631.oss.wefanbot.com/cdn/vuex.min.js"></script>
     <script src="https://wfg-1631.oss.wefanbot.com/cdn/iview.min.js"></script>
+    <script
+        src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1758187289797/echarts.js"></script>
     <link rel="shortcut icon" href="<%= BASE_URL %>favicon.ico">
     <title>
         <%= htmlWebpackPlugin.options.title %>

BIN
sound-chain-pc.7z


+ 8 - 1
src/pages/platform/components/main/index.vue

@@ -58,9 +58,16 @@ export default {
             }
             menuList = [
                 {
+                    title: '外呼数据',
+                    name: '外呼数据',
+                    menuUrl: '/home',
+                    iconUrl1: '/img/menu/product_menu_icon1@2x.png',
+                    iconUrl2: '/img/menu/product_menu_icon2@2x.png',
+                },
+                {
                     title: '经销商管理',
                     name: '经销商管理',
-                    menuUrl: '/home',
+                    menuUrl: '/store',
                     iconUrl1: '/img/menu/home_menu_icon1@2x.png',
                     iconUrl2: '/img/menu/home_menu_icon2@2x.png',
                 },

+ 8 - 0
src/pages/platform/router/staticRoutes.js

@@ -15,6 +15,14 @@ export default [
                 path: 'home',
                 name: 'home',
                 meta: {
+                    title: '外呼数据',
+                },
+                component: () => import('_platform/view/calldata/index.vue')
+            },
+            {
+                path: 'store',
+                name: 'store',
+                meta: {
                     title: '经销商管理',
                 },
                 component: () => import('_platform/view/store/index.vue')

+ 375 - 0
src/pages/platform/view/calldata/index.vue

@@ -0,0 +1,375 @@
+<template>
+    <div class="data_page">
+        <div class="page_header">
+            <div class="header_left">
+                <div class="header_label">外呼数据</div>
+            </div>
+            <div class="header_right">
+                <userBox />
+            </div>
+        </div>
+        <div class="filter_box">
+            <div class="filter_left">
+                <div class="filter_item">
+                    <div class="filter_time_list">
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='yesterday'}" @click="handleChangeRadio('yesterday')">昨日</div>
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='sevenDays'}" @click="handleChangeRadio('sevenDays')">近7日</div>
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='thirtyDays'}" @click="handleChangeRadio('thirtyDays')">近30日</div>
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='custom'}" @click="handleChangeRadio('custom')">自定义</div>
+                    </div>
+                    <DatePicker class="date_picker" v-show="curDateRange==='custom'" :transfer="true"  type="daterange"
+                        placeholder="选择日期" clearable @on-change="handleChangeDate" :options="dateOption" v-model="curDate" format="yyyy-MM-dd">
+                    </DatePicker>
+                </div>
+            </div>
+            <div class="filter_right">
+            </div>
+        </div>
+        <div class="page_content">
+            <div class="info_list">
+                <div class="info_item">
+                    <div class="label">五菱数据量</div>
+                    <div class="val">{{countData.callClientCount}}</div>
+                </div>
+                <div class="info_item">
+                    <div class="label">外呼任务量</div>
+                    <div class="val">{{countData.yiWiseCount+countData.pengYueCount}}</div>
+                    <div class="group_val">
+                        <div class="total_val">
+                            <div class="val_label">SA1:</div>
+                            <div class="item_val">{{countData.yiWiseCount}}</div>
+                        </div>
+                        <div class="total_val">
+                            <div class="val_label">SA2:</div>
+                            <div class="item_val">{{countData.pengYueCount}}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="chart_box">
+                <!-- 折线图 -->
+                <div class="line_chart" id="lineChart"></div>
+            </div>
+        </div>
+    </div>
+</template>
+<script>
+import { mapState } from 'vuex'
+import { getDateTime, digitalUnit } from '@/utils'
+import userBox from '@/pages/platform/components/userBox'
+export default {
+    components: {
+        userBox
+    },
+    data() {
+        return {
+            dateOption: {
+              disabledDate (date) {
+                  return date.valueOf() >= new Date()
+              }
+            },
+            yesterday: [getDateTime(-1), getDateTime(-1)],
+            sevenDays: [getDateTime(-7), getDateTime(-1)],
+            thirtyDays: [getDateTime(-30), getDateTime(-1)],
+            curDate: [getDateTime(-7), getDateTime(-1)],
+            curDateRange: 'sevenDays',
+            countData: {
+                callClientCount: 0,
+                pengYueCount: 0,
+                yiWiseCount: 0,
+            },
+            positionsData: {
+                clientPositions: [],
+                taskPositions: []
+            }
+        }
+    },
+    computed: {
+        ...mapState({
+            user: (state) => state.user, //
+        }),
+    },
+    created () {
+        this.handleSearch()
+    },
+    methods: {
+        handleChangeDate (val) {
+            this.curDate = [val[0], val[1]]
+            this.handleSearch()
+        },
+        handleChangeRadio(val) {
+            this.curDateRange = val
+            if (this[val]) {
+                this.curDate = this[val]
+                this.handleSearch()
+            }
+        },
+        handleSearch() {
+            this.currentPage = 1
+            this.initData()
+            this.getPositions()
+        },
+        initData() {
+            this.$http.get(`/call/api/stats/total-count`, {
+                params: {
+                    startDate: (this.curDate && this.curDate[0]) || getDateTime(-7),
+                    endDate: (this.curDate && this.curDate[7]) || getDateTime(-1),
+                }
+            }).then((response) => {
+                let { data, code, msg } = response
+                if (code === 1) {
+                    this.countData = data || {
+                        callClientCount: 0,
+                        pengYueCount: 0,
+                        yiWiseCount: 0,
+                    }
+                } else {
+                    this.$Notice.warning({
+                        desc: msg
+                    })
+                }
+            })
+        },
+        getPositions () {
+            this.$http.get(`/call/api/stats/positions`, {
+                params: {
+                    startDate: (this.curDate && this.curDate[0]) || getDateTime(-7),
+                    endDate: (this.curDate && this.curDate[7]) || getDateTime(-1),
+                }
+            }).then((response) => {
+                let { data, code, msg } = response
+                if (code === 1) {
+                    this.positionsData = data || {
+                        clientPositions: [],
+                        taskPositions: []
+                    }
+                    this.lineChartDraw()
+                } else {
+                    this.$Notice.warning({
+                        desc: msg
+                    })
+                }
+            })
+        },
+        lineChartDraw () {
+            let chartDiv = document.getElementById('lineChart')
+            let clientChart = echarts.init(chartDiv)
+            window.addEventListener('resize', function() {
+                clientChart.resize()
+            })
+            clientChart.setOption({
+                legend: {
+                    data: ['五菱数据量', '外呼任务量'],
+                    icon: 'roundRect',
+                    align: 'right',
+                    itemWidth: 24,
+                    itemHeight: 1,
+                    itemGap: 24,
+                    top: 14,
+                    left: 20,
+                    // 图标样式
+                    itemStyle: {
+                        borderWidth: 0,
+                        borderRadius: 3,
+                        opacity: 1
+                    },
+                    textStyle: {//图例文字的样式
+                        fontWeight: 400,
+                        fontSize: '12px',
+                        color: '#999999',
+                    }
+                },
+                tooltip: {
+                    trigger: 'axis',
+                },
+                grid: {
+                    left: '24px',
+                    right: '24px',
+                    top: '58px',
+                    bottom: '24px',
+                    containLabel: true
+                },
+                xAxis: {
+                    type: 'category',
+                    data: (this.positionsData.clientPositions || []).map(item => {
+                            return item.x
+                        }),
+                    axisLine: { show: false },
+                    axisTick: { show: false },
+                    axisLabel: {
+                        margin: 20
+                    }
+                },
+                yAxis: {
+                    splitLine: { //网格线
+                        lineStyle: {
+                            type: 'dashed'//设置网格线类型 dotted:虚线   solid:实线
+                        },
+                        show: true //隐藏或显示
+                    },
+                    axisLine: { show: false },
+                    axisTick: { show: false },
+                    min: 0,
+                    minInterval: 1,
+                    splitNumbe: 8,
+                    axisLabel: {
+                        formatter: function(value, index) {
+                            return digitalUnit(value)
+                        }
+                    }
+                },
+                series: [
+                    {
+                        name: '五菱数据量',
+                        type: 'line',
+                        data: (this.positionsData.clientPositions || []).map(item => {
+                            return item.y
+                        }),
+                        symbol: 'circle', // 可选值: 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
+                        symbolSize: 12, //拐点大小
+                        itemStyle: {
+                            color: '#1677FF', // 拐点填充色
+                            borderColor: '#ffffff', // 拐点边框颜色
+                            borderWidth: 2, // 拐点边框宽度
+                            opacity: 1 // 完全不透明
+                        },
+                        smooth: true,
+                        showSymbol: false,
+                        color: '#1677FF',
+                        lineStyle: {
+                            width: 2,
+                            color: '#1677FF',
+                        }, //线条的样式
+                        areaStyle: {
+                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                                {
+                                    offset: 0,
+                                    color: 'rgba(22, 119, 255, 0.5)'
+                                },
+                                {
+                                    offset: 1,
+                                    color: 'rgba(22, 119, 255, 0)'
+                                }
+                            ])
+                        },
+                    },
+                    {
+                        name: '外呼任务量',
+                        type: 'line', //形状为柱状图
+                        data: (this.positionsData.taskPositions || []).map(item => {
+                            return item.y
+                        }),
+                        symbol: 'circle', // 可选值: 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
+                        symbolSize: 12, //拐点大小
+                        itemStyle: {
+                            color: '#FFCF88', // 拐点填充色
+                            borderColor: '#ffffff', // 拐点边框颜色
+                            borderWidth: 2, // 拐点边框宽度
+                            opacity: 1 // 完全不透明
+                        },
+                        smooth: true,
+                        showSymbol: false,
+                        color: '#FFCF88',
+                        lineStyle: {
+                            width: 2,
+                            color: '#FFCF88',
+                        }, //线条的样式
+                        areaStyle: {
+                            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                                {
+                                    offset: 0,
+                                    color: 'rgba(255, 207, 136, 0.5)'
+                                },
+                                {
+                                    offset: 1,
+                                    color: 'rgba(255, 207, 136, 0)'
+                                }
+                            ])
+                        },
+                    }
+                ],
+            }, true)
+        },
+    },
+}
+</script>
+<style lang="less" scoped>
+.data_page {
+    .page_content {
+        .info_list {
+            display: flex;
+            gap: 24px;
+            margin-bottom: 24px;
+            .info_item {
+                flex: 1;
+                min-width: 189px;
+                max-width: 289px;
+                height: 138px;
+                background: linear-gradient(91deg, #FFFFFF 0%, rgba(255, 255, 255, 0.5) 100%);
+                border-radius: 20px;
+                border: 1px solid #FFFFFF;
+                cursor: pointer;
+                padding: 16px 100px 12px 24px;
+                position: relative;
+
+                .label {
+                    font-weight: 400;
+                    font-size: 14px;
+                    color: #222222;
+                    line-height: 20px;
+                    display: flex;
+                    width: max-content;
+                    white-space: nowrap;
+                    .ivu-tooltip{
+                        height: 20px;
+                    }
+                    .tooltip_icon {
+                        width: 20px;
+                        height: 20px;
+                        margin-left: 4px;
+                    }
+                }
+
+                .val {
+                    font-family: DINOT;
+                    font-weight: bold;
+                    font-size: 30px;
+                    color: #222222;
+                    height: 30px;
+                    line-height: 30px;
+                    margin: 24px 0 16px;
+                }
+                .group_val{
+                    display: flex;
+                    align-items: center;
+                    gap: 24px;
+                    .total_val{
+                        display: flex;
+                        align-items: center;
+                        font-weight: 400;
+                        font-size: 14px;
+                        line-height: 20px;
+                        .val_label {
+                            color: #999999;
+                            white-space: nowrap;
+                        }
+                        .item_val {
+                            color: #136DFB;
+                        }
+                    }
+                }
+            }
+
+        }
+        .chart_box{
+            .line_chart{
+                flex:1;
+                min-width: 1120px;
+                height: 400px;
+                background: #FFFFFF;
+                border-radius: 10px
+            }
+        }
+    }
+}
+</style>

+ 46 - 14
src/pages/platform/view/config/index.vue

@@ -11,6 +11,17 @@
         <div class="filter_box">
             <div class="filter_left">
                 <div class="filter_item">
+                    <div class="filter_time_list">
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='yesterday'}" @click="handleChangeRadio('yesterday')">昨日</div>
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='sevenDays'}" @click="handleChangeRadio('sevenDays')">近7日</div>
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='thirtyDays'}" @click="handleChangeRadio('thirtyDays')">近30日</div>
+                        <div class="filter_time_item" :class="{'current_date':curDateRange==='custom'}" @click="handleChangeRadio('custom')">自定义</div>
+                    </div>
+                    <DatePicker class="date_picker" v-show="curDateRange==='custom'" :transfer="true"  type="daterange"
+                        placeholder="选择日期" clearable @on-change="handleChangeDate" :options="dateOption" v-model="curDate" format="yyyy-MM-dd">
+                    </DatePicker>
+                </div>
+                <div class="filter_item">
                     <label>话术名称:</label>
                     <Input v-model="name" icon="ios-search" placeholder="请输入" clearable @on-change="handleSearch" />
                 </div>
@@ -74,7 +85,7 @@
 </template>
 <script>
 import { mapState } from 'vuex'
-import { debounce } from '@/utils'
+import { debounce, getDateTime } from '@/utils'
 import userBox from '@/pages/platform/components/userBox'
 import tablePageMixin from '_m/tablePage.mixin'
 export default {
@@ -117,7 +128,17 @@ export default {
             smsTemplateList: [],
             storeId: null,
             existOptions: [],
-            apiTaskId: null
+            apiTaskId: null,
+            dateOption: {
+              disabledDate (date) {
+                  return date.valueOf() >= new Date()
+              }
+            },
+            yesterday: [getDateTime(-1), getDateTime(-1)],
+            sevenDays: [getDateTime(-7), getDateTime(-1)],
+            thirtyDays: [getDateTime(-30), getDateTime(-1)],
+            curDate: [getDateTime(-7), getDateTime(-1)],
+            curDateRange: 'sevenDays',
         }
     },
     computed: {
@@ -136,29 +157,26 @@ export default {
                     title: '经销商',
                     minWidth: 120,
                     key: 'tenancyName',
-                    align: 'center',
+                    align: 'left',
                 },
                 {
                     title: '话术模板',
                     minWidth: 180,
                     key: 'dialogFlowName',
-                    align: 'center'
+                    align: 'left'
                 },
                 {
                     title: '操作',
-                    align: 'center',
-                    minWidth: 130,
+                    align: 'left',
+                    width: 130,
                     render: (h, params) => {
                         const array = []
                         const { row, index } = params
                         array.push(h('div', {
                             style: {
-                                margin: '5px 10px 5px 0',
                                 color: '#1677FF',
                                 cursor: 'pointer',
-                                display: 'inline-block',
-                                paddingRight: '30px',
-                                lineHeight: '17px',
+
                             },
                             on: {
                                 click: (event) => {
@@ -168,11 +186,8 @@ export default {
                         }, '编辑'))
                         array.push(h('div', {
                             style: {
-                                margin: '5px 10px 5px 0',
                                 color: '#FF4E4E',
                                 cursor: 'pointer',
-                                display: 'inline-block',
-                                lineHeight: '17px',
                             },
                             on: {
                                 click: (event) => {
@@ -180,7 +195,14 @@ export default {
                                 }
                             }
                         }, '删除'))
-                        return h('div', array)
+                        return h('div', {
+                            style: {
+                                display: 'flex',
+                                lineHeight: '17px',
+                                gap: '5px 30px',
+                                whiteSpace: 'nowrap'
+                            },
+                        }, array)
                     }
                 }
             ]
@@ -220,6 +242,16 @@ export default {
                 this.tableHeight = tableContentHeight - (32 + 40 + 58)
             })
         },
+        handleChangeDate (val) {
+            this.curDate = [val[0], val[1]]
+        },
+        handleChangeRadio(val) {
+            this.curDateRange = val
+            if (this[val]) {
+                this.curDate = this[val]
+                this.handleChangeDate(this.curDate)
+            }
+        },
         handleSearch() {
             this.currentPage = 1
             this.initData()

+ 12 - 8
src/pages/platform/view/store/index.vue

@@ -111,7 +111,7 @@ export default {
             let data3 = []
             if (data1.length) {
                 data3.push({
-                    title: 'PENG_YUE',
+                    title: 'SA2',
                     expand: true,
                     value: 'PENG_YUE',
                     selected: false,
@@ -121,7 +121,7 @@ export default {
             }
             if (data2.length) {
                 data3.push({
-                    title: '一知智能',
+                    title: 'SA1',
                     expand: true,
                     value: 'YI_WISE',
                     selected: false,
@@ -148,7 +148,7 @@ export default {
                     title: '经销商',
                     minWidth: 140,
                     key: 'name',
-                    align: 'center',
+                    align: 'left',
                 },
                 {
                     title: '可用话术',
@@ -194,17 +194,14 @@ export default {
                 {
                     title: '操作',
                     align: 'center',
-                    minWidth: 80,
+                    width: 80,
                     render: (h, params) => {
                         const array = []
                         const { row, index } = params
                         array.push(h('div', {
                             style: {
-                                margin: '5px 10px 5px 0',
                                 color: '#1677FF',
                                 cursor: 'pointer',
-                                display: 'inline-block',
-                                lineHeight: '17px',
                             },
                             on: {
                                 click: (event) => {
@@ -212,7 +209,14 @@ export default {
                                 }
                             }
                         }, '编辑'))
-                        return h('div', array)
+                        return h('div', {
+                            style: {
+                                display: 'flex',
+                                lineHeight: '17px',
+                                gap: '5px 30px',
+                                whiteSpace: 'nowrap'
+                            },
+                        }, array)
                     }
                 }
             ]

+ 13 - 13
src/pages/platform/view/user/index.vue

@@ -126,7 +126,7 @@ export default {
                     title: '经销商',
                     minWidth: 140,
                     key: 'store',
-                    align: 'center',
+                    align: 'left',
                     render: (h, params) => {
                         const { row } = params
                         let text = (row.store && row.store.name) || ''
@@ -137,19 +137,19 @@ export default {
                     title: '短信模板ID',
                     minWidth: 140,
                     key: 'smsAfterCallParam',
-                    align: 'center'
+                    align: 'left'
                 },
                 {
                     title: '绑定企微',
                     minWidth: 140,
                     key: 'wxcpMemberName',
-                    align: 'center'
+                    align: 'left'
                 },
                 {
                     title: '状态',
                     minWidth: 140,
                     key: 'status',
-                    align: 'center',
+                    align: 'left',
                     render: (h, params) => {
                         const { row } = params
                         let text = ''
@@ -183,18 +183,14 @@ export default {
                 {
                     title: '操作',
                     align: 'left',
-                    minWidth: 100,
+                    width: 130,
                     render: (h, params) => {
                         const array = []
                         const { row, index } = params
                         array.push(h('div', {
                             style: {
-                                margin: '5px 10px 5px 0',
                                 color: '#1677FF',
                                 cursor: 'pointer',
-                                display: 'inline-block',
-                                paddingRight: '30px',
-                                lineHeight: '17px',
                             },
                             on: {
                                 click: (event) => {
@@ -205,11 +201,8 @@ export default {
                         if (row.userStatus !== -1) {
                             array.push(h('div', {
                                 style: {
-                                    margin: '5px 10px 5px 0',
                                     color: '#FF4E4E',
                                     cursor: 'pointer',
-                                    display: 'inline-block',
-                                    lineHeight: '17px',
                                 },
                                 on: {
                                     click: (event) => {
@@ -218,7 +211,14 @@ export default {
                                 }
                             }, '禁用'))
                         }
-                        return h('div', array)
+                        return h('div', {
+                            style: {
+                                display: 'flex',
+                                lineHeight: '17px',
+                                gap: '5px 30px',
+                                whiteSpace: 'nowrap'
+                            },
+                        }, array)
                     }
                 }
             ]