|
@@ -12,17 +12,49 @@
|
|
|
<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%" />
|
|
|
+ <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 == '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-button
|
|
|
+ type="primary"
|
|
|
+ @click="exportToPDF"
|
|
|
+ style="
|
|
|
+ background: linear-gradient(135deg, #1890ff, #40a9ff);
|
|
|
+ border-color: #1890ff;
|
|
|
+ box-shadow: 0 2px 0 rgba(24, 144, 255, 0.1);
|
|
|
+ margin-left: 10px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ "
|
|
|
+ :style="{
|
|
|
+ '&:hover': {
|
|
|
+ background: 'linear-gradient(135deg, #40a9ff, #69c0ff)',
|
|
|
+ borderColor: '#40a9ff',
|
|
|
+ boxShadow: '0 2px 0 rgba(64, 169, 255, 0.1)'
|
|
|
+ },
|
|
|
+ '&:active': {
|
|
|
+ background: 'linear-gradient(135deg, #096dd9, #1890ff)',
|
|
|
+ borderColor: '#096dd9'
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #icon>
|
|
|
+ <DownloadOutlined />
|
|
|
+ </template>
|
|
|
+ 导出PDF
|
|
|
+ </a-button>
|
|
|
</a-col>
|
|
|
</a-row>
|
|
|
</div>
|
|
@@ -60,15 +92,10 @@
|
|
|
<a-col :span="24">
|
|
|
<p class="t1">优化得分</p>
|
|
|
<div class="score-circle" style="padding-bottom: 10px">
|
|
|
- <a-progress
|
|
|
- type="circle"
|
|
|
- :percent="customerStats.optiScore.toFixed(2)"
|
|
|
- :width="80"
|
|
|
- :stroke-color="{
|
|
|
- '0%': '#FFB800',
|
|
|
- '100%': '#FFC53D',
|
|
|
- }"
|
|
|
- />
|
|
|
+ <a-progress type="circle" :percent="customerStats.optiScore.toFixed(2)" :width="80" :stroke-color="{
|
|
|
+ '0%': '#FFB800',
|
|
|
+ '100%': '#FFC53D',
|
|
|
+ }" />
|
|
|
</div>
|
|
|
</a-col>
|
|
|
</a-row>
|
|
@@ -84,10 +111,14 @@
|
|
|
<template v-if="dailyStats.values && dailyStats.values.length > 0">
|
|
|
<div class="fl">
|
|
|
<a-button-group>
|
|
|
- <a-button :class="{ active: activeChart === 'impression' }" @click="switchChart('impression')">展示</a-button>
|
|
|
- <a-button :class="{ active: activeChart === 'clicks' }" @click="switchChart('clicks')">点击</a-button>
|
|
|
- <a-button :class="{ active: activeChart === 'ctr' }" @click="switchChart('ctr')">点击率(%)</a-button>
|
|
|
- <a-button :class="{ active: activeChart === 'conversion' }" @click="switchChart('conversion')">转化数</a-button>
|
|
|
+ <a-button :class="{ active: activeChart === 'impression' }"
|
|
|
+ @click="switchChart('impression')">展示</a-button>
|
|
|
+ <a-button :class="{ active: activeChart === 'clicks' }"
|
|
|
+ @click="switchChart('clicks')">点击</a-button>
|
|
|
+ <a-button :class="{ active: activeChart === 'ctr' }"
|
|
|
+ @click="switchChart('ctr')">点击率(%)</a-button>
|
|
|
+ <a-button :class="{ active: activeChart === 'conversion' }"
|
|
|
+ @click="switchChart('conversion')">转化数</a-button>
|
|
|
<a-button :class="{ active: activeChart === 'cost' }" @click="switchChart('cost')">花费</a-button>
|
|
|
</a-button-group>
|
|
|
</div>
|
|
@@ -106,58 +137,37 @@
|
|
|
<a-row>
|
|
|
<a-col :span="24">
|
|
|
<a-card style="margin: 10px" title="广告系列">
|
|
|
- <a-table
|
|
|
- :columns="campaignColumns"
|
|
|
- :data-source="tableData"
|
|
|
- :loading="loading"
|
|
|
- :pagination="{
|
|
|
- pageSize: 10,
|
|
|
- showSizeChanger: false,
|
|
|
- showQuickJumper: true,
|
|
|
- showTotal: (total) => `共 ${total} 条`,
|
|
|
- }"
|
|
|
- bordered
|
|
|
- :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : '')"
|
|
|
- style="width: 100%"
|
|
|
- />
|
|
|
+ <a-table :columns="campaignColumns" :data-source="tableData" :loading="loading" :pagination="{
|
|
|
+ pageSize: 10,
|
|
|
+ showSizeChanger: false,
|
|
|
+ showQuickJumper: true,
|
|
|
+ showTotal: (total) => `共 ${total} 条`,
|
|
|
+ }" bordered :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : '')"
|
|
|
+ style="width: 100%" />
|
|
|
</a-card>
|
|
|
</a-col>
|
|
|
</a-row>
|
|
|
<a-row :gutter="8">
|
|
|
<a-col :span="12">
|
|
|
<a-card style="margin: 10px" title="TOP关键词">
|
|
|
- <a-table
|
|
|
- :columns="keywordColumns"
|
|
|
- :data-source="keywordData"
|
|
|
- :loading="loading"
|
|
|
- :pagination="{
|
|
|
- pageSize: 10,
|
|
|
- showSizeChanger: false,
|
|
|
- showQuickJumper: true,
|
|
|
- showTotal: (total) => `共 ${total} 条`,
|
|
|
- }"
|
|
|
- bordered
|
|
|
- :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : '')"
|
|
|
- style="width: 100%"
|
|
|
- />
|
|
|
+ <a-table :columns="keywordColumns" :data-source="keywordData" :loading="loading" :pagination="{
|
|
|
+ pageSize: 10,
|
|
|
+ showSizeChanger: false,
|
|
|
+ showQuickJumper: true,
|
|
|
+ showTotal: (total) => `共 ${total} 条`,
|
|
|
+ }" bordered :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : '')"
|
|
|
+ style="width: 100%" />
|
|
|
</a-card>
|
|
|
</a-col>
|
|
|
<a-col :span="12">
|
|
|
<a-card style="margin: 10px" title="TOP展示位">
|
|
|
- <a-table
|
|
|
- :columns="positionColumns"
|
|
|
- :data-source="positionData"
|
|
|
- :loading="loading"
|
|
|
- :pagination="{
|
|
|
- pageSize: 10,
|
|
|
- showSizeChanger: false,
|
|
|
- showQuickJumper: true,
|
|
|
- showTotal: (total) => `共 ${total} 条`,
|
|
|
- }"
|
|
|
- bordered
|
|
|
- :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : '')"
|
|
|
- style="width: 100%"
|
|
|
- />
|
|
|
+ <a-table :columns="positionColumns" :data-source="positionData" :loading="loading" :pagination="{
|
|
|
+ pageSize: 10,
|
|
|
+ showSizeChanger: false,
|
|
|
+ showQuickJumper: true,
|
|
|
+ showTotal: (total) => `共 ${total} 条`,
|
|
|
+ }" bordered :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : '')"
|
|
|
+ style="width: 100%" />
|
|
|
</a-card>
|
|
|
</a-col>
|
|
|
</a-row>
|
|
@@ -171,26 +181,19 @@
|
|
|
</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="true"
|
|
|
- >
|
|
|
+ <a-table :rowKey="(record, index) => {
|
|
|
+ return index;
|
|
|
+ }
|
|
|
+ " class="chartTable" :scroll="{ y: 500 }" :pagination="false" :columns="chartDetailDataCol"
|
|
|
+ :data-source="chartDetailData" :showHeader="true">
|
|
|
<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.impressions }} | {{ record.clicks }} </template>
|
|
|
+ <template v-if="column.key === 'numSlot'"> {{ record.impressions }} | {{ record.clicks }}
|
|
|
+ </template>
|
|
|
</template>
|
|
|
</a-table>
|
|
|
</a-col>
|
|
@@ -203,743 +206,807 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="marketing-googleads">
|
|
|
- import { ref, reactive } from 'vue';
|
|
|
- import dayjs from 'dayjs';
|
|
|
- import selectSite from '@/components/Adweb/selectSite.vue';
|
|
|
- import LineChart from './charts/Line.vue';
|
|
|
- import MapAdweb from '@/components/chart/mapAdweb.vue';
|
|
|
- import {
|
|
|
- getGoogleAdsCustomerStats,
|
|
|
- getGoogleAdsDailyStats,
|
|
|
- getGoogleAdsCampaignStats,
|
|
|
- getGoogleAdsKeywordStats,
|
|
|
- getGoogleAdsPlacementStats,
|
|
|
- getGoogleAdsCountryStats,
|
|
|
- } from './googleads.api';
|
|
|
- import 'flag-icon-css/css/flag-icons.css';
|
|
|
-
|
|
|
- const rangeDate = ref([]);
|
|
|
- const queryParam = reactive<any>({});
|
|
|
- queryParam.limit = 10;
|
|
|
- queryParam.siteCode = localStorage.getItem('siteCode');
|
|
|
- const loading = ref(false);
|
|
|
- const activeChart = ref('impression');
|
|
|
- const chartDetailData = ref([]);
|
|
|
- const countryMapData = ref([]);
|
|
|
- const chartDetailDataCol = ref([
|
|
|
- {
|
|
|
- title: '',
|
|
|
- key: 'flagSlot',
|
|
|
- width: '30px',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '国家/地区名称',
|
|
|
- dataIndex: 'countryName',
|
|
|
- key: 'countryName',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '展示数 | 点击数',
|
|
|
- key: 'numSlot',
|
|
|
- align: 'right',
|
|
|
- },
|
|
|
- ]);
|
|
|
-
|
|
|
- const changeSite = (selectedSiteInfo: any) => {
|
|
|
- queryParam.siteCode = selectedSiteInfo.code;
|
|
|
- localStorage.setItem('siteCode', queryParam.siteCode);
|
|
|
+import { ref, reactive } from 'vue';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+import selectSite from '@/components/Adweb/selectSite.vue';
|
|
|
+import LineChart from './charts/Line.vue';
|
|
|
+import MapAdweb from '@/components/chart/mapAdweb.vue';
|
|
|
+import {
|
|
|
+ getGoogleAdsCustomerStats,
|
|
|
+ getGoogleAdsDailyStats,
|
|
|
+ getGoogleAdsCampaignStats,
|
|
|
+ getGoogleAdsKeywordStats,
|
|
|
+ getGoogleAdsPlacementStats,
|
|
|
+ getGoogleAdsCountryStats,
|
|
|
+} from './googleads.api';
|
|
|
+import 'flag-icon-css/css/flag-icons.css';
|
|
|
+import html2canvas from 'html2canvas';
|
|
|
+import jsPDF from 'jspdf';
|
|
|
+import { DownloadOutlined } from '@ant-design/icons-vue';
|
|
|
+
|
|
|
+const rangeDate = ref([]);
|
|
|
+const queryParam = reactive<any>({});
|
|
|
+queryParam.limit = 10;
|
|
|
+queryParam.siteCode = localStorage.getItem('siteCode');
|
|
|
+const loading = ref(false);
|
|
|
+const activeChart = ref('impression');
|
|
|
+const chartDetailData = ref([]);
|
|
|
+const countryMapData = ref([]);
|
|
|
+const chartDetailDataCol = ref([
|
|
|
+ {
|
|
|
+ title: '',
|
|
|
+ key: 'flagSlot',
|
|
|
+ width: '30px',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '国家/地区名称',
|
|
|
+ dataIndex: 'countryName',
|
|
|
+ key: 'countryName',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '展示数 | 点击数',
|
|
|
+ key: 'numSlot',
|
|
|
+ align: 'right',
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const changeSite = (selectedSiteInfo: any) => {
|
|
|
+ queryParam.siteCode = selectedSiteInfo.code;
|
|
|
+ localStorage.setItem('siteCode', queryParam.siteCode);
|
|
|
+ reloadData();
|
|
|
+};
|
|
|
+
|
|
|
+const onChangeDatePicker = (date, dateString) => {
|
|
|
+ if (dateString.length > 0) {
|
|
|
+ console.log('rangeDate:', rangeDate.value);
|
|
|
+ rangeDate.value = date;
|
|
|
+ console.log('date:', date);
|
|
|
+ queryParam.start = dateString[0];
|
|
|
+ queryParam.end = dateString[1];
|
|
|
+ queryParam.dateType = undefined;
|
|
|
reloadData();
|
|
|
- };
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+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()];
|
|
|
+ }
|
|
|
|
|
|
- const onChangeDatePicker = (date, dateString) => {
|
|
|
- if (dateString.length > 0) {
|
|
|
- console.log('rangeDate:', rangeDate.value);
|
|
|
- rangeDate.value = date;
|
|
|
- console.log('date:', date);
|
|
|
- queryParam.start = dateString[0];
|
|
|
- queryParam.end = dateString[1];
|
|
|
- queryParam.dateType = undefined;
|
|
|
- reloadData();
|
|
|
- }
|
|
|
- };
|
|
|
+ reloadData();
|
|
|
+};
|
|
|
+
|
|
|
+//日期选择只能今天之前
|
|
|
+const disabledDate = (current) => {
|
|
|
+ return current && current > dayjs();
|
|
|
+};
|
|
|
+
|
|
|
+//重新刷新页面数据
|
|
|
+const reloadData = () => {
|
|
|
+ loading.value = true;
|
|
|
+ getCustomerStats();
|
|
|
+ getDailyStats();
|
|
|
+ getCampaignStats();
|
|
|
+ getKeywordStats();
|
|
|
+ getPlacementStats();
|
|
|
+ getCountryStats();
|
|
|
+ loading.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+// 存储所有图表数据
|
|
|
+const allChartData = ref({
|
|
|
+ dates: [] as string[],
|
|
|
+ impression: [] as number[],
|
|
|
+ clicks: [] as number[],
|
|
|
+ ctr: [] as number[],
|
|
|
+ conversion: [] as number[],
|
|
|
+ cost: [] as number[],
|
|
|
+});
|
|
|
+
|
|
|
+// 修改 getDailyStats 函数
|
|
|
+const getDailyStats = async () => {
|
|
|
+ try {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await getGoogleAdsDailyStats(queryParam);
|
|
|
+ if (res) {
|
|
|
+ // 按日期排序并保存原始数据
|
|
|
+ const sortedData = res.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
|
+
|
|
|
+ // 保存所有数据
|
|
|
+ allChartData.value = {
|
|
|
+ dates: sortedData.map((item) => item.date),
|
|
|
+ impression: sortedData.map((item) => Number(item.impressions) || 0),
|
|
|
+ clicks: sortedData.map((item) => Number(item.clicks) || 0),
|
|
|
+ ctr: sortedData.map((item) => (Number(item.ctr) * 100).toFixed(2) || 0.0),
|
|
|
+ conversion: sortedData.map((item) => Number(item.conversions) || 0),
|
|
|
+ cost: sortedData.map((item) => Number(item.cost) || 0),
|
|
|
+ };
|
|
|
|
|
|
- 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()];
|
|
|
+ // 设置初始显示数据
|
|
|
+ dailyStats.value = {
|
|
|
+ dates: allChartData.value.dates,
|
|
|
+ values: allChartData.value[activeChart.value] || [],
|
|
|
+ };
|
|
|
}
|
|
|
-
|
|
|
- reloadData();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取每日统计数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 简化切换图表类型的函数
|
|
|
+const switchChart = (type: string) => {
|
|
|
+ activeChart.value = type;
|
|
|
+ // 直接使用已有数据更新图表
|
|
|
+ dailyStats.value = {
|
|
|
+ dates: allChartData.value.dates,
|
|
|
+ values: allChartData.value[type] || [],
|
|
|
};
|
|
|
+};
|
|
|
+
|
|
|
+const campaignColumns = ref([
|
|
|
+ {
|
|
|
+ title: '广告系列名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '状态',
|
|
|
+ dataIndex: 'status',
|
|
|
+ key: 'status',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '类型',
|
|
|
+ dataIndex: 'advertisingChannelType',
|
|
|
+ key: 'advertisingChannelType',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '展示数',
|
|
|
+ dataIndex: 'impressions',
|
|
|
+ key: 'impressions',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '点击数',
|
|
|
+ dataIndex: 'clicks',
|
|
|
+ key: 'clicks',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '点击率(%)',
|
|
|
+ dataIndex: 'ctr',
|
|
|
+ key: 'ctr',
|
|
|
+ customRender: ({ text }) => {
|
|
|
+ return text ? (Number(text) * 100).toFixed(2) + '%' : '0.00%';
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: 'CPC',
|
|
|
+ dataIndex: 'averageCpc',
|
|
|
+ key: 'cpc',
|
|
|
+ slots: { customRender: 'cpc' },
|
|
|
+ tooltip: '单次点击费用',
|
|
|
+ customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '转化数',
|
|
|
+ dataIndex: 'conversions',
|
|
|
+ key: 'conversions',
|
|
|
+ customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '花费',
|
|
|
+ dataIndex: 'cost',
|
|
|
+ key: 'cost',
|
|
|
+ customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const tableData = ref([]);
|
|
|
+
|
|
|
+const keywordColumns = ref([
|
|
|
+ {
|
|
|
+ title: '关键词',
|
|
|
+ dataIndex: 'keyword',
|
|
|
+ key: 'keyword',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '展示次数',
|
|
|
+ dataIndex: 'impressions',
|
|
|
+ key: 'impressions',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '点击次数',
|
|
|
+ dataIndex: 'clicks',
|
|
|
+ key: 'clicks',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '花费',
|
|
|
+ dataIndex: 'cost',
|
|
|
+ key: 'cost',
|
|
|
+ customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const positionColumns = ref([
|
|
|
+ {
|
|
|
+ title: '展示位置',
|
|
|
+ dataIndex: 'placement',
|
|
|
+ key: 'placement',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '类型',
|
|
|
+ dataIndex: 'type',
|
|
|
+ key: 'type',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '展示次数',
|
|
|
+ dataIndex: 'impressions',
|
|
|
+ key: 'impressions',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '点击次数',
|
|
|
+ dataIndex: 'clicks',
|
|
|
+ key: 'clicks',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '花费',
|
|
|
+ dataIndex: 'cost',
|
|
|
+ key: 'cost',
|
|
|
+ customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+const keywordData = ref([]);
|
|
|
+const positionData = ref([]);
|
|
|
+
|
|
|
+// 修改响应式数据的类型定义
|
|
|
+const customerStats = ref({
|
|
|
+ customerId: '-',
|
|
|
+ descriptiveName: '-',
|
|
|
+ currency: '',
|
|
|
+ balance: '-',
|
|
|
+ cost: '-',
|
|
|
+ conversions: '-',
|
|
|
+ optiScore: 0,
|
|
|
+});
|
|
|
+
|
|
|
+// 添加格式化数字的函数
|
|
|
+const formatNumber = (num: string | number) => {
|
|
|
+ if (!num) return '-';
|
|
|
+ // 处理字符串类型的数字
|
|
|
+ const numStr = typeof num === 'string' ? num : num.toString();
|
|
|
+ // 分离整数和小数部分
|
|
|
+ const parts = numStr.split('.');
|
|
|
+ // 对整数部分添加千位分隔符
|
|
|
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
|
+ // 重新组合整数和小数部分
|
|
|
+ return parts.join('.');
|
|
|
+};
|
|
|
+
|
|
|
+// 获取客户统计数据
|
|
|
+const getCustomerStats = async () => {
|
|
|
+ try {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await getGoogleAdsCustomerStats(queryParam);
|
|
|
+ const stats = res;
|
|
|
+
|
|
|
+ // // 处理余额和花费的分割
|
|
|
+ // const balanceParts = (stats.balance ?? '-').toString().match(/^([\d,.]+)\s*(.*)$/);
|
|
|
+ // const costParts = (stats.cost ?? '-').toString().match(/^([\d,.]+)\s*(.*)$/);
|
|
|
+
|
|
|
+ customerStats.value = {
|
|
|
+ customerId: stats.customerId ?? '-',
|
|
|
+ descriptiveName: stats.descriptiveName ?? '-',
|
|
|
+ currency: stats.currencyCode ?? '',
|
|
|
+ balance: stats.balance ? formatNumber(stats.balance) : '0',
|
|
|
+ cost: stats.cost ? formatNumber(stats.cost) : '-',
|
|
|
+ conversions: stats.conversions ? formatNumber(stats.conversions) : '-',
|
|
|
+ optiScore: stats.optiScore ? stats.optiScore * 100 : 0,
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取客户统计数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
- //日期选择只能今天之前
|
|
|
- const disabledDate = (current) => {
|
|
|
- return current && current > dayjs();
|
|
|
- };
|
|
|
+const dailyStats = ref({
|
|
|
+ dates: [] as string[],
|
|
|
+ values: [] as number[],
|
|
|
+});
|
|
|
|
|
|
- //重新刷新页面数据
|
|
|
- const reloadData = () => {
|
|
|
+const getCampaignStats = async () => {
|
|
|
+ try {
|
|
|
loading.value = true;
|
|
|
- getCustomerStats();
|
|
|
- getDailyStats();
|
|
|
- getCampaignStats();
|
|
|
- getKeywordStats();
|
|
|
- getPlacementStats();
|
|
|
- getCountryStats();
|
|
|
+ const res = await getGoogleAdsCampaignStats(queryParam);
|
|
|
+ if (res) {
|
|
|
+ tableData.value = res.map((item) => ({
|
|
|
+ name: item.name,
|
|
|
+ status: item.status,
|
|
|
+ advertisingChannelType: item.advertisingChannelType,
|
|
|
+ biddingStrategyType: item.biddingStrategyType,
|
|
|
+ budget: item.budget,
|
|
|
+ impressions: item.impressions,
|
|
|
+ clicks: item.clicks,
|
|
|
+ ctr: item.ctr,
|
|
|
+ averageCpc: item.averageCpc,
|
|
|
+ averageCpm: item.averageCpm,
|
|
|
+ conversions: item.conversions,
|
|
|
+ cost: item.cost,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取广告系列数据失败:', error);
|
|
|
+ } finally {
|
|
|
loading.value = false;
|
|
|
- };
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
- // 存储所有图表数据
|
|
|
- const allChartData = ref({
|
|
|
- dates: [] as string[],
|
|
|
- impression: [] as number[],
|
|
|
- clicks: [] as number[],
|
|
|
- ctr: [] as number[],
|
|
|
- conversion: [] as number[],
|
|
|
- cost: [] as number[],
|
|
|
- });
|
|
|
+const getKeywordStats = async () => {
|
|
|
+ try {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await getGoogleAdsKeywordStats(queryParam);
|
|
|
+ if (res) {
|
|
|
+ keywordData.value = res.map((item) => ({
|
|
|
+ keyword: item.keyword,
|
|
|
+ impressions: item.impressions,
|
|
|
+ clicks: item.clicks,
|
|
|
+ ctr: item.ctr,
|
|
|
+ cost: item.cost,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取关键词统计数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
- // 修改 getDailyStats 函数
|
|
|
- const getDailyStats = async () => {
|
|
|
- try {
|
|
|
- loading.value = true;
|
|
|
- const res = await getGoogleAdsDailyStats(queryParam);
|
|
|
- if (res) {
|
|
|
- // 按日期排序并保存原始数据
|
|
|
- const sortedData = res.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
|
-
|
|
|
- // 保存所有数据
|
|
|
- allChartData.value = {
|
|
|
- dates: sortedData.map((item) => item.date),
|
|
|
- impression: sortedData.map((item) => Number(item.impressions) || 0),
|
|
|
- clicks: sortedData.map((item) => Number(item.clicks) || 0),
|
|
|
- ctr: sortedData.map((item) => (Number(item.ctr) * 100).toFixed(2) || 0.0),
|
|
|
- conversion: sortedData.map((item) => Number(item.conversions) || 0),
|
|
|
- cost: sortedData.map((item) => Number(item.cost) || 0),
|
|
|
- };
|
|
|
-
|
|
|
- // 设置初始显示数据
|
|
|
- dailyStats.value = {
|
|
|
- dates: allChartData.value.dates,
|
|
|
- values: allChartData.value[activeChart.value] || [],
|
|
|
- };
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取每日统计数据失败:', error);
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
+const getPlacementStats = async () => {
|
|
|
+ try {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await getGoogleAdsPlacementStats(queryParam);
|
|
|
+ if (res) {
|
|
|
+ positionData.value = res.map((item) => ({
|
|
|
+ placement: item.placement,
|
|
|
+ type: item.type,
|
|
|
+ impressions: item.impressions,
|
|
|
+ clicks: item.clicks,
|
|
|
+ ctr: item.ctr,
|
|
|
+ cost: item.cost,
|
|
|
+ }));
|
|
|
}
|
|
|
- };
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取展示位置统计数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
- // 简化切换图表类型的函数
|
|
|
- const switchChart = (type: string) => {
|
|
|
- activeChart.value = type;
|
|
|
- // 直接使用已有数据更新图表
|
|
|
- dailyStats.value = {
|
|
|
- dates: allChartData.value.dates,
|
|
|
- values: allChartData.value[type] || [],
|
|
|
- };
|
|
|
+const getCountryStats = async () => {
|
|
|
+ try {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await getGoogleAdsCountryStats(queryParam);
|
|
|
+ if (res) {
|
|
|
+ // 处理地图数据
|
|
|
+ countryMapData.value = res.map((item) => ({
|
|
|
+ name: item.countryName,
|
|
|
+ value: item.impressions,
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 处理表格数据
|
|
|
+ chartDetailData.value = res.map((item) => ({
|
|
|
+ countryCode: item.countryCode?.toLowerCase(),
|
|
|
+ countryName: item.countryName,
|
|
|
+ clicks: item.clicks,
|
|
|
+ impressions: item.impressions,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取国家统计数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// Add PDF export function
|
|
|
+const exportToPDF = () => {
|
|
|
+ const element = document.querySelector('.googlleads') as HTMLElement;
|
|
|
+ if (!element) return;
|
|
|
+
|
|
|
+ // 获取元素的实际高度
|
|
|
+ const elementHeight = element.scrollHeight;
|
|
|
+ const elementWidth = element.scrollWidth;
|
|
|
+
|
|
|
+ // 设置html2canvas配置
|
|
|
+ const options = {
|
|
|
+ scale: 2, // 提高质量
|
|
|
+ useCORS: true,
|
|
|
+ allowTaint: true,
|
|
|
+ logging: true,
|
|
|
+ width: elementWidth,
|
|
|
+ height: elementHeight,
|
|
|
+ scrollX: 0,
|
|
|
+ scrollY: -window.scrollY, // 修复滚动位置
|
|
|
+ windowWidth: elementWidth,
|
|
|
+ windowHeight: elementHeight
|
|
|
};
|
|
|
|
|
|
- const campaignColumns = ref([
|
|
|
- {
|
|
|
- title: '广告系列名称',
|
|
|
- dataIndex: 'name',
|
|
|
- key: 'name',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '状态',
|
|
|
- dataIndex: 'status',
|
|
|
- key: 'status',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '类型',
|
|
|
- dataIndex: 'advertisingChannelType',
|
|
|
- key: 'advertisingChannelType',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '展示数',
|
|
|
- dataIndex: 'impressions',
|
|
|
- key: 'impressions',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '点击数',
|
|
|
- dataIndex: 'clicks',
|
|
|
- key: 'clicks',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '点击率(%)',
|
|
|
- dataIndex: 'ctr',
|
|
|
- key: 'ctr',
|
|
|
- customRender: ({ text }) => {
|
|
|
- return text ? (Number(text) * 100).toFixed(2) + '%' : '0.00%';
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- title: 'CPC',
|
|
|
- dataIndex: 'averageCpc',
|
|
|
- key: 'cpc',
|
|
|
- slots: { customRender: 'cpc' },
|
|
|
- tooltip: '单次点击费用',
|
|
|
- customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
- },
|
|
|
- {
|
|
|
- title: '转化数',
|
|
|
- dataIndex: 'conversions',
|
|
|
- key: 'conversions',
|
|
|
- customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
- },
|
|
|
- {
|
|
|
- title: '花费',
|
|
|
- dataIndex: 'cost',
|
|
|
- key: 'cost',
|
|
|
- customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
- },
|
|
|
- ]);
|
|
|
+ html2canvas(element, options).then((canvas) => {
|
|
|
+ const imgData = canvas.toDataURL('image/png');
|
|
|
+ const pdf = new jsPDF('p', 'mm', 'a4');
|
|
|
|
|
|
- const tableData = ref([]);
|
|
|
+ // PDF页面尺寸
|
|
|
+ const pageWidth = pdf.internal.pageSize.getWidth();
|
|
|
+ const pageHeight = pdf.internal.pageSize.getHeight();
|
|
|
|
|
|
- const keywordColumns = ref([
|
|
|
- {
|
|
|
- title: '关键词',
|
|
|
- dataIndex: 'keyword',
|
|
|
- key: 'keyword',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '展示次数',
|
|
|
- dataIndex: 'impressions',
|
|
|
- key: 'impressions',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '点击次数',
|
|
|
- dataIndex: 'clicks',
|
|
|
- key: 'clicks',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '花费',
|
|
|
- dataIndex: 'cost',
|
|
|
- key: 'cost',
|
|
|
- customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
- },
|
|
|
- ]);
|
|
|
+ // 计算图片在PDF中的尺寸
|
|
|
+ const imgWidth = pageWidth;
|
|
|
+ const imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
|
|
|
|
- const positionColumns = ref([
|
|
|
- {
|
|
|
- title: '展示位置',
|
|
|
- dataIndex: 'placement',
|
|
|
- key: 'placement',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '类型',
|
|
|
- dataIndex: 'type',
|
|
|
- key: 'type',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '展示次数',
|
|
|
- dataIndex: 'impressions',
|
|
|
- key: 'impressions',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '点击次数',
|
|
|
- dataIndex: 'clicks',
|
|
|
- key: 'clicks',
|
|
|
- },
|
|
|
- {
|
|
|
- title: '花费',
|
|
|
- dataIndex: 'cost',
|
|
|
- key: 'cost',
|
|
|
- customRender: ({ text }) => `${text.toFixed(2)}`,
|
|
|
- },
|
|
|
- ]);
|
|
|
-
|
|
|
- const keywordData = ref([]);
|
|
|
- const positionData = ref([]);
|
|
|
-
|
|
|
- // 修改响应式数据的类型定义
|
|
|
- const customerStats = ref({
|
|
|
- customerId: '-',
|
|
|
- descriptiveName: '-',
|
|
|
- currency: '',
|
|
|
- balance: '-',
|
|
|
- cost: '-',
|
|
|
- conversions: '-',
|
|
|
- optiScore: 0,
|
|
|
- });
|
|
|
+ // 分页处理
|
|
|
+ let heightLeft = imgHeight;
|
|
|
+ let position = 0;
|
|
|
|
|
|
- // 添加格式化数字的函数
|
|
|
- const formatNumber = (num: string | number) => {
|
|
|
- if (!num) return '-';
|
|
|
- // 处理字符串类型的数字
|
|
|
- const numStr = typeof num === 'string' ? num : num.toString();
|
|
|
- // 分离整数和小数部分
|
|
|
- const parts = numStr.split('.');
|
|
|
- // 对整数部分添加千位分隔符
|
|
|
- parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
|
- // 重新组合整数和小数部分
|
|
|
- return parts.join('.');
|
|
|
- };
|
|
|
+ // 第一页
|
|
|
+ pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
|
|
+ heightLeft -= pageHeight;
|
|
|
|
|
|
- // 获取客户统计数据
|
|
|
- const getCustomerStats = async () => {
|
|
|
- try {
|
|
|
- loading.value = true;
|
|
|
- const res = await getGoogleAdsCustomerStats(queryParam);
|
|
|
- const stats = res;
|
|
|
-
|
|
|
- // // 处理余额和花费的分割
|
|
|
- // const balanceParts = (stats.balance ?? '-').toString().match(/^([\d,.]+)\s*(.*)$/);
|
|
|
- // const costParts = (stats.cost ?? '-').toString().match(/^([\d,.]+)\s*(.*)$/);
|
|
|
-
|
|
|
- customerStats.value = {
|
|
|
- customerId: stats.customerId ?? '-',
|
|
|
- descriptiveName: stats.descriptiveName ?? '-',
|
|
|
- currency: stats.currencyCode ?? '',
|
|
|
- balance: stats.balance ? formatNumber(stats.balance) : '0',
|
|
|
- cost: stats.cost ? formatNumber(stats.cost) : '-',
|
|
|
- conversions: stats.conversions ? formatNumber(stats.conversions) : '-',
|
|
|
- optiScore: stats.optiScore ? stats.optiScore * 100 : 0,
|
|
|
- };
|
|
|
- } catch (error) {
|
|
|
- console.error('获取客户统计数据失败:', error);
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
+ // 后续页面
|
|
|
+ while (heightLeft >= 0) {
|
|
|
+ position = heightLeft - imgHeight;
|
|
|
+ pdf.addPage();
|
|
|
+ pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
|
|
+ heightLeft -= pageHeight;
|
|
|
}
|
|
|
- };
|
|
|
|
|
|
- const dailyStats = ref({
|
|
|
- dates: [] as string[],
|
|
|
- values: [] as number[],
|
|
|
+ pdf.save('google_ads_report.pdf');
|
|
|
});
|
|
|
+};
|
|
|
+
|
|
|
+// onMounted(() => {
|
|
|
+// getCustomerStats();
|
|
|
+// getDailyStats();
|
|
|
+// getCampaignStats();
|
|
|
+// getKeywordStats();
|
|
|
+// getPlacementStats();
|
|
|
+// getCountryStats();
|
|
|
+// });
|
|
|
+</script>
|
|
|
|
|
|
- const getCampaignStats = async () => {
|
|
|
- try {
|
|
|
- loading.value = true;
|
|
|
- const res = await getGoogleAdsCampaignStats(queryParam);
|
|
|
- if (res) {
|
|
|
- tableData.value = res.map((item) => ({
|
|
|
- name: item.name,
|
|
|
- status: item.status,
|
|
|
- advertisingChannelType: item.advertisingChannelType,
|
|
|
- biddingStrategyType: item.biddingStrategyType,
|
|
|
- budget: item.budget,
|
|
|
- impressions: item.impressions,
|
|
|
- clicks: item.clicks,
|
|
|
- ctr: item.ctr,
|
|
|
- averageCpc: item.averageCpc,
|
|
|
- averageCpm: item.averageCpm,
|
|
|
- conversions: item.conversions,
|
|
|
- cost: item.cost,
|
|
|
- }));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取广告系列数据失败:', error);
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
- }
|
|
|
- };
|
|
|
+<style scoped lang="less">
|
|
|
+.googlleads {
|
|
|
+ min-height: calc(100vh - 64px); // 64px is the typical header height
|
|
|
+ padding-bottom: 24px; // Add some padding at the bottom
|
|
|
+ background: #fff; // Add background color if needed
|
|
|
+}
|
|
|
|
|
|
- const getKeywordStats = async () => {
|
|
|
- try {
|
|
|
- loading.value = true;
|
|
|
- const res = await getGoogleAdsKeywordStats(queryParam);
|
|
|
- if (res) {
|
|
|
- keywordData.value = res.map((item) => ({
|
|
|
- keyword: item.keyword,
|
|
|
- impressions: item.impressions,
|
|
|
- clicks: item.clicks,
|
|
|
- ctr: item.ctr,
|
|
|
- cost: item.cost,
|
|
|
- }));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取关键词统计数据失败:', error);
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
- }
|
|
|
- };
|
|
|
+.r1 {
|
|
|
+ margin: 20px;
|
|
|
|
|
|
- const getPlacementStats = async () => {
|
|
|
- try {
|
|
|
- loading.value = true;
|
|
|
- const res = await getGoogleAdsPlacementStats(queryParam);
|
|
|
- if (res) {
|
|
|
- positionData.value = res.map((item) => ({
|
|
|
- placement: item.placement,
|
|
|
- type: item.type,
|
|
|
- impressions: item.impressions,
|
|
|
- clicks: item.clicks,
|
|
|
- ctr: item.ctr,
|
|
|
- cost: item.cost,
|
|
|
- }));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取展示位置统计数据失败:', error);
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
- }
|
|
|
- };
|
|
|
+ .choose-site {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
|
|
|
- const getCountryStats = async () => {
|
|
|
- try {
|
|
|
- loading.value = true;
|
|
|
- const res = await getGoogleAdsCountryStats(queryParam);
|
|
|
- if (res) {
|
|
|
- // 处理地图数据
|
|
|
- countryMapData.value = res.map((item) => ({
|
|
|
- name: item.countryName,
|
|
|
- value: item.impressions,
|
|
|
- }));
|
|
|
-
|
|
|
- // 处理表格数据
|
|
|
- chartDetailData.value = res.map((item) => ({
|
|
|
- countryCode: item.countryCode?.toLowerCase(),
|
|
|
- countryName: item.countryName,
|
|
|
- clicks: item.clicks,
|
|
|
- impressions: item.impressions,
|
|
|
- }));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取国家统计数据失败:', error);
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
+ :deep(.ant-form-item) {
|
|
|
+ margin-bottom: 0;
|
|
|
}
|
|
|
- };
|
|
|
-
|
|
|
- // onMounted(() => {
|
|
|
- // getCustomerStats();
|
|
|
- // getDailyStats();
|
|
|
- // getCampaignStats();
|
|
|
- // getKeywordStats();
|
|
|
- // getPlacementStats();
|
|
|
- // getCountryStats();
|
|
|
- // });
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped lang="less">
|
|
|
- .r1 {
|
|
|
- margin: 20px;
|
|
|
+ }
|
|
|
|
|
|
- .choose-site {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
+ .t1 {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 400;
|
|
|
+ letter-spacing: 0px;
|
|
|
+ line-height: 32px;
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
|
|
|
- :deep(.ant-form-item) {
|
|
|
- margin-bottom: 0;
|
|
|
- }
|
|
|
- }
|
|
|
+ .ant-form-item {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
|
|
|
- .t1 {
|
|
|
- font-size: 18px;
|
|
|
- font-weight: 400;
|
|
|
- letter-spacing: 0px;
|
|
|
- line-height: 32px;
|
|
|
- margin-left: 10px;
|
|
|
- }
|
|
|
+ .ant-calendar-picker {
|
|
|
+ margin-right: 20px;
|
|
|
+ }
|
|
|
|
|
|
- .ant-form-item {
|
|
|
- flex: 1;
|
|
|
+ /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;
|
|
|
}
|
|
|
|
|
|
- .ant-calendar-picker {
|
|
|
- margin-right: 20px;
|
|
|
+ &.active {
|
|
|
+ color: @primary-color;
|
|
|
+ background: #e6f7ff;
|
|
|
+ border-color: @primary-color;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ .time-btn-group {
|
|
|
/deep/ .ant-btn {
|
|
|
- background: transparent;
|
|
|
- margin-right: 10px;
|
|
|
+ background: #fff;
|
|
|
padding: 4px 15px;
|
|
|
border: 1px solid #d9d9d9;
|
|
|
- border-radius: 4px;
|
|
|
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;
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- .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;
|
|
|
- }
|
|
|
+.r2 {
|
|
|
+ background: #fff;
|
|
|
+ padding: 20px;
|
|
|
+ margin: 15px 10px 10px;
|
|
|
|
|
|
- &:last-child {
|
|
|
- border-top-right-radius: 4px;
|
|
|
- border-bottom-right-radius: 4px;
|
|
|
- }
|
|
|
+ .ant-col:not(:last-child) {
|
|
|
+ border-right: 1px solid #e6e6e6;
|
|
|
+ }
|
|
|
|
|
|
- &:not(:first-child) {
|
|
|
- margin-left: -1px;
|
|
|
- }
|
|
|
+ p {
|
|
|
+ margin: 0;
|
|
|
+ text-align: center;
|
|
|
|
|
|
- &:hover {
|
|
|
- color: @primary-color;
|
|
|
- border-color: @primary-color;
|
|
|
- position: relative;
|
|
|
- z-index: 1;
|
|
|
- background: #fff;
|
|
|
- }
|
|
|
+ &.t1 {
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
|
- &.active {
|
|
|
- color: @primary-color;
|
|
|
- background: #e6f7ff;
|
|
|
- border-color: @primary-color;
|
|
|
- position: relative;
|
|
|
- z-index: 2;
|
|
|
- }
|
|
|
+ img {
|
|
|
+ margin-right: 10px;
|
|
|
+ width: 15px;
|
|
|
+ margin-top: -5px;
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- .r2 {
|
|
|
- background: #fff;
|
|
|
- padding: 20px;
|
|
|
- margin: 15px 10px 10px;
|
|
|
|
|
|
- .ant-col:not(:last-child) {
|
|
|
- border-right: 1px solid #e6e6e6;
|
|
|
+ &.t2 {
|
|
|
+ color: @primary-color;
|
|
|
+ font-size: 30px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1;
|
|
|
+ padding-left: 25px;
|
|
|
}
|
|
|
|
|
|
- p {
|
|
|
- margin: 0;
|
|
|
- text-align: center;
|
|
|
-
|
|
|
- &.t1 {
|
|
|
- color: #333;
|
|
|
- margin-bottom: 15px;
|
|
|
+ &.t3 {
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 700;
|
|
|
+ letter-spacing: 0px;
|
|
|
+ line-height: 38px;
|
|
|
+ color: rgba(13, 62, 122, 1);
|
|
|
|
|
|
- img {
|
|
|
- margin-right: 10px;
|
|
|
- width: 15px;
|
|
|
- margin-top: -5px;
|
|
|
- }
|
|
|
+ .value {
|
|
|
+ font-size: 32px;
|
|
|
}
|
|
|
|
|
|
- &.t2 {
|
|
|
- color: @primary-color;
|
|
|
- font-size: 30px;
|
|
|
- font-weight: 500;
|
|
|
- line-height: 1;
|
|
|
- padding-left: 25px;
|
|
|
+ .currency {
|
|
|
+ font-size: 16px;
|
|
|
+ margin-left: 4px;
|
|
|
}
|
|
|
|
|
|
- &.t3 {
|
|
|
- font-size: 32px;
|
|
|
- font-weight: 700;
|
|
|
- letter-spacing: 0px;
|
|
|
- line-height: 38px;
|
|
|
- color: rgba(13, 62, 122, 1);
|
|
|
-
|
|
|
- .value {
|
|
|
- font-size: 32px;
|
|
|
- }
|
|
|
-
|
|
|
- .currency {
|
|
|
- font-size: 16px;
|
|
|
- margin-left: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- &.company-name {
|
|
|
- font-size: 18px;
|
|
|
- text-align: left;
|
|
|
- line-height: 1;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
+ &.company-name {
|
|
|
+ font-size: 18px;
|
|
|
+ text-align: left;
|
|
|
+ line-height: 1;
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- .r5 {
|
|
|
- background: #fff;
|
|
|
- padding: 10px;
|
|
|
+.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;
|
|
|
- margin: 0 !important;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
|
- .wrap {
|
|
|
+ &.blue {
|
|
|
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 {
|
|
|
+ &.effect:hover {
|
|
|
+ box-shadow: none;
|
|
|
background: rgb(241, 248, 255);
|
|
|
- border: none;
|
|
|
- color: #000;
|
|
|
- padding: 10px;
|
|
|
}
|
|
|
|
|
|
- /deep/ .ant-table-tbody .ant-table-row td {
|
|
|
- padding: 10px;
|
|
|
- color: #000;
|
|
|
+ img {
|
|
|
+ width: 15px;
|
|
|
}
|
|
|
|
|
|
- .r5-1 {
|
|
|
- display: inline-block;
|
|
|
- width: 100%;
|
|
|
- margin-top: 30px;
|
|
|
+ .fr {
|
|
|
+ float: right;
|
|
|
+ width: calc(100% - 15px);
|
|
|
+ text-align: center;
|
|
|
|
|
|
- .fl {
|
|
|
- float: left;
|
|
|
- position: relative;
|
|
|
+ p:last-child {
|
|
|
+ font-size: 30px;
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- .ant-btn {
|
|
|
- border-radius: 0;
|
|
|
- border: none;
|
|
|
- margin-right: 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;
|
|
|
+ }
|
|
|
|
|
|
- .ant-btn-group {
|
|
|
- .ant-btn {
|
|
|
- background: #f5f5f5;
|
|
|
+ .r5-1 {
|
|
|
+ display: inline-block;
|
|
|
+ width: 100%;
|
|
|
+ margin-top: 30px;
|
|
|
|
|
|
- &:hover {
|
|
|
- background: #fff;
|
|
|
- }
|
|
|
+ .fl {
|
|
|
+ float: left;
|
|
|
+ position: relative;
|
|
|
|
|
|
- &.active {
|
|
|
- color: @primary-color;
|
|
|
- background: #e6f7ff;
|
|
|
- border-color: @primary-color;
|
|
|
- z-index: 2;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ .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;
|
|
|
+ .ant-btn-group {
|
|
|
+ .ant-btn {
|
|
|
+ background: #f5f5f5;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #fff;
|
|
|
}
|
|
|
|
|
|
- &:last-child i {
|
|
|
- background: #f0b358;
|
|
|
+ &.active {
|
|
|
+ color: @primary-color;
|
|
|
+ background: #e6f7ff;
|
|
|
+ border-color: @primary-color;
|
|
|
+ z-index: 2;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .box {
|
|
|
- border-radius: 10px;
|
|
|
- text-align: center;
|
|
|
- min-height: 180px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- justify-content: center;
|
|
|
+ .fr {
|
|
|
+ float: right;
|
|
|
+ line-height: 2;
|
|
|
|
|
|
- p {
|
|
|
- color: #fff;
|
|
|
+ span {
|
|
|
+ margin-right: 30px;
|
|
|
|
|
|
- img {
|
|
|
- width: 19px;
|
|
|
- margin: -5px 10px 0 0;
|
|
|
+ i {
|
|
|
+ display: inline-block;
|
|
|
+ width: 25px;
|
|
|
+ height: 3px;
|
|
|
+ background: #544beb;
|
|
|
+ position: relative;
|
|
|
+ top: -4px;
|
|
|
+ margin-right: 20px;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- .num {
|
|
|
- font-size: 30px;
|
|
|
- margin-bottom: 10px;
|
|
|
+ &:last-child i {
|
|
|
+ background: #f0b358;
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- &.b1 {
|
|
|
- background: rgb(233, 107, 95);
|
|
|
- }
|
|
|
+ .box {
|
|
|
+ border-radius: 10px;
|
|
|
+ text-align: center;
|
|
|
+ min-height: 180px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
|
|
|
- &.b2 {
|
|
|
- background: rgb(88, 204, 168);
|
|
|
- }
|
|
|
+ p {
|
|
|
+ color: #fff;
|
|
|
|
|
|
- &.b3 {
|
|
|
- background: rgb(124, 152, 252);
|
|
|
+ img {
|
|
|
+ width: 19px;
|
|
|
+ margin: -5px 10px 0 0;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- &.b4 {
|
|
|
- background: #f0b358;
|
|
|
- }
|
|
|
+ .num {
|
|
|
+ font-size: 30px;
|
|
|
+ margin-bottom: 10px;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- .score-circle {
|
|
|
- text-align: center;
|
|
|
- margin-top: 20px;
|
|
|
- }
|
|
|
+ &.b1 {
|
|
|
+ background: rgb(233, 107, 95);
|
|
|
+ }
|
|
|
|
|
|
- // 添加表格样式
|
|
|
- :deep(.table-striped) {
|
|
|
- background-color: #fafafa;
|
|
|
- }
|
|
|
+ &.b2 {
|
|
|
+ background: rgb(88, 204, 168);
|
|
|
+ }
|
|
|
|
|
|
- // 修改表格头部样式
|
|
|
- :deep(.ant-table-thead > tr > th) {
|
|
|
- background: #e6f7ff !important;
|
|
|
- font-weight: 700; // 字重改为700
|
|
|
- color: rgba(13, 62, 122, 1) !important; // 字体颜色调整
|
|
|
- }
|
|
|
+ &.b3 {
|
|
|
+ background: rgb(124, 152, 252);
|
|
|
+ }
|
|
|
|
|
|
- :deep(.ant-table-tbody > tr > td) {
|
|
|
- border-bottom: 1px solid #f0f0f0;
|
|
|
+ &.b4 {
|
|
|
+ background: #f0b358;
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+.score-circle {
|
|
|
+ text-align: center;
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+// 添加表格样式
|
|
|
+:deep(.table-striped) {
|
|
|
+ background-color: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+// 修改表格头部样式
|
|
|
+:deep(.ant-table-thead > tr > th) {
|
|
|
+ background: #e6f7ff !important;
|
|
|
+ font-weight: 700; // 字重改为700
|
|
|
+ color: rgba(13, 62, 122, 1) !important; // 字体颜色调整
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.ant-table-tbody > tr > td) {
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
</style>
|