|
@@ -1,58 +1,240 @@
|
|
<template>
|
|
<template>
|
|
- <el-dialog
|
|
|
|
- :title="'统计'"
|
|
|
|
- width="80%"
|
|
|
|
- v-model="visible"
|
|
|
|
- :destroy-on-close="true"
|
|
|
|
- :close-on-click-modal="false"
|
|
|
|
- draggable>
|
|
|
|
- <div class="statistics-table-wrapper">
|
|
|
|
- <StatisticsIndex :row="row" />
|
|
|
|
- </div>
|
|
|
|
- </el-dialog>
|
|
|
|
|
|
+ <div class="dialog">
|
|
|
|
+ <el-dialog
|
|
|
|
+ :title="'统计'"
|
|
|
|
+ width="80%"
|
|
|
|
+ v-model="visible"
|
|
|
|
+ :destroy-on-close="true"
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
+ :before-close="done=>handleClose(done)"
|
|
|
|
+ draggable>
|
|
|
|
+ <div class="dialog-body">
|
|
|
|
+
|
|
|
|
+ <div class="statistics-table-wrapper">
|
|
|
|
+ <el-row v-show="showSearch">
|
|
|
|
+ <el-form :inline="true" :model="state.queryForm" @keyup.enter="withCollapsedChildren(getDataList)" ref="queryRef">
|
|
|
|
+ <el-form-item :label="'时间范围'" prop="timeRange">
|
|
|
|
+ <el-select v-model="state.queryForm.timeRange" @change="handleTimeChange()">
|
|
|
|
+ <el-option :value="TimeRange.ALL" :label="'全部'">全部</el-option>
|
|
|
|
+ <el-option :value="TimeRange.TWENTY_FOUR_HOURS" :label="'24小时'">24小时</el-option>
|
|
|
|
+ <el-option :value="TimeRange.FIFTEEN_MINUTES" :label="'15分钟'">15分钟</el-option>
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ </el-row>
|
|
|
|
+ <el-table class="statistics-table" :data="state.dataList" row-key="ip" @sort-change="withCollapsedChildren(sortChangeHandle)" style="width: 100%"
|
|
|
|
+ v-loading="state.loading" border :cell-style="tableStyle.cellStyle"
|
|
|
|
+ :header-cell-style="tableStyle.headerCellStyle"
|
|
|
|
+ :expand-row-keys="expandedRowKeys"
|
|
|
|
+ @expand-change="handleExpandChange"
|
|
|
|
+ >
|
|
|
|
+ <!-- <el-table-column align="center" type="selection" width="40" /> -->
|
|
|
|
+ <el-table-column show-overflow-tooltip type="expand">
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
+ <div class="child-table-container">
|
|
|
|
+ <el-table :data="row.childTableData" v-loading="row.childTableData.childLoading" border :cell-style="tableStyle.cellStyle"
|
|
|
|
+ :header-cell-style="tableStyle.headerCellStyle">
|
|
|
|
+ <!-- <el-table-column :label="t('apps.ip')" prop="ip" show-overflow-tooltip></el-table-column> -->
|
|
|
|
+ <el-table-column :label="t('apps.fingerprint')" prop="fingerprint" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.device')" prop="device" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.firstAccessTime')" prop="firstAccessTime" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.lastAccessTime')" prop="lastAccessTime" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.lastVisitedPage')" prop="lastVisitedPage" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.accessVolume')" prop="accessVolume" show-overflow-tooltip></el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ <div class="pagination-container" @click.stop>
|
|
|
|
+ <pagination v-bind="row.childPagination"
|
|
|
|
+ :current-page="row.childPagination.page"
|
|
|
|
+ :page-size="row.childPagination.size"
|
|
|
|
+ :total="row.childPagination.total"
|
|
|
|
+ :key="`pagination-${row.ip}-${row.childPagination.page}`"
|
|
|
|
+ @size-change="size => handlePageSizeChange(row, size)"
|
|
|
|
+ @current-change="page => handlePageChange(row, page)" />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.firstAccessTime')" prop="firstAccessTime" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.lastAccessTime')" prop="lastAccessTime" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.accessSource')" prop="accessSource" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.lastVisitedPage')" prop="lastVisitedPage" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.IP')" prop="IP" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.area')" prop="area" show-overflow-tooltip></el-table-column>
|
|
|
|
+ <el-table-column :label="t('apps.accessVolume')" prop="accessVolume" show-overflow-tooltip></el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+
|
|
|
|
+ <pagination
|
|
|
|
+ @current-change="page => withCollapsedChildren(currentChangeHandle, page)"
|
|
|
|
+ @size-change="size => withCollapsedChildren(sizeChangeHandle, size)"
|
|
|
|
+ v-bind="state.pagination">
|
|
|
|
+ </pagination>
|
|
|
|
+ <br>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+ </el-dialog>
|
|
|
|
+ </div>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
-import { ref } from 'vue';
|
|
|
|
-const StatisticsIndex = defineAsyncComponent(() => import('/@/views/marketing/statistics/index.vue'));
|
|
|
|
|
|
+import { ref, reactive, watchEffect } from 'vue';
|
|
|
|
+import { BasicTableProps, useTable } from '/@/hooks/table';
|
|
|
|
+import { pageList, detailList } from '/@/api/marketing/statistics';
|
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
|
+
|
|
|
|
+enum TimeRange {
|
|
|
|
+ ALL = 0,
|
|
|
|
+ TWENTY_FOUR_HOURS = 1,
|
|
|
|
+ FIFTEEN_MINUTES = 2,
|
|
|
|
+}
|
|
|
|
|
|
const visible = ref(false);
|
|
const visible = ref(false);
|
|
const row = ref({});
|
|
const row = ref({});
|
|
|
|
+const { t } = useI18n();
|
|
|
|
+// 定义变量内容
|
|
|
|
+const queryRef = ref();
|
|
|
|
+const showSearch = ref(true);
|
|
|
|
+const expandedRowKeys = ref<number[]>([]); // 记录展开的行
|
|
|
|
+const setIntervalTime = ref(0);
|
|
|
|
+
|
|
|
|
+// table hook
|
|
|
|
+const state: BasicTableProps = reactive<BasicTableProps>({
|
|
|
|
+ queryForm: {
|
|
|
|
+ timeRange: TimeRange.ALL,
|
|
|
|
+ appId: '',
|
|
|
|
+ },
|
|
|
|
+ pageList: pageList,
|
|
|
|
+ dataListLoading: true,
|
|
|
|
+ pagination: {
|
|
|
|
+ current: 1,
|
|
|
|
+ size: 10,
|
|
|
|
+ total: 0,
|
|
|
|
+ pageSizes: [5, 10, 20, 50, 100]
|
|
|
|
+ },
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const { getDataList, currentChangeHandle, sortChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
|
|
|
|
|
|
// 打开弹窗
|
|
// 打开弹窗
|
|
const openDialog = async (_row: any) => {
|
|
const openDialog = async (_row: any) => {
|
|
visible.value = true;
|
|
visible.value = true;
|
|
- row.value = _row;
|
|
|
|
|
|
+ getDataList();
|
|
|
|
+ state.queryForm.timeRange = TimeRange.ALL;
|
|
|
|
+ state.queryForm.appId = _row.id;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const handleClose = (done) => {
|
|
|
|
+ clearInterval(setIntervalTime.value);
|
|
|
|
+ done();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// // 清空搜索条件
|
|
|
|
+// const resetQuery = () => {
|
|
|
|
+// queryRef.value?.resetFields();
|
|
|
|
+// withCollapsedChildren(getDataList);
|
|
|
|
+// };
|
|
|
|
+
|
|
|
|
+const handleTimeChange = () => {
|
|
|
|
+ withCollapsedChildren(getDataList);
|
|
|
|
+ if(state.queryForm.timeRange === TimeRange.FIFTEEN_MINUTES) {
|
|
|
|
+ setIntervalTime.value = setInterval(() => {
|
|
|
|
+ getDataList();
|
|
|
|
+ }, 4000);
|
|
|
|
+ }else{
|
|
|
|
+ clearInterval(setIntervalTime.value);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 初始化二级表格数据和分页
|
|
|
|
+const initChildData = (parentRow) => {
|
|
|
|
+ parentRow.expanded = false;
|
|
|
|
+ parentRow.childTableData = []
|
|
|
|
+ parentRow.childLoading = false;
|
|
|
|
+ parentRow.childPagination = reactive({
|
|
|
|
+ page: 1,
|
|
|
|
+ size: 5,
|
|
|
|
+ total: 0,
|
|
|
|
+ pageSizes: [5, 10, 20, 50, 100]
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 初始化所有二级表格数据
|
|
|
|
+watchEffect(() => {
|
|
|
|
+ state?.dataList?.forEach(row => {
|
|
|
|
+ initChildData(row);
|
|
|
|
+ })
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 获取二级表格数据
|
|
|
|
+const loadChildData = async (parentRow) => {
|
|
|
|
+ parentRow.childLoading = true;
|
|
|
|
+ try {
|
|
|
|
+ const res = await detailList({
|
|
|
|
+ ip: parentRow.ip,
|
|
|
|
+ current: parentRow.childPagination.page,
|
|
|
|
+ size: parentRow.childPagination.size
|
|
|
|
+ });
|
|
|
|
+ const data = res.data;
|
|
|
|
+ parentRow.childTableData = data.records;
|
|
|
|
+ parentRow.childPagination.total = data.total;
|
|
|
|
+ } finally {
|
|
|
|
+ parentRow.childLoading = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 关闭所有二级表格,后执行对应函数
|
|
|
|
+ * @param callback 执行函数
|
|
|
|
+ * @param args 执行函数(参数)
|
|
|
|
+ */
|
|
|
|
+const withCollapsedChildren = (callback: Function, ...args: any[]) => {
|
|
|
|
+ expandedRowKeys.value = [];
|
|
|
|
+ return callback(...args);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 处理二级表格分页大小变化
|
|
|
|
+const handlePageSizeChange = (row: any, size: number) => {
|
|
|
|
+ row.childPagination.size = size;
|
|
|
|
+ row.childPagination.page = 1; // 重置为第一页
|
|
|
|
+ loadChildData(row);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 处理二级表格页码变化
|
|
|
|
+const handlePageChange = (row: any, page: number) => {
|
|
|
|
+ row.childPagination.page = page;
|
|
|
|
+ loadChildData(row);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 二级表格展开/关闭
|
|
|
|
+const handleExpandChange = (row: any, expandedRows: any[]) => {
|
|
|
|
+ expandedRowKeys.value = expandedRows.map(item => item.ip);
|
|
|
|
+ row.expanded = !row.expanded;
|
|
|
|
+
|
|
|
|
+ // 加载二级表格第1页数据
|
|
|
|
+ if (row.expanded && (!row.childTableData || row.childTableData.length === 0)) {
|
|
|
|
+ loadChildData(row);
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
// 暴露变量 只有暴漏出来的变量 父组件才能使用
|
|
// 暴露变量 只有暴漏出来的变量 父组件才能使用
|
|
defineExpose({
|
|
defineExpose({
|
|
openDialog,
|
|
openDialog,
|
|
});
|
|
});
|
|
-</script>
|
|
|
|
-<style>
|
|
|
|
-.config-container {
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- margin-bottom: 10px;
|
|
|
|
- font-size: 14px;
|
|
|
|
- font-weight: 400;
|
|
|
|
- width: 100%;
|
|
|
|
- justify-content: start;
|
|
|
|
-}
|
|
|
|
-.config-actions {
|
|
|
|
- width: 50px;
|
|
|
|
- display: flex;
|
|
|
|
- align-items: center;
|
|
|
|
- justify-content: space-between;
|
|
|
|
-}
|
|
|
|
|
|
|
|
-.apps-loadmore.el-select-dropdown .el-scrollbar__wrap {
|
|
|
|
- height: 330px !important;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-.statistics-table-wrapper {
|
|
|
|
- height: 70vh;
|
|
|
|
- position: relative;
|
|
|
|
|
|
+// 页面卸载时
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ clearInterval(setIntervalTime.value);
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
+.dialog-body {
|
|
|
|
+ padding: 0 0 10px 0;
|
|
|
|
+ .statistics-table-wrapper {
|
|
|
|
+ height: 70vh;
|
|
|
|
+ position: relative;
|
|
|
|
+ .child-table-container {
|
|
|
|
+ margin: 20px;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
</style>
|
|
</style>
|