123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- <template>
- <div class="traffic-analysis-page">
- <div class="search-form">
- <!-- 站点选择和时间筛选 -->
- <a-row class="r1 search-form-container" :gutter="8">
- <a-col :xl="7" :xxl="6">
- <div class="choose-site">
- <span class="t1">站点:</span>
- <select-site @set-site-info="changeSite" select-width="100%" />
- </div>
- </a-col>
- <a-col :xl="8" :xxl="6">
- <div class="choose-site">
- <span class="t1">统计时间:</span>
- <a-range-picker @change="onChangeDatePicker" :disabledDate="disabledDate" :value="rangeDate" style="width: 70%" />
- </div>
- </a-col>
- <a-col :xl="9" :xxl="12">
- <a-button-group class="time-btn-group">
- <a-button :class="queryParam.dateType == '' ? 'active' : ''" @click="setTime('')">全部时间</a-button>
- <a-button :class="queryParam.dateType == 'thirtyDay' ? 'active' : ''" @click="setTime('thirtyDay')">近30天</a-button>
- <a-button :class="queryParam.dateType == 'sevenDay' ? 'active' : ''" @click="setTime('sevenDay')">近一周</a-button>
- <a-button :class="queryParam.dateType == 'yesterday' ? 'active' : ''" @click="setTime('yesterday')">昨日</a-button>
- <a-button :class="queryParam.dateType == 'today' ? 'active' : ''" @click="setTime('today')">今日</a-button>
- </a-button-group>
- </a-col>
- </a-row>
- </div>
- <a-spin :spinning="loading" tip="加载中...">
- <a-row class="r2">
- <a-col :span="6">
- <p class="t1">访客数(UV)</p>
- <p class="t3">{{ flowIndexNums.uv.toLocaleString() }}</p>
- </a-col>
- <a-col :span="6">
- <p class="t1">浏览量(PV)</p>
- <p class="t3">{{ flowIndexNums.pv.toLocaleString() }}</p>
- </a-col>
- <a-col :span="6">
- <p class="t1">会话数</p>
- <p class="t3">{{ flowIndexNums.sessions.toLocaleString() }}</p>
- </a-col>
- <a-col :span="6">
- <p class="t1">询盘数</p>
- <p class="t3">{{ flowIndexNums.enquiry.toLocaleString() }}</p>
- </a-col>
- </a-row>
- <a-row>
- <a-col :span="24">
- <a-card style="margin: 10px" title="核心数据">
- <a-row class="r5" :gutter="[20, 20]">
- <a-row class="r5-1">
- <a-col :span="24">
- <area-chart v-if="coreDataChart.x.length > 0" :dataSource="coreDataChart" />
- <a-empty v-else style="float: right; width: 100%; margin-top: 110px" />
- </a-col>
- </a-row>
- </a-row>
- <a-row class="r2">
- <a-col style="width: 20%">
- <p class="t1">日均访问量</p>
- <p class="t3">{{ statistics.averageVisit.toLocaleString() }}</p>
- </a-col>
- <a-col style="width: 20%">
- <p class="t1">平均访问时长</p>
- <p class="t3">{{ statistics.averageVisitDuration }}</p>
- </a-col>
- <a-col style="width: 20%">
- <p class="t1">访客平均访问页面数</p>
- <p class="t3">{{ statistics.averageVisitPage.toLocaleString() }}</p>
- </a-col>
- <a-col style="width: 20%">
- <p class="t1">跳出率</p>
- <p class="t3">{{ statistics.bounceRate }}</p>
- </a-col>
- <a-col style="width: 20%">
- <p class="t1">UV到询盘转化率</p>
- <p class="t3">{{ statistics.conversionRate }}</p>
- </a-col>
- </a-row>
- </a-card>
- </a-col>
- <a-col :span="24">
- <a-card style="margin: 10px" title="访客数地域分布">
- <a-row class="r5">
- <a-col :span="18">
- <map-adweb v-if="countryMapData.length > 0" :dataSource="countryMapData" height="400" />
- <a-empty v-else style="margin-top: 50px" />
- </a-col>
- <a-col :span="6">
- <a-table
- :rowKey="
- (record, index) => {
- return index;
- }
- "
- class="chartTable"
- :scroll="{ y: 500 }"
- :pagination="false"
- :columns="chartDetailDataCol"
- :data-source="chartDetailData"
- :showHeader="false"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'flagSlot'">
- <span class="img-box">
- <span :class="'flag-icon flag-icon-' + record.countryCode"></span>
- </span>
- </template>
- <template v-if="column.key === 'numSlot'"> {{ record.totalUsers }} | {{ record.totalUsersProportion }} </template>
- </template>
- </a-table>
- </a-col>
- </a-row>
- </a-card>
- </a-col>
- <a-col :span="12">
- <a-card style="margin: 10px" title="访客数设备分布">
- <a-row class="r5" :gutter="[20, 20]">
- <a-col :span="24">
- <DeviceStatsUV :deviceStats="deviceStats" />
- </a-col>
- </a-row>
- </a-card>
- </a-col>
- <a-col :span="12">
- <a-card style="margin: 10px" title="浏览量设备分布">
- <a-row class="r5" :gutter="[20, 20]">
- <a-col :span="24">
- <DeviceStatsPV :deviceStats="deviceStats" />
- </a-col>
- </a-row>
- </a-card>
- </a-col>
- <!-- 来源媒介 -->
- <a-col :span="24">
- <a-card style="margin: 10px" title="来源媒介">
- <a-row class="r5" :gutter="[20, 20]">
- <a-col :span="24">
- <a-table :columns="mediaListColumns" :data-source="mediaDatasource" size="middle" rowKey="type" :pagination="false">
- <template #filterDropdown>
- <div style="padding: 10px">
- affiliate:通过联属营销计划点击链接的用户<br />
- cpc:(每次点击费用的缩写)点击付费广告的用户<br />
- organic:点击搜索引擎中的链接的用户<br />
- referral:点击网站上的链接(例如,视频说明中的链接)的用户<br />
- (none):直接流量
- </div>
- </template>
- <template #filterIcon>
- <a-icon type="question-circle" :style="{ fontSize: '16px', color: '#108ee9' }" />
- </template>
- <template #bodyCell="{ column, record, index, text }">
- <template v-if="column.key === 'typeSlotFirst'">
- {{ record.type.split('/')[0] }}
- </template>
- <template v-if="column.key === 'typeSlotLast'">
- <a-popover>
- <template #content>
- <template v-if="record.type.split('/')[1] === ' affiliate'"> 通过联属营销计划点击链接的用户 </template>
- <template v-if="record.type.split('/')[1] === ' cpc'"> (每次点击费用的缩写)点击付费广告的用户 </template>
- <template v-if="record.type.split('/')[1] === ' organic'"> 点击搜索引擎中的链接的用户 </template>
- <template v-if="record.type.split('/')[1] === ' referral'"> 点击网站上的链接(例如,视频说明中的链接)的用户 </template>
- <template v-if="record.type.split('/')[1] === ' (none)'"> 直接流量 </template>
- </template>
- {{ record.type.split('/')[1] }}
- </a-popover>
- </template>
- <template v-if="column.key === 'avgSessionDurationSlot'">
- <span style="margin-left: 30px">{{ record.avgSessionDuration }} s</span>
- </template>
- </template>
- </a-table>
- </a-col>
- </a-row>
- </a-card>
- </a-col>
- <!-- 最多访问TOP10 -->
- <a-col :span="24">
- <a-card style="margin: 10px" title="最多访问TOP10">
- <a-row class="r5" :gutter="[20, 20]">
- <a-col :span="24">
- <a-table :columns="mostAccessColumns" :data-source="mostAccessDatasource" size="middle" rowKey="type" :pagination="false">
- <template #bodyCell="{ column, record, index, text }">
- <template v-if="column.key === 'pagePathSlot'">
- <a-popover>
- <template #content>
- {{ text }}
- </template>
- <a :href="text" target="_blank">
- <div style="width: 700px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
- {{ text }}
- </div>
- </a>
- </a-popover>
- </template>
- <template v-if="column.key === 'centerSlot'">
- <span style="margin-left: 20px">{{ text }}</span>
- </template>
- <template v-if="column.key === 'avgTimeOnPageSlot'">
- <span style="margin-left: 30px">{{ text }} s</span>
- </template>
- </template>
- </a-table>
- </a-col>
- </a-row>
- </a-card>
- </a-col>
- </a-row>
- </a-spin>
- </div>
- </template>
- <script lang="ts" name="data-trafficAnalysis" setup>
- import selectSite from '@/components/Adweb/selectSite.vue';
- import areaChart from './chart/areaChart.vue';
- import { computed, reactive, ref } from 'vue';
- import { getAction } from '@/api/manage/manage';
- import MapAdweb from '@/components/chart/mapAdweb.vue';
- import DeviceStatsPV from './components/DeviceStatsPV.vue';
- import DeviceStatsUV from './components/DeviceStatsUV.vue';
- import 'flag-icon-css/css/flag-icons.css';
- import dayjs from 'dayjs';
- const queryParam = reactive<any>({});
- queryParam.limit = 10;
- queryParam.siteCode = localStorage.getItem('siteCode');
- const loading = ref(false);
- const chartDetailDataCol = ref([
- {
- title: '国旗',
- align: 'center',
- key: 'flagSlot',
- width: 30,
- scopedSlots: { customRender: 'flagSlot' },
- },
- {
- title: '国家',
- align: 'left',
- dataIndex: 'countryName',
- customRender: function (text, record) {
- return text === null ? record.country : text;
- },
- },
- {
- title: '数量',
- align: 'right',
- key: 'numSlot',
- scopedSlots: { customRender: 'numSlot' },
- },
- ]);
- // 来源媒介列表
- const mediaListColumns = ref([
- {
- title: '来源',
- key: 'typeSlotFirst',
- scopedSlots: {
- customRender: 'typeSlotFirst',
- },
- },
- {
- title: '媒介',
- key: 'typeSlotLast',
- scopedSlots: {
- filterDropdown: 'filterDropdown',
- filterIcon: 'filterIcon',
- customRender: 'typeSlotLast',
- },
- },
- {
- title: '访客数(UV)',
- dataIndex: 'totalUsers',
- },
- {
- title: '占比',
- dataIndex: 'totalUsersProportion',
- },
- {
- title: '新访客数',
- dataIndex: 'newUsers',
- },
- {
- title: '新客占比',
- dataIndex: 'newUsersRatio',
- },
- {
- title: '浏览量(PV)',
- dataIndex: 'pageViews',
- },
- {
- title: '平均访问页面数',
- dataIndex: 'pageViewsPerSession',
- },
- {
- title: '会话数',
- dataIndex: 'sessions',
- },
- {
- title: '平均会话时长',
- key: 'avgSessionDurationSlot',
- sortDirections: ['descend', 'ascend'],
- sorter: (a, b) => a.avgSessionDuration - b.avgSessionDuration,
- scopedSlots: {
- customRender: 'avgSessionDurationSlot',
- },
- },
- ]);
- // 最多访问TOP10列表
- const mostAccessColumns = ref([
- {
- title: '来源',
- dataIndex: 'pagePath',
- scopedSlots: {
- customRender: 'pagePathSlot',
- },
- },
- {
- title: '浏览量(PV)',
- dataIndex: 'pageViews',
- defaultSortOrder: 'descend',
- sorter: (a, b) => a.pageViews - b.pageViews,
- width: 160,
- scopedSlots: {
- customRender: 'centerSlot',
- },
- },
- {
- title: '浏览量占比',
- dataIndex: 'pvProportion',
- width: 160,
- scopedSlots: {
- customRender: 'centerSlot',
- },
- },
- // {
- // title: '平均页面停留时间',
- // dataIndex: 'avgTimeOnPage',
- // sortDirections: ['descend', 'ascend'],
- // width: 160,
- // scopedSlots: {
- // customRender: 'avgTimeOnPageSlot',
- // }
- // },
- ]);
- function changeSite(selectedSiteInfo: any) {
- queryParam.siteCode = selectedSiteInfo.code;
- localStorage.setItem('siteCode', queryParam.siteCode);
- reloadData();
- }
- //重新刷新页面数据
- function reloadData() {
- loading.value = true;
- getFlowIndexNumber();
- getCountryMapData();
- getMediaList();
- getMostAccessList();
- getDeviceStats();
- }
- const flowIndexNums = ref({
- uv: 0,
- pv: 0,
- sessions: 0,
- enquiry: 0,
- });
- const coreDataChart = ref({
- x: [],
- uv: [],
- pv: [],
- enquiry: [],
- });
- const statistics = ref({
- averageVisit: 0,
- averageVisitDuration: 0,
- averageVisitPage: 0,
- bounceRate: '0%',
- conversionRate: '0%',
- });
- //访客量、浏览量、询盘数量、折线图以及统计
- const getFlowIndexNumber = async () => {
- try {
- const res = await getAction('/dmp-data/site-overview/stats', queryParam);
- if (!res.result) {
- flowIndexNums.value = {
- uv: 0,
- pv: 0,
- sessions: 0,
- enquiry: 0,
- };
- coreDataChart.value = {
- x: [],
- uv: [],
- pv: [],
- enquiry: [],
- };
- statistics.value = {
- averageVisit: 0,
- averageVisitDuration: 0,
- averageVisitPage: 0,
- bounceRate: '0%',
- conversionRate: '0%',
- };
- loading.value = false;
- return;
- }
- flowIndexNums.value.uv = res.result.totalUsers;
- flowIndexNums.value.pv = res.result.pageViews;
- flowIndexNums.value.sessions = res.result.sessions;
- flowIndexNums.value.enquiry = res.result.enquires;
- const r = res.result.dailyStats;
- const x = [],
- pv = [],
- uv = [],
- enquiry = [];
- if (r != null && r.length > 0) {
- for (let item of r) {
- x.push(item.date);
- pv.push(item.pageViews);
- uv.push(item.totalUsers);
- enquiry.push(item.enquires);
- }
- }
- coreDataChart.value.x = x;
- coreDataChart.value.pv = pv;
- coreDataChart.value.uv = uv;
- coreDataChart.value.enquiry = enquiry;
- statistics.value.averageVisit = res.result.dailyTotalUsers;
- statistics.value.averageVisitDuration = res.result.avgTimeOnPage;
- statistics.value.averageVisitPage = res.result.pageViewsPerSession;
- statistics.value.bounceRate = res.result.bounceRate;
- statistics.value.conversionRate = res.result.enquiryConversionRate;
- loading.value = false;
- } catch (error) {
- console.error(error);
- }
- };
- const chartDetailData = ref([]);
- const countryMapData = ref([]);
- //访客数地域分布
- const getCountryMapData = async () => {
- try {
- const res = await getAction('/dmp-data/country/stats', queryParam);
- if (res.code === 200) {
- chartDetailData.value = res.result;
- countryMapData.value = chartDetailData.value.map((entry) => ({
- name: entry.countryName,
- value: entry.totalUsers,
- }));
- }
- } catch (error) {
- console.error(error);
- }
- };
- const mediaDatasource = ref([]);
- const deviceStats = ref([]); // 设备统计数据
- // 新增获取设备统计数据的函数
- const getDeviceStats = async () => {
- try {
- const res = await getAction('/dmp-data/device/stats', queryParam);
- if (res.code === 200) {
- deviceStats.value = res.result; // Update the deviceStats with the response
- } else {
- deviceStats.value = []; // Reset if there's an error
- }
- } catch (error) {
- console.error(error);
- }
- };
- //来源媒介列表、最多访问top10列表
- const getMediaList = async () => {
- try {
- const res = await getAction('/dmp-data/source-medium/stats', queryParam);
- if (res.code == 200) {
- mediaDatasource.value = res.result;
- } else {
- mediaDatasource.value = [];
- }
- } catch (error) {
- console.error(error);
- }
- };
- const mostAccessDatasource = ref([]);
- //
- const getMostAccessList = async () => {
- try {
- const res = await getAction('/dmp-data/page-path/stats', queryParam);
- if (res.code == 200) {
- mostAccessDatasource.value = res.result;
- } else {
- mostAccessDatasource.value = [];
- }
- } catch (error) {
- console.error(error);
- }
- };
- const rangeDate = ref([]);
- const onChangeDatePicker = (date, dateString) => {
- if (dateString.length > 0) {
- rangeDate.value = date;
- queryParam.start = dateString[0];
- queryParam.end = dateString[1];
- queryParam.dateType = undefined;
- reloadData();
- }
- };
- //日期选择只能今天之前
- function disabledDate(current) {
- return current && current > dayjs();
- }
- const setTime = (time) => {
- queryParam.dateType = time;
- queryParam.start = '';
- queryParam.end = '';
- if (time == '') {
- rangeDate.value = undefined;
- } else if (time == 'sevenDay') {
- rangeDate.value = [dayjs().add(-7, 'd'), dayjs().add(-1, 'd')];
- } else if (time == 'thirtyDay') {
- rangeDate.value = [dayjs().add(-30, 'd'), dayjs().add(-1, 'd')];
- } else if (time == 'yesterday') {
- rangeDate.value = [dayjs().add(-1, 'd'), dayjs().add(-1, 'd')];
- } else if (time == 'today') {
- rangeDate.value = [dayjs(), dayjs()];
- }
- reloadData();
- };
- </script>
- <style lang="less" scoped>
- .self-pop {
- .ant-popover-inner-content {
- background: rgb(245, 243, 254);
- p {
- font-size: 13px;
- }
- }
- .ant-popover-arrow {
- border-color: rgb(245, 243, 254) !important;
- }
- }
- .img-box {
- width: 22px;
- height: 15px;
- display: flex;
- justify-content: center;
- align-items: center;
- img {
- width: 100%;
- height: 100%;
- }
- }
- .ant-alert {
- /deep/ .ant-btn {
- border-radius: 0;
- margin-left: 10px;
- }
- }
- .theme-color {
- color: @primary-color;
- }
- .r1 {
- margin: 20px;
- .choose-site {
- display: flex;
- }
- .t1 {
- font-size: 18px;
- font-weight: 400;
- letter-spacing: 0px;
- line-height: 32px;
- margin-left: 10px;
- }
- .ant-form-item {
- flex: 1;
- }
- .ant-calendar-picker {
- margin-right: 20px;
- }
- /deep/ .ant-btn {
- background: transparent;
- margin-right: 10px;
- padding: 4px 15px;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- transition: all 0.3s;
- &:hover {
- color: @primary-color;
- border-color: @primary-color;
- }
- &.active {
- color: @primary-color;
- background: #e6f7ff;
- border-color: @primary-color;
- }
- }
- .time-btn-group {
- /deep/ .ant-btn {
- background: #fff;
- padding: 4px 15px;
- border: 1px solid #d9d9d9;
- transition: all 0.3s;
- margin-right: 0;
- &:first-child {
- border-top-left-radius: 4px;
- border-bottom-left-radius: 4px;
- }
- &:last-child {
- border-top-right-radius: 4px;
- border-bottom-right-radius: 4px;
- }
- &:not(:first-child) {
- margin-left: -1px;
- }
- &:hover {
- color: @primary-color;
- border-color: @primary-color;
- position: relative;
- z-index: 1;
- background: #fff;
- }
- &.active {
- color: @primary-color;
- background: #e6f7ff;
- border-color: @primary-color;
- position: relative;
- z-index: 2;
- }
- }
- }
- }
- .r2 {
- background: #fff;
- padding: 30px 20px;
- margin: 0 10px 10px 10px;
- .ant-col:not(:last-child) {
- border-right: 1px solid #e6e6e6;
- }
- p {
- margin: 0;
- text-align: center;
- &.t1 {
- color: #333;
- margin-bottom: 15px;
- img {
- margin-right: 10px;
- width: 15px;
- margin-top: -5px;
- }
- }
- &.t2 {
- color: @primary-color;
- font-size: 30px;
- font-weight: 500;
- line-height: 1;
- padding-left: 25px;
- }
- &.t3 {
- font-size: 32px;
- font-weight: 700;
- letter-spacing: 0px;
- line-height: 38px;
- color: rgba(13, 62, 122, 1);
- }
- }
- }
- .r5 {
- background: #fff;
- padding: 10px;
- border-radius: 10px;
- margin: 0 !important;
- .wrap {
- box-shadow: 0px 2px 4px 0px @primary-color;
- padding: 15px;
- border-radius: 10px;
- overflow: hidden;
- background: #fff;
- transition: all 0.3s;
- &.blue {
- box-shadow: 0px 2px 4px 0px @primary-color;
- }
- &.effect:hover {
- box-shadow: none;
- background: rgb(241, 248, 255);
- }
- img {
- width: 15px;
- }
- .fr {
- float: right;
- width: calc(100% - 15px);
- text-align: center;
- p:last-child {
- font-size: 30px;
- text-align: center;
- margin-top: 10px;
- }
- }
- }
- /deep/ .ant-table-thead > tr > th {
- background: rgb(241, 248, 255);
- border: none;
- color: #000;
- padding: 10px;
- }
- /deep/ .ant-table-tbody .ant-table-row td {
- padding: 10px;
- color: #000;
- }
- .r5-1 {
- display: inline-block;
- width: 100%;
- margin-top: 30px;
- .fl {
- float: left;
- position: relative;
- .ant-btn {
- border-radius: 0;
- border: none;
- margin-right: 10px;
- }
- }
- .fr {
- float: right;
- line-height: 2;
- span {
- margin-right: 30px;
- i {
- display: inline-block;
- width: 25px;
- height: 3px;
- background: #544beb;
- position: relative;
- top: -4px;
- margin-right: 20px;
- }
- &:last-child i {
- background: #f0b358;
- }
- }
- }
- }
- .box {
- border-radius: 10px;
- text-align: center;
- min-height: 180px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- p {
- color: #fff;
- img {
- width: 19px;
- margin: -5px 10px 0 0;
- }
- }
- .num {
- font-size: 30px;
- margin-bottom: 10px;
- }
- &.b1 {
- background: rgb(233, 107, 95);
- }
- &.b2 {
- background: rgb(88, 204, 168);
- }
- &.b3 {
- background: rgb(124, 152, 252);
- }
- &.b4 {
- background: #f0b358;
- }
- }
- }
- </style>
|