269 lines
8.2 KiB
Vue
269 lines
8.2 KiB
Vue
<template>
|
||
<div class="generic-monitor-list">
|
||
<el-card shadow="never">
|
||
<el-form :inline="true" :model="filters" class="filter-form">
|
||
<el-form-item v-for="col in filterableColumns" :key="col.dataIndex" :label="col.title">
|
||
<template v-if="col.filterType === 'text'">
|
||
<el-input
|
||
v-model="filters[col.dataIndex]"
|
||
:placeholder="`搜索 ${col.title}`"
|
||
clearable
|
||
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
|
||
></el-input>
|
||
</template>
|
||
<template v-else-if="col.filterType === 'number'">
|
||
<el-input-number
|
||
v-model="filters[col.dataIndex]"
|
||
:placeholder="`搜索 ${col.title}`"
|
||
:controls="false"
|
||
clearable
|
||
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
|
||
></el-input-number>
|
||
</template>
|
||
<template v-else-if="col.filterType === 'dateRange'">
|
||
<el-date-picker
|
||
v-model="filters[col.dataIndex]"
|
||
type="datetimerange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
|
||
></el-date-picker>
|
||
</template>
|
||
<template v-else-if="col.filterType === 'select'">
|
||
<el-select
|
||
v-model="filters[col.dataIndex]"
|
||
:placeholder="`选择 ${col.title}`"
|
||
clearable
|
||
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
|
||
>
|
||
<el-option
|
||
v-for="option in col.filterOptions"
|
||
:key="option.value"
|
||
:label="option.text"
|
||
:value="option.value"
|
||
></el-option>
|
||
</el-select>
|
||
</template>
|
||
<template v-else-if="col.filterType === 'boolean'">
|
||
<el-select
|
||
v-model="filters[col.dataIndex]"
|
||
:placeholder="`选择 ${col.title}`"
|
||
clearable
|
||
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
|
||
>
|
||
<el-option label="是" :value="true"></el-option>
|
||
<el-option label="否" :value="false"></el-option>
|
||
</el-select>
|
||
</template>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="loadData">查询</el-button>
|
||
<el-button @click="resetFilters">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-table
|
||
:data="data"
|
||
v-loading="loading"
|
||
border
|
||
stripe
|
||
style="width: 100%"
|
||
table-layout="auto"
|
||
:fit="true"
|
||
:scrollbar-always-on="true"
|
||
@sort-change="handleSortChange"
|
||
>
|
||
<el-table-column
|
||
v-for="col in tableColumns"
|
||
:key="col.key"
|
||
:prop="col.prop"
|
||
:label="col.title"
|
||
:sortable="col.sorter ? 'custom' : false"
|
||
:formatter="col.formatter"
|
||
:min-width="col.minWidth"
|
||
>
|
||
<template v-if="col.render" #default="{ row }">
|
||
<component :is="col.render(row)"/>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<el-pagination
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
:current-page="pagination.currentPage"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:page-size="pagination.pageSize"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="pagination.total"
|
||
background
|
||
style="margin-top: 20px; text-align: right;"
|
||
></el-pagination>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {ref, reactive, onMounted, watch, computed} from 'vue';
|
||
import {ElMessage} from 'element-plus';
|
||
|
||
const props = defineProps({
|
||
fetchData: {
|
||
type: Function,
|
||
required: true,
|
||
},
|
||
columnsConfig: {
|
||
type: Array,
|
||
required: true,
|
||
},
|
||
});
|
||
|
||
const data = ref([]);
|
||
const loading = ref(false);
|
||
const pagination = reactive({
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
});
|
||
const filters = reactive({});
|
||
const sortOrder = reactive({
|
||
prop: undefined,
|
||
order: undefined,
|
||
});
|
||
|
||
const filterableColumns = computed(() => {
|
||
return props.columnsConfig.filter(col => col.filterType);
|
||
});
|
||
|
||
const tableColumns = computed(() => {
|
||
return props.columnsConfig.map(col => {
|
||
const newCol = {...col};
|
||
newCol.prop = Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : col.dataIndex;
|
||
|
||
// 添加智能默认 formatter
|
||
if (!newCol.formatter) {
|
||
newCol.formatter = (row, column, cellValue) => {
|
||
if (typeof cellValue === 'object' && cellValue !== null) {
|
||
try {
|
||
return JSON.stringify(cellValue, null, 2); // 格式化为可读的JSON字符串
|
||
} catch (e) {
|
||
console.warn('Failed to stringify object for display:', cellValue, e);
|
||
return '[Object]'; // 无法序列化时显示简短提示
|
||
}
|
||
} else if (Array.isArray(cellValue)) {
|
||
return cellValue.join(', '); // 数组也默认用逗号连接
|
||
}
|
||
return cellValue;
|
||
};
|
||
}
|
||
|
||
return newCol;
|
||
});
|
||
});
|
||
|
||
const loadData = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const params = {
|
||
page: pagination.currentPage,
|
||
page_size: pagination.pageSize, // Changed from pageSize to page_size
|
||
...filters,
|
||
orderBy: sortOrder.prop,
|
||
order: sortOrder.order === 'ascending' ? 'asc' : (sortOrder.order === 'descending' ? 'desc' : undefined),
|
||
};
|
||
|
||
// Custom function to format Date objects to YYYY-MM-DDTHH:mm:ssZ
|
||
const formatToRFC3339WithOffset = (date) => {
|
||
if (!date) return ''; // Handle null or undefined dates
|
||
const year = date.getUTCFullYear();
|
||
const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
|
||
const day = date.getUTCDate().toString().padStart(2, '0');
|
||
const hours = date.getUTCHours().toString().padStart(2, '0');
|
||
const minutes = date.getUTCMinutes().toString().padStart(2, '0');
|
||
const seconds = date.getUTCSeconds().toString().padStart(2, '0');
|
||
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z`;
|
||
};
|
||
|
||
// 将日期范围筛选转换为 start_time 和 end_time,并确保是 RFC3339 UTC 格式 (不带毫秒)
|
||
filterableColumns.value.forEach(col => {
|
||
if (col.filterType === 'dateRange' && filters[col.dataIndex] && filters[col.dataIndex].length === 2) {
|
||
// filters[col.dataIndex] will now contain Date objects directly from el-date-picker
|
||
const startDateObj = filters[col.dataIndex][0];
|
||
const endDateObj = filters[col.dataIndex][1];
|
||
|
||
params[`start_time`] = formatToRFC3339WithOffset(startDateObj);
|
||
params[`end_time`] = formatToRFC3339WithOffset(endDateObj);
|
||
delete params[col.dataIndex];
|
||
}
|
||
});
|
||
|
||
console.log('Sending parameters to fetchData:', params);
|
||
|
||
const result = await props.fetchData(params);
|
||
data.value = result.list;
|
||
pagination.total = result.total;
|
||
} catch (error) {
|
||
console.error('Failed to fetch data:', error);
|
||
ElMessage.error('获取数据失败,请稍后再试。');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleSizeChange = (val) => {
|
||
pagination.pageSize = val;
|
||
pagination.currentPage = 1;
|
||
loadData();
|
||
};
|
||
|
||
const handleCurrentChange = (val) => {
|
||
pagination.currentPage = val;
|
||
loadData();
|
||
};
|
||
|
||
const handleSortChange = ({prop, order}) => {
|
||
sortOrder.prop = prop;
|
||
sortOrder.order = order;
|
||
loadData();
|
||
};
|
||
|
||
const handleFilterChange = (key, value) => {
|
||
filters[key] = value;
|
||
pagination.currentPage = 1;
|
||
};
|
||
|
||
const resetFilters = () => {
|
||
for (const key in filters) {
|
||
delete filters[key];
|
||
}
|
||
sortOrder.prop = undefined;
|
||
sortOrder.order = undefined;
|
||
pagination.currentPage = 1;
|
||
loadData();
|
||
};
|
||
|
||
onMounted(() => {
|
||
loadData();
|
||
});
|
||
|
||
</script>
|
||
|
||
<style scoped>
|
||
.generic-monitor-list {
|
||
padding: 20px;
|
||
}
|
||
|
||
.filter-form {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.filter-form .el-form-item {
|
||
min-width: 220px; /* 增加最小宽度 */
|
||
}
|
||
|
||
.el-card {
|
||
border: none;
|
||
}
|
||
</style>
|