Files
pig-farm-controller-fe/src/components/GenericMonitorList.vue
2025-10-22 15:17:44 +08:00

269 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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,
pageSize: pagination.pageSize,
...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>