Files
pig-farm-controller-fe/src/components/GenericMonitorList.vue

248 lines
7.1 KiB
Vue
Raw Normal View History

2025-10-20 15:48:08 +08:00
<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
2025-10-20 16:14:59 +08:00
v-model="filters[col.dataIndex]"
:placeholder="`搜索 ${col.title}`"
clearable
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
2025-10-20 15:48:08 +08:00
></el-input>
</template>
<template v-else-if="col.filterType === 'number'">
<el-input-number
2025-10-20 16:14:59 +08:00
v-model="filters[col.dataIndex]"
:placeholder="`搜索 ${col.title}`"
:controls="false"
clearable
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
2025-10-20 15:48:08 +08:00
></el-input-number>
</template>
<template v-else-if="col.filterType === 'dateRange'">
<el-date-picker
2025-10-20 16:14:59 +08:00
v-model="filters[col.dataIndex]"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
2025-10-20 15:48:08 +08:00
></el-date-picker>
</template>
<template v-else-if="col.filterType === 'select'">
<el-select
2025-10-20 16:14:59 +08:00
v-model="filters[col.dataIndex]"
:placeholder="`选择 ${col.title}`"
clearable
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
2025-10-20 15:48:08 +08:00
>
<el-option
2025-10-20 16:14:59 +08:00
v-for="option in col.filterOptions"
:key="option.value"
:label="option.text"
:value="option.value"
2025-10-20 15:48:08 +08:00
></el-option>
</el-select>
</template>
<template v-else-if="col.filterType === 'boolean'">
<el-select
2025-10-20 16:14:59 +08:00
v-model="filters[col.dataIndex]"
:placeholder="`选择 ${col.title}`"
clearable
@change="handleFilterChange(col.dataIndex, filters[col.dataIndex])"
2025-10-20 15:48:08 +08:00
>
<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
2025-10-20 16:14:59 +08:00
:data="data"
v-loading="loading"
border
stripe
style="width: 100%"
2025-10-20 19:22:23 +08:00
table-layout="auto"
:fit="true"
:scrollbar-always-on="true"
2025-10-20 16:14:59 +08:00
@sort-change="handleSortChange"
2025-10-20 15:48:08 +08:00
>
<el-table-column
2025-10-20 16:14:59 +08:00
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"
2025-10-20 15:48:08 +08:00
>
<template v-if="col.render" #default="{ row }">
2025-10-20 16:14:59 +08:00
<component :is="col.render(row)"/>
2025-10-20 15:48:08 +08:00
</template>
</el-table-column>
</el-table>
<el-pagination
2025-10-20 16:14:59 +08:00
@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;"
2025-10-20 15:48:08 +08:00
></el-pagination>
</el-card>
</div>
</template>
<script setup>
2025-10-20 16:14:59 +08:00
import {ref, reactive, onMounted, watch, computed} from 'vue';
import {ElMessage} from 'element-plus';
2025-10-20 15:48:08 +08:00
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 => {
2025-10-20 16:14:59 +08:00
const newCol = {...col};
2025-10-20 15:48:08 +08:00
newCol.prop = Array.isArray(col.dataIndex) ? col.dataIndex.join('.') : col.dataIndex;
2025-10-20 19:22:23 +08:00
// 添加智能默认 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;
};
}
2025-10-20 15:48:08 +08:00
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),
};
// 将日期范围筛选转换为 start_time 和 end_time
filterableColumns.value.forEach(col => {
if (col.filterType === 'dateRange' && filters[col.dataIndex] && filters[col.dataIndex].length === 2) {
params[`start_time`] = filters[col.dataIndex][0];
params[`end_time`] = filters[col.dataIndex][1];
delete params[col.dataIndex];
}
});
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();
};
2025-10-20 16:14:59 +08:00
const handleSortChange = ({prop, order}) => {
2025-10-20 15:48:08 +08:00
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;
}
2025-10-20 16:14:59 +08:00
2025-10-20 15:48:08 +08:00
.filter-form {
margin-bottom: 20px;
}
2025-10-20 16:14:59 +08:00
2025-10-20 15:48:08 +08:00
.el-card {
2025-10-20 16:14:59 +08:00
border: none;
2025-10-20 15:48:08 +08:00
}
</style>