| | |
| | | "core-js": "^3.6.5", |
| | | "cos-js-sdk-v5": "^1.10.1", |
| | | "dplayer": "^1.26.0", |
| | | "echarts": "^5.6.0", |
| | | "js-cookie": "^2.2.1", |
| | | "node-sass": "^4.14.1", |
| | | "price-color": "1.0.2", |
| | |
| | | method: 'GET' |
| | | }) |
| | | } |
| | | |
| | | export const getFootVideoPage =(params) =>{ |
| | | return service({ |
| | | url:'/customerManager/videoFootPage', |
| | | method: 'GET', |
| | | params:params |
| | | }) |
| | | } |
| | | |
| | | export const memberActionAnalyse = (params) =>{ |
| | | return service({ |
| | | url:'/customerManager/memberActionAnalyse/'+params, |
| | | method:'GET', |
| | | }) |
| | | } |
| | | |
New file |
| | |
| | | <template> |
| | | <div class="watch-history-simple"> |
| | | <!-- 统计卡片 --> |
| | | <Row :gutter="16" class="stats-row"> |
| | | <Col :span="12"> |
| | | <Card dis-hover> |
| | | <p slot="title">总观看时长</p> |
| | | <h1 style="color: #2d8cf0;">{{ stats.totalDuration || '0'}} 小时</h1> |
| | | </Card> |
| | | </Col> |
| | | <Col :span="12"> |
| | | <Card dis-hover> |
| | | <p slot="title">平均完成率</p> |
| | | <h1 style="color: #19be6b;">{{ stats.avgProgress || 0}}%</h1> |
| | | </Card> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <!-- 数据表格 --> |
| | | <Card dis-hover class="table-card"> |
| | | <Table |
| | | :columns="columns" |
| | | :data="tableData" |
| | | :loading="loading" |
| | | @on-row-click="handleRowClick" |
| | | style="height: 600px" |
| | | > |
| | | <template slot-scope="{ row }" slot="video"> |
| | | <div class="video-cell"> |
| | | <img |
| | | :src="row.coverCOSUrl" |
| | | class="video-cover" |
| | | @click.stop="previewImage(row.coverCOSUrl)" |
| | | alt=""> |
| | | <span class="video-title">{{ row.title }}</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <template slot-scope="{ row }" slot="progress"> |
| | | <Tooltip :content="getPercent(row)" style="width: 100%"> |
| | | <Progress :percent="getPercent(row)" |
| | | :status="getPercent(row) >= 100 ? 'success' : 'active'" |
| | | /> |
| | | </Tooltip> |
| | | </template> |
| | | |
| | | <!-- <template slot-scope="{ row }" slot="action" >--> |
| | | <!-- <Button--> |
| | | <!-- type="primary"--> |
| | | <!-- size="small"--> |
| | | <!-- @click.stop="showDetail(row)"--> |
| | | <!-- >--> |
| | | <!-- 详情--> |
| | | <!-- </Button>--> |
| | | <!-- </template>--> |
| | | </Table> |
| | | |
| | | <!-- 分页 --> |
| | | <Page |
| | | :current="searchForm.pageNumber" |
| | | :total="total" |
| | | :page-size="searchForm.pageSize" |
| | | @on-change="handlePageChange" |
| | | show-total |
| | | class="pagination" |
| | | /> |
| | | </Card> |
| | | |
| | | <!-- 图片预览 --> |
| | | <Modal |
| | | v-model="previewVisible" |
| | | title="视频封面" |
| | | footer-hide |
| | | width="60%" |
| | | > |
| | | <img :src="previewImageUrl" class="preview-image" alt=""> |
| | | </Modal> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { getFootVideoPage } from '@/api/customer.js' |
| | | import { Progress, Tooltip } from 'view-design'; |
| | | export default { |
| | | props: ['memberId'], |
| | | watch: { |
| | | memberId(newVal) { |
| | | if (newVal) { |
| | | this.loadData(); |
| | | } |
| | | } |
| | | }, |
| | | components: { |
| | | Progress, |
| | | Tooltip, |
| | | }, |
| | | data() { |
| | | |
| | | return { |
| | | stats: { |
| | | totalDuration: '', |
| | | avgProgress: 0 |
| | | }, |
| | | loading: false, |
| | | tableData: [], |
| | | columns: [ |
| | | { |
| | | title: '视频内容', |
| | | slot: 'video', |
| | | minWidth: 300 |
| | | }, |
| | | { |
| | | title: '观看时间', |
| | | key: 'viewDuration', |
| | | width: 180, |
| | | render: (h, { row }) => { |
| | | return h('span', row.viewDuration /1000 +"秒") |
| | | } |
| | | }, |
| | | { |
| | | title: '观看进度', |
| | | slot: 'progress', |
| | | width: 150 |
| | | }, |
| | | // { |
| | | // title: '操作', |
| | | // slot: 'action', |
| | | // minWidth: 120, // 改为最小宽度 |
| | | // align: 'center' |
| | | // } |
| | | ], |
| | | searchForm:{ |
| | | memberId: '', //id |
| | | pageNumber: 1, // 当前页数 |
| | | pageSize: 10, // 页面大小 |
| | | }, |
| | | total:0, |
| | | previewVisible: false, |
| | | previewImageUrl: '', |
| | | } |
| | | }, |
| | | methods: { |
| | | async loadData() { |
| | | this.loading = true |
| | | this.searchForm.memberId = this.memberId; |
| | | getFootVideoPage(this.searchForm).then(res =>{ |
| | | this.loading = false; |
| | | if (res.code === 200){ |
| | | this.tableData = res.data.data; |
| | | this.total = res.data.total; |
| | | this.stats.avgProgress = res.data.avgProgress; |
| | | this.stats.totalDuration = (res.data.totalDuration / 1000/ 60/60).toFixed(2); |
| | | }else { |
| | | this.$Message.error(res.msg) |
| | | } |
| | | }) |
| | | |
| | | }, |
| | | handlePageChange(page) { |
| | | this.searchForm.pageNumber = page |
| | | this.loadData() |
| | | }, |
| | | handleRowClick(row) { |
| | | console.log('Row clicked:', row) |
| | | }, |
| | | showDetail(row) { |
| | | this.$Modal.info({ |
| | | title: '观看详情', |
| | | content: ` |
| | | <p><strong>视频ID:</strong>${row.id}</p> |
| | | <p><strong>完整进度:</strong>${row.progress}%</p> |
| | | <p><strong>设备信息:</strong>${row.device}</p> |
| | | `, |
| | | width: 500 |
| | | }) |
| | | }, |
| | | previewImage(url) { |
| | | this.previewImageUrl = url |
| | | this.previewVisible = true |
| | | }, |
| | | getPercent(row) { |
| | | return Number((row.playProgress * 100).toFixed(0)); |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .watch-history-simple { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .stats-row { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .table-card { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .video-cell { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .video-cover { |
| | | width: 80px; |
| | | height: 45px; |
| | | object-fit: cover; |
| | | margin-right: 10px; |
| | | cursor: pointer; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .video-title { |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .pagination { |
| | | margin-top: 16px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .preview-image { |
| | | width: 100%; |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | |
| | | <Modal |
| | | v-model="showCustomerInfo" |
| | | :title="modelTitle" |
| | | width="700" |
| | | width="850" |
| | | :mask-closable="false" |
| | | > |
| | | |
| | | <!-- 主内容区 --> |
| | | <Tabs v-model="activeTab" @on-click="handleTabChange"> |
| | | <TabPane label="基础资料" name="basic"> |
| | | <div class="customer-detail"> |
| | | <div class="avatar-section"> |
| | | <Avatar :src="customerInfo.face" size="large" /> |
| | |
| | | </div> |
| | | |
| | | <Divider /> |
| | | |
| | | <div class="detail-grid"> |
| | | <div class="detail-row"> |
| | | <span class="detail-label">性别:</span> |
| | |
| | | <span class="detail-value">{{ customerInfo.lastLoginDate }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="customerInfo.customerTagList && customerInfo.customerTagList.length > 0" class="tags-section"> |
| | | <h4>用户标签</h4> |
| | | <div> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </TabPane> |
| | | <TabPane label="视频浏览历史" name="history"> |
| | | <watch-history |
| | | ref="watchHistoryRef" |
| | | :memberId="customerInfo.id" |
| | | key="history"/> |
| | | </TabPane> |
| | | <TabPane label="行为分析" name="actionAnalyse" > |
| | | <div class="chart-container"> |
| | | <div ref="chart" class="chart-content"></div> |
| | | </div> |
| | | </TabPane> |
| | | </Tabs> |
| | | |
| | | |
| | | <div slot="footer"> |
| | | <Button type="primary" @click="showCustomerInfo = false">关闭</Button> |
| | |
| | | |
| | | <script> |
| | | import JsonExcel from "vue-json-excel"; |
| | | import {getCustomerList,addCustomerTag,saveCustomerTagById,getTagList,getStoreSelectOptions,getCustomerInfo} from "@/api/customer"; |
| | | import {memberActionAnalyse,getCustomerList,addCustomerTag,saveCustomerTagById,getTagList,getStoreSelectOptions,getCustomerInfo} from "@/api/customer"; |
| | | import {addCustomerBlackByPC} from "@/api/customer-black.js" |
| | | import WatchHistory from "./WatchHistory.vue"; |
| | | import * as echarts from 'echarts'; |
| | | |
| | | export default { |
| | | name:"customer", |
| | | components:{ |
| | | "download-excel": JsonExcel, |
| | | WatchHistory, |
| | | "download-excel": JsonExcel |
| | | |
| | | }, |
| | | |
| | | data(){ |
| | | return{ |
| | | myChart: null, |
| | | activeTab: 'basic', // 当前激活的标签页 |
| | | userId: '', // 当前查看的用户ID |
| | | |
| | | customerInfo: { |
| | | birthday: null, |
| | | blackId: null, |
| | |
| | | blackParam:{ |
| | | storeId:'', |
| | | userId:'', |
| | | } |
| | | }, |
| | | |
| | | |
| | | |
| | | } |
| | | }, |
| | | |
| | | mounted(){ |
| | | this.init(); |
| | | }, |
| | | methods:{ |
| | | showLoading() { |
| | | if (this.myChart) { |
| | | this.myChart.showLoading({ |
| | | text: '数据加载中...', |
| | | color: '#1890ff', |
| | | textColor: '#333', |
| | | maskColor: 'rgba(255, 255, 255, 0.8)', |
| | | zlevel: 0, |
| | | fontSize: 14, |
| | | showSpinner: true, |
| | | spinnerRadius: 10, |
| | | lineWidth: 2, |
| | | fontWeight: 'normal' |
| | | }); |
| | | } |
| | | }, |
| | | |
| | | hideLoading() { |
| | | if (this.myChart) { |
| | | this.myChart.hideLoading(); |
| | | } |
| | | }, |
| | | getEmptyOption() { |
| | | return { |
| | | title: { |
| | | text: '暂无数据', |
| | | left: 'center', |
| | | top: 'center', |
| | | textStyle: { |
| | | color: '#999', |
| | | fontWeight: 'normal', |
| | | fontSize: 16 |
| | | } |
| | | }, |
| | | series: [] |
| | | }; |
| | | }, |
| | | initEcharts(id){ |
| | | // 销毁旧实例 |
| | | if (this.myChart) { |
| | | this.myChart.dispose(); |
| | | } |
| | | |
| | | this.myChart = echarts.init(this.$refs.chart); |
| | | this.showLoading() |
| | | memberActionAnalyse(id).then(res =>{ |
| | | let option; |
| | | this.hideLoading() |
| | | if (res.code === 200){ |
| | | const chartData = Object.entries(res.data).map(([name, value]) => ({ |
| | | value, |
| | | name |
| | | })); |
| | | console.log(chartData) |
| | | if (chartData.length ===0){ |
| | | option = this.getEmptyOption(); |
| | | }else { |
| | | option = { |
| | | title: { |
| | | text: '用户行为分析', |
| | | left: 'center' |
| | | }, |
| | | |
| | | tooltip: { |
| | | trigger: 'item' |
| | | }, |
| | | series: [{ |
| | | radius: ['40%', '70%'], |
| | | type: 'pie', |
| | | data: chartData, |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: 'rgba(0, 0, 0, 0.5)' |
| | | } |
| | | } |
| | | }] |
| | | }; |
| | | } |
| | | |
| | | this.myChart.setOption(option); |
| | | } |
| | | }) |
| | | }, |
| | | // 标签页切换处理 |
| | | handleTabChange(tabName) { |
| | | this.activeTab = tabName; |
| | | }, |
| | | |
| | | // 获得商家下拉框数据 |
| | | getStoreSelectOptions(){ |
| | | this.storeSelectLoading = true; |
| | |
| | | // 获得客户标签下拉框数据 |
| | | getCustomerTagSelectOptions(){ |
| | | this.selectLoading = true; |
| | | getTagList().then(res =>{ |
| | | this.selectLoading = false; |
| | | if (res.code === 200){ |
| | | this.tagList = res.data; |
| | | } |
| | | }) |
| | | // getTagList().then(res =>{ |
| | | // this.selectLoading = false; |
| | | // if (res.code === 200){ |
| | | // this.tagList = res.data; |
| | | // } |
| | | // }) |
| | | }, |
| | | init(){ |
| | | this.getCustomerList(); |
| | |
| | | this.selectList = e.map(d => d.id); |
| | | this.selectCount = e.length; |
| | | }, |
| | | memberActionAnalyse(id){ |
| | | |
| | | }, |
| | | //查看详情 |
| | | openInfo(row){ |
| | | this.showCustomerInfo = true; |
| | | this.modelTitle = "用户详情" |
| | | |
| | | this.activeTab = "basic" |
| | | |
| | | getCustomerInfo(row.id).then(res =>{ |
| | | if(res.code === 200){ |
| | | this.customerInfo = { |
| | |
| | | }; |
| | | } |
| | | }) |
| | | |
| | | this.initEcharts(row.id); |
| | | }, |
| | | // 编辑标签 |
| | | openEdit(row){ |
| | |
| | | |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | .chart-container { |
| | | width: 100%; |
| | | height: 100%; /* 改为100%填充父容器 */ |
| | | min-height: 400px; /* 最小高度保障 */ |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | position: relative; |
| | | margin: auto; /* 新增 */ |
| | | } |
| | | .chart-content { |
| | | width: 400px; |
| | | height: 400px; |
| | | margin: 0 auto; |
| | | } |
| | | .customer-detail { |
| | | padding: 16px; |
| | | } |