فهرست منبع

客户行为统计

yujinwen 3 ماه پیش
والد
کامیت
8ec8bbaffe

+ 18 - 11
src/components/EmptyWrap.vue

@@ -1,21 +1,28 @@
 <script setup>
 
+const props = defineProps({
+  msg: {
+    type: String,
+    default: '暂无数据'
+  }
+})
+
 </script>
 
 <template>
-    <div class="empty-wrap">
-        <img src="@/assets/imgs/nodata.png" alt="">
-        <p>暂无数据</p>
-    </div>
+  <div class="empty-wrap">
+    <img src="@/assets/imgs/nodata.png" alt="" />
+    <p>{{props.msg}}</p>
+  </div>
 </template>
 
 <style lang="scss" scoped>
-.empty-wrap{
-    text-align: center;
-    color: #666;
-    img{
-        width: 200px;
-        margin-bottom: 20px;
-    }
+.empty-wrap {
+  text-align: center;
+  color: #666;
+  img {
+    width: 200px;
+    margin-bottom: 20px;
+  }
 }
 </style>

+ 6 - 6
src/layout/components/LeftWrap.vue

@@ -25,12 +25,12 @@ const navList = ref([
   },
   {
     name: 'ETA客户行为统计',
-    path: '/',
+    path: '/customer',
     IsLevel: 1,
     Children: [
       {
         name: 'ETA客户行为统计',
-        path: '/',
+        path: '/customer/userActionStatistic',
       }
     ]
   },
@@ -221,13 +221,13 @@ function getMenuIcon(item) {
       :width="['200px', '80px']"
     >
       <template v-for="level1 in navList" :key="level1.SysMenuId">
-        <t-menu-item :value="level1.path" v-if="level1.IsLevel === 1">
+        <t-menu-item :value="level1.Children[0].path" v-if="level1.IsLevel === 1">
           <svg-icon
-            :name="getMenuIcon(level1.path)"
-            :color="$route.path === level1.path ? '#086CE0' : '#333'"
+            :name="getMenuIcon(level1.Children[0].path)"
+            :color="$route.path === level1.Children[0].path ? '#086CE0' : '#333'"
             style="font-size: 16px"
           ></svg-icon>
-          <span style="margin-left: 5px">{{ level1.name }}</span>
+          <span style="margin-left: 5px">{{ level1.Children[0].name }}</span>
         </t-menu-item>
         <t-submenu :value="level1.path" v-if="level1.IsLevel === 2">
           <template #title>

+ 4 - 0
src/main.js

@@ -6,6 +6,7 @@ import './styles/common.scss'
 import './styles/tdesign.scss'
 // 引入组件库的少量全局样式变量
 import 'tdesign-vue-next/es/style/index.css';
+import {formatTime} from '@/utils/common'
 
 //引入注册脚本
 import 'virtual:svg-icons-register'
@@ -13,6 +14,9 @@ import registerGlobalComponents from '@/components/globalComponents';
 
 const app= createApp(App)
 
+// 挂载一个全局格式化时间方法
+app.config.globalProperties.formatTime=formatTime
+
 // 注册全局组件
 registerGlobalComponents(app)
 

+ 20 - 2
src/router/modules/customer.js

@@ -12,11 +12,29 @@ export default[
       {
         path:'userList',
         name:'CustomerUserList',
-        component:()=>import('@/views/customer/userList/Index.vue'),
+        component:()=>import('@/views/customer/user/Index.vue'),
         meta:{
           title:'用户管理'
         },
       }
     ]
-  }
+  },
+  {
+    path:'/customer',
+    name:'CustomerUser',
+    component:LayoutIndex,
+    meta:{
+      title:'ETA客户行为统计'
+    },
+    children:[
+      {
+        path:'userActionStatistic',
+        name:'CustomerUserActionStatistic',
+        component:()=>import('@/views/customer/user/ActionStatistic.vue'),
+        meta:{
+          title:'ETA客户行为统计'
+        },
+      }
+    ]
+  },
 ]

+ 29 - 0
src/styles/common.scss

@@ -20,6 +20,7 @@ P{
 }
 
 img {
+    object-fit: contain;
     image-rendering: -moz-crisp-edges;
     image-rendering: -o-crisp-edges;
     image-rendering: -webkit-optimize-contrast;
@@ -39,4 +40,32 @@ img {
 .t-table thead td, .t-table th{
     color: #333 !important;
     font-weight: bold !important;
+}
+
+/* 单行省略 */
+.text-ellipsis--l1 {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+
+/* 两行省略*/
+.text-ellipsis--l2 {
+	display: -webkit-box;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	-webkit-line-clamp: 2;
+	line-break: anywhere;
+	-webkit-box-orient: vertical;
+}
+
+/* 三行省略*/
+.text-ellipsis--l3 {
+	display: -webkit-box;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	-webkit-line-clamp: 3;
+	line-break: anywhere;
+	-webkit-box-orient: vertical;
 }

+ 1 - 1
src/styles/var.scss

@@ -1,3 +1,3 @@
 :root{
-  
+  --border-color:#DCDFE6;
 }

+ 32 - 0
src/utils/common.js

@@ -0,0 +1,32 @@
+import moment from "moment";
+//验证密码的正则 产品定的规则是:8位及以上,包含数字、大写字母、小写字母、特殊字符中的三个类型
+export const patternPassWord = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{8,}$/;
+export function checkPassWord(pwd) {
+  let num = 0;
+  const patternArr = [/^(?=.*[0-9])/, /^(?=.*[a-z])/, /^(?=.*[A-Z])/, /^(?=.*[@#$%^&+=.])/];
+  patternArr.forEach((pattern) => {
+    if (pattern.test(pwd)) {
+      num++;
+    }
+  });
+  if (pwd.length < 8) {
+    num = 0;
+  }
+  return num >= 3;
+}
+
+//验证手机号的正则 仅支持国内大陆的
+export const patternPhone = /0?(13|14|15|18|17)[0-9]{9}/;
+export function isMobileNo(account) {
+  //改成和后端一样的正则
+  const phonePatter = new RegExp("(^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0-9])|(17[0-9])|(16[0-9])|(19[0-9]))\\d{8}$)");
+  return phonePatter.test(account);
+}
+//验证邮箱的正则
+export const patternEmail = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
+
+// 格式话时间 t时间字符串 f要格式化的样式
+export function formatTime(t, f = "YYYY-MM-DD HH:mm:ss") {
+  if (!t) return "";
+  return moment(t).format(f);
+}

+ 48 - 0
src/views/customer/components/SelectETACustomer.vue

@@ -0,0 +1,48 @@
+<script setup>
+
+const emits=defineEmits(['change'])
+
+const value = defineModel('value');
+const options = ref([]);
+const loading = ref(false);
+
+function remoteMethod (search) {
+
+    loading.value = true;
+    setTimeout(() => {
+      loading.value = false;
+      options.value = [
+        {
+          value: `腾讯_test1`,
+          label: `腾讯_test1`,
+        },
+        {
+          value: `腾讯_test2`,
+          label: `腾讯_test2`,
+        },
+        {
+          value: `腾讯_test3`,
+          label: `腾讯_test3`,
+        },
+      ];
+    }, 500);
+  
+};
+
+function handleChange(value,context){
+  emits('change',value,context)
+}
+
+</script>
+
+<template>
+  <t-select
+    v-model="value"
+    filterable
+    placeholder="输入社会信用码或客户名称"
+    :loading="loading"
+    :options="options"
+    @search="remoteMethod"
+    @change="handleChange"
+  />
+</template>

+ 49 - 0
src/views/customer/user/ActionStatistic.vue

@@ -0,0 +1,49 @@
+<script setup>
+import ActionStatisticForChart from './components/ActionStatisticForChart.vue'
+import ActionStatisticForCustomer from './components/ActionStatisticForCustomer.vue'
+
+const showType=ref('按客户')
+
+
+</script>
+
+<template>
+  <div class="user-action-statistic-page">
+    <div class="flex top-type">
+      <span :class="['btn',showType==='按客户'?'active_btn':'']" @click="showType='按客户'">按客户</span>
+      <span :class="['btn',showType==='按图表'?'active_btn':'']" @click="showType='按图表'">按图表</span>
+    </div>
+    <div class="bg-white main-wrap">
+      <ActionStatisticForCustomer v-if="showType==='按客户'"/>
+      <ActionStatisticForChart v-if="showType==='按图表'"/>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.user-action-statistic-page{
+  .top-type{
+    border: 1px solid #DCDFE6;
+    border-top-left-radius: 4px;
+    border-top-right-radius: 4px;
+    overflow: hidden;
+    width: fit-content;
+    .btn{
+      display: block;
+      width: 120px;
+      height: 40px;
+      line-height: 40px;
+      text-align: center;
+      color: #666666;
+      cursor: pointer;
+    }
+    .active_btn{
+      background-color: var(--td-brand-color);
+      color: #fff;
+    }
+  }
+  .main-wrap{
+    padding: 30px 20px;
+  }
+}
+</style>

+ 2 - 0
src/views/customer/userList/Index.vue → src/views/customer/user/Index.vue

@@ -48,6 +48,7 @@ const tablePagination = {
   defaultCurrent: 1,
   defaultPageSize: 20,
   total: 0,
+  showPageSize:false
 }
 
 
@@ -62,6 +63,7 @@ const showEditUser=ref(false)
     <div class="bg-white flex top-wrap">
       <t-button style="width: 120px" @click="showEditUser=true">新增用户</t-button>
       <t-button style="width: 120px; margin-left: 10px">批量导入用户</t-button>
+      <t-button style="width: 120px; margin-left: 10px" theme="primary" variant="text">下载导入模板</t-button>
       <t-input
         style="width: 310px; margin-left: auto"
         placeholder="姓名/手机号"

+ 126 - 0
src/views/customer/user/components/ActionStatisticForChart.vue

@@ -0,0 +1,126 @@
+<script setup>
+import Highcharts from "highcharts";
+import HighchartsMore from "highcharts/highcharts-more";
+import HighchartszhCN from "@/hooks/chart/highcahrts-zh_CN";
+
+
+HighchartszhCN(Highcharts);
+HighchartsMore(Highcharts);
+
+const chartDefaultOpts = {
+  chart: {
+    type: "column",
+    inverted: true, //xy轴换位置
+    marginRight: 85,
+  },
+  title: {
+    text: "",
+  },
+  //版权信息
+  credits: { enabled: false },
+  plotOptions: {
+    column: {
+      pointPadding: 0,
+      stacking: "normal",
+      dataLabels: {
+        enabled: true,
+        align: "right",
+        color: "#333",
+        inside: false,
+        crop: false,
+        overflow: true,
+        x: 80,
+        formatter: function (e) {
+          return this.point.options.isLabel;
+        },
+      },
+    },
+  },
+  legend: {
+    enabled: false, //关闭图例
+  },
+  tooltip: {
+    enabled: false,
+  },
+}
+
+function chartRender() {
+  let series = [
+    {
+      name: '累计收藏图表量',
+      data: [
+        {
+          y: 10,
+          isLabel: '2024-10-10'
+        },
+        {
+          y: 10,
+          isLabel: '2024-10-10'
+        },
+        {
+          y: 10,
+          isLabel: '2024-10-10'
+        }
+      ]
+    }
+  ]
+  let xAxis = {
+    categories: ['图表1','图表2','图表3',],
+    tickWidth: 1,
+    lineColor:'#E4E4E4',
+    tickColor:'#E4E4E4',
+    tickPosition: "outside",
+  };
+  let yAxis = {
+    opposite: true,
+    gridLineWidth: 1,
+    gridLineColor: "#E4E4E4",
+    gridLineDashStyle: "longdash",
+    endOnTick: false,
+    showLastLabel: true,
+    lineWidth: 1,
+    lineColor:'#E4E4E4',
+    tickWidth: 1,
+    tickColor:'#E4E4E4',
+    tickPosition: "outside",
+    title: {
+      text: "",
+    },
+    reversedStacks: false,
+  };
+
+  const options={
+    ...chartDefaultOpts,
+    xAxis:xAxis,
+    yAxis:yAxis,
+    series:series,
+  }
+
+  Highcharts.chart(`fav-chart-statistic`, options);
+}
+
+function getChartDetail() {
+  chartRender()
+}
+
+onMounted(() => {
+  getChartDetail()
+})
+
+
+</script>
+
+<template>
+  <div class="fav-chart-wrap">
+    <h3 class="wrap-label">累计收藏图表量</h3>
+    <div class="chart-render-box" id="fav-chart-statistic"></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.fav-chart-wrap {
+  .wrap-label {
+    text-align: center;
+  }
+}
+</style>

+ 76 - 0
src/views/customer/user/components/ActionStatisticForCustomer.vue

@@ -0,0 +1,76 @@
+<script setup>
+import SelectETACustomer from '../../components/SelectETACustomer.vue'
+import FavChartStatistic from './FavChartStatistic.vue'
+import { Calendar1Icon } from 'tdesign-icons-vue-next'
+
+const timeType = [
+  {
+    label: '今天',
+    value: '今天'
+  },
+  {
+    label: '过去3天',
+    value: '过去3天'
+  },
+  {
+    label: '过去一周',
+    value: '过去一周'
+  },
+  {
+    label: '过去一月',
+    value: '过去一月'
+  }
+]
+const timeTypeValue = ref('')
+const selectDate = ref([])
+
+const tableData = ref([])
+const columns = [
+  { align: 'center', colKey: '', title: '用户名' },
+  { align: 'center', colKey: '', title: '客户名' },
+  { align: 'center', colKey: '', title: '累计收藏图表' },
+  { align: 'center', colKey: '', title: '最近一次收藏时间', sorter: true, },
+]
+const tablePagination = {
+  defaultCurrent: 1,
+  defaultPageSize: 20,
+  total: 0,
+  showPageSize: false
+}
+
+const showFavChart=ref(false)
+
+</script>
+
+<template>
+  <div class="flex top-filter">
+    <SelectETACustomer style="width: 240px; margin-right: 50px" />
+    <t-button
+      :variant="timeTypeValue === item.value ? 'base' : 'outline'"
+      v-for="item in timeType"
+      :key="item.value"
+      style="width: 100px"
+      >{{ item.label }}</t-button
+    >
+    <t-date-range-picker v-model="selectDate" clearable style="width: 300px" />
+  </div>
+  <t-table
+    rowKey="id"
+    :data="tableData"
+    :columns="columns"
+    bordered
+    :pagination="tablePagination"
+    show-header
+    resizable
+  />
+
+  <!-- 用户收藏图表 -->
+  <FavChartStatistic v-model:show="showFavChart"/>
+</template>
+
+<style lang="scss" scoped>
+.top-filter {
+  gap: 20px;
+  margin-bottom: 30px;
+}
+</style>

+ 0 - 0
src/views/customer/userList/components/EditUser.vue → src/views/customer/user/components/EditUser.vue


+ 78 - 0
src/views/customer/user/components/FavChartStatistic.vue

@@ -0,0 +1,78 @@
+<script setup>
+import {SearchIcon} from 'tdesign-icons-vue-next'
+
+const show = defineModel('show', { type: Boolean, default: false })
+const props=defineProps({
+  data:{
+    type:[null,Object],
+    default:null
+  }
+})
+
+const keyword=ref('')
+
+function handleScroll({scrollBottom}){
+  if(scrollBottom<50){
+
+  }
+}
+
+</script>
+
+<template>
+  <t-dialog
+    v-model:visible="show"
+    header="累计收藏图表"
+    draggable
+    attach="body"
+    top="50px"
+    width="80vw"
+    :cancelBtn="null"
+    :confirmBtn="null"
+    :footer="false"
+    class="fav-chart-wrap"
+  >
+    <t-input placeholder="图表名称" v-model="keyword" clearable>
+      <template #prefixIcon><SearchIcon /></template>
+    </t-input>
+    <t-list class="chart-list" @scroll="handleScroll">
+      <div class="chart-item-box" v-for="item in 26" :key="item">
+        <div class="text-ellipsis--l1 title">图表标题图表标题图表标题图表标题</div>
+        <img class="img" src="" alt="">
+        <div>
+          <span>收藏时间:</span>
+          <span>{{formatTime('2024-10-10','YYYY-MM-DD')}}</span>
+        </div>
+      </div>
+    </t-list>
+  </t-dialog>
+</template>
+
+<style lang="scss" scoped>
+.chart-list{
+  padding-top: 30px;
+  height: 60vh;
+  :global(.t-list__inner){
+    display: flex;
+    flex-wrap: wrap;
+    align-content: flex-start;
+    gap: 20px;
+  }
+  .chart-item-box{
+    border: 1px solid var(--border-color);
+    padding: 10px;
+    border-radius: 4px;
+    width: calc(calc(100% - 60px) / 4);
+    .title{
+      border-bottom: 1px solid var(--border-color);
+      line-height: 2;
+    }
+    .img{
+      width: 100%;
+      height: 230px;
+      display: block;
+      margin: 10px 0;
+    }
+  }
+}
+</style>

+ 0 - 0
src/views/customer/userList/components/MoveUser.vue → src/views/customer/user/components/MoveUser.vue


+ 1 - 1
src/views/etaChart/Index.vue

@@ -122,7 +122,7 @@ function handleSelectChart(item) {
   gap: 0 20px;
   .table-wrap {
     flex-shrink: 0;
-    height: calc(100vh - 120px);
+    height: calc(100vh - 150px);
     .table-box {
       width: 100%;
       border-color: #dcdfe6;

+ 1 - 1
src/views/etaChart/components/ClassifyWrap.vue

@@ -544,7 +544,7 @@ const showBatchMove = ref(false)
   }
   .classify-list-box {
     padding-top: 10px;
-    height: calc(100vh - 300px);
+    height: calc(100vh - 340px);
     overflow-y: auto;
     .classify-item-box {
       flex: 1;