| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">车è¾ç¼å·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.vehicleCode" |
| | | style="width: 200px" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | clearable |
| | | @keyup.enter.native="handleQuery" |
| | | /> |
| | | |
| | | <span class="search_title ml10">å æ²¹æ¶é´ï¼</span> |
| | | <el-date-picker |
| | | v-model="searchForm.dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | @change="handleQuery" |
| | | /> |
| | | |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" icon="Plus" @click="openAdd"> |
| | | æ°å¢å æ²¹è®°å½ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¡¨æ ¼ --> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | :row-class-name="tableRowClassName" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column |
| | | prop="vehicleCode" |
| | | label="车è¾ç¼å·" |
| | | width="130" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="plateNumber" |
| | | label="车çå·ç " |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="fuelDate" |
| | | label="å æ²¹æ¥æ" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | prop="gunNo" |
| | | label="æ²¹æªå·" |
| | | width="90" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | prop="amount" |
| | | label="éé¢(å
)" |
| | | width="100" |
| | | align="right" |
| | | > |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.amount?.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="liters" |
| | | label="åæ°(L)" |
| | | width="90" |
| | | align="right" |
| | | > |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.liters?.toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="startMileage" |
| | | label="èµ·å§éç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="endMileage" |
| | | label="ç»æéç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="distance" |
| | | label="è¡é©¶éç¨(km)" |
| | | width="120" |
| | | align="right" |
| | | /> |
| | | <el-table-column |
| | | prop="fuelConsumption" |
| | | label="æ²¹è(L/100km)" |
| | | width="130" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <span |
| | | :style="scope.row.isAbnormal ? 'color:#F56C6C;font-weight:600;' : ''" |
| | | > |
| | | {{ scope.row.fuelConsumption != null ? scope.row.fuelConsumption.toFixed(2) : '-' }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | prop="avgConsumption" |
| | | label="车è¾å¹³åæ²¹è" |
| | | width="130" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <span> |
| | | {{ scope.row.avgConsumption != null ? scope.row.avgConsumption.toFixed(2) : '-' }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å¼å¸¸é¢è¦" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="scope.row.isAbnormal" type="danger" size="small"> |
| | | å¼å¸¸ |
| | | </el-tag> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" fixed="right" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | @click="openEdit(scope.row)" |
| | | > |
| | | ç¼è¾ |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | @click="removeRow(scope.row)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="640px" |
| | | destroy-on-close |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110px" |
| | | label-position="right" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车è¾ç¼å·ï¼" prop="vehicleCode"> |
| | | <el-input |
| | | v-model="form.vehicleCode" |
| | | placeholder="请è¾å
¥è½¦è¾ç¼å·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车çå·ç ï¼" prop="plateNumber"> |
| | | <el-input |
| | | v-model="form.plateNumber" |
| | | placeholder="请è¾å
¥è½¦çå·ç " |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å æ²¹æ¥æï¼" prop="fuelDate"> |
| | | <el-date-picker |
| | | v-model="form.fuelDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="è¯·éæ©å æ²¹æ¥æ" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ²¹æªå·ï¼" prop="gunNo"> |
| | | <el-input |
| | | v-model="form.gunNo" |
| | | placeholder="请è¾å
¥æ²¹æªå·" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éé¢(å
)ï¼" prop="amount"> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | placeholder="请è¾å
¥éé¢" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åæ°(L)ï¼" prop="liters"> |
| | | <el-input-number |
| | | v-model="form.liters" |
| | | :min="0" |
| | | :step="0.01" |
| | | :precision="2" |
| | | placeholder="请è¾å
¥åæ°" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµ·å§éç¨(km)ï¼" prop="startMileage"> |
| | | <el-input-number |
| | | v-model="form.startMileage" |
| | | :min="0" |
| | | :step="1" |
| | | placeholder="请è¾å
¥èµ·å§éç¨" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»æéç¨(km)ï¼" prop="endMileage"> |
| | | <el-input-number |
| | | v-model="form.endMileage" |
| | | :min="0" |
| | | :step="1" |
| | | placeholder="请è¾å
¥ç»æéç¨" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="handleCancel">å æ¶</el-button> |
| | | <el-button type="primary" @click="handleSubmit">ä¿ å</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | |
| | | // 模æå æ²¹è®°å½æ°æ® |
| | | const rawRecords = ref([ |
| | | { |
| | | id: 1, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2024-12-01", |
| | | gunNo: "01", |
| | | amount: 500, |
| | | liters: 70, |
| | | startMileage: 12000, |
| | | endMileage: 12600, |
| | | }, |
| | | { |
| | | id: 2, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2024-12-15", |
| | | gunNo: "02", |
| | | amount: 520, |
| | | liters: 72, |
| | | startMileage: 12600, |
| | | endMileage: 13250, |
| | | }, |
| | | { |
| | | id: 3, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | fuelDate: "2024-12-05", |
| | | gunNo: "03", |
| | | amount: 430, |
| | | liters: 60, |
| | | startMileage: 8000, |
| | | endMileage: 8520, |
| | | }, |
| | | { |
| | | id: 4, |
| | | vehicleCode: "CL-202402", |
| | | plateNumber: "粤B67890", |
| | | fuelDate: "2024-12-20", |
| | | gunNo: "01", |
| | | amount: 450, |
| | | liters: 63, |
| | | startMileage: 8520, |
| | | endMileage: 9000, |
| | | }, |
| | | { |
| | | id: 5, |
| | | vehicleCode: "CL-202401", |
| | | plateNumber: "粤A12345", |
| | | fuelDate: "2025-01-05", |
| | | gunNo: "01", |
| | | amount: 700, |
| | | liters: 90, |
| | | startMileage: 13250, |
| | | endMileage: 13600, // ææ¾å¼å¸¸æ²¹è |
| | | }, |
| | | ]); |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | vehicleCode: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | // è¡¨æ ¼æ°æ®ï¼å
å«è®¡ç®åæ®µï¼ |
| | | const tableData = ref([]); |
| | | |
| | | // å¼¹çª & 表å |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref("æ°å¢å 油记å½"); |
| | | const isEdit = ref(false); |
| | | const formRef = ref(null); |
| | | const form = reactive({ |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | fuelDate: "", |
| | | gunNo: "", |
| | | amount: null, |
| | | liters: null, |
| | | startMileage: null, |
| | | endMileage: null, |
| | | }); |
| | | |
| | | const rules = { |
| | | vehicleCode: [{ required: true, message: "请è¾å
¥è½¦è¾ç¼å·", trigger: "blur" }], |
| | | plateNumber: [{ required: true, message: "请è¾å
¥è½¦çå·ç ", trigger: "blur" }], |
| | | fuelDate: [{ required: true, message: "è¯·éæ©å æ²¹æ¥æ", trigger: "change" }], |
| | | gunNo: [{ required: true, message: "请è¾å
¥æ²¹æªå·", trigger: "blur" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | liters: [{ required: true, message: "请è¾å
¥åæ°", trigger: "blur" }], |
| | | startMileage: [{ required: true, message: "请è¾å
¥èµ·å§éç¨", trigger: "blur" }], |
| | | endMileage: [{ required: true, message: "请è¾å
¥ç»æéç¨", trigger: "blur" }], |
| | | }; |
| | | |
| | | // éæ°è®¡ç®æ²¹èã平忲¹èåå¼å¸¸é¢è¦ |
| | | const recomputeTable = () => { |
| | | const records = rawRecords.value; |
| | | |
| | | // 1. å
æè½¦è¾ç»è®¡å¹³åæ²¹è |
| | | const stats = {}; |
| | | records.forEach((r) => { |
| | | const distance = r.endMileage - r.startMileage; |
| | | if (distance <= 0 || !r.liters) return; |
| | | const cons = (r.liters / distance) * 100; // L/100km |
| | | if (!stats[r.vehicleCode]) { |
| | | stats[r.vehicleCode] = { totalCons: 0, count: 0 }; |
| | | } |
| | | stats[r.vehicleCode].totalCons += cons; |
| | | stats[r.vehicleCode].count += 1; |
| | | }); |
| | | |
| | | const avgMap = {}; |
| | | Object.keys(stats).forEach((key) => { |
| | | avgMap[key] = stats[key].totalCons / stats[key].count; |
| | | }); |
| | | |
| | | // 2. æçéæ¡ä»¶è¿æ»¤å¹¶è¡¥å
计ç®å段 |
| | | const filtered = records |
| | | .filter((r) => { |
| | | if ( |
| | | searchForm.vehicleCode && |
| | | !r.vehicleCode.includes(searchForm.vehicleCode.trim()) |
| | | ) { |
| | | return false; |
| | | } |
| | | if (Array.isArray(searchForm.dateRange) && searchForm.dateRange.length === 2) { |
| | | const [start, end] = searchForm.dateRange; |
| | | if (r.fuelDate < start || r.fuelDate > end) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | }) |
| | | .map((r) => { |
| | | const distance = r.endMileage - r.startMileage; |
| | | const fuelConsumption = |
| | | distance > 0 && r.liters |
| | | ? (r.liters / distance) * 100 |
| | | : null; |
| | | const avgConsumption = |
| | | avgMap[r.vehicleCode] != null ? avgMap[r.vehicleCode] : null; |
| | | const isAbnormal = |
| | | avgConsumption != null && |
| | | fuelConsumption != null && |
| | | fuelConsumption > avgConsumption * 1.2; |
| | | |
| | | return { |
| | | ...r, |
| | | distance, |
| | | fuelConsumption, |
| | | avgConsumption, |
| | | isAbnormal, |
| | | }; |
| | | }); |
| | | |
| | | tableData.value = filtered; |
| | | }; |
| | | |
| | | // æ¥è¯¢ |
| | | const handleQuery = () => { |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | const resetSearch = () => { |
| | | searchForm.vehicleCode = ""; |
| | | searchForm.dateRange = []; |
| | | recomputeTable(); |
| | | }; |
| | | |
| | | // è¡æ ·å¼ï¼å¼å¸¸é«äº®ï¼ |
| | | const tableRowClassName = ({ row }) => { |
| | | if (row.isAbnormal) { |
| | | return "row-abnormal"; |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | // æ°å¢ |
| | | const openAdd = () => { |
| | | dialogTitle.value = "æ°å¢å 油记å½"; |
| | | isEdit.value = false; |
| | | Object.assign(form, { |
| | | id: null, |
| | | vehicleCode: "", |
| | | plateNumber: "", |
| | | fuelDate: "", |
| | | gunNo: "", |
| | | amount: null, |
| | | liters: null, |
| | | startMileage: null, |
| | | endMileage: null, |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ç¼è¾ |
| | | const openEdit = (row) => { |
| | | dialogTitle.value = "ç¼è¾å 油记å½"; |
| | | isEdit.value = true; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¿å |
| | | const handleSubmit = () => { |
| | | if (!formRef.value) return; |
| | | formRef.value.validate((valid) => { |
| | | if (!valid) return; |
| | | |
| | | if (form.endMileage <= form.startMileage) { |
| | | ElMessage.warning("ç»æéç¨å¿
须大äºèµ·å§éç¨"); |
| | | return; |
| | | } |
| | | |
| | | if (isEdit.value) { |
| | | const index = rawRecords.value.findIndex((r) => r.id === form.id); |
| | | if (index !== -1) { |
| | | rawRecords.value[index] = { ...form }; |
| | | } |
| | | ElMessage.success("å æ²¹è®°å½å·²æ´æ°"); |
| | | } else { |
| | | const newId = rawRecords.value.length |
| | | ? Math.max(...rawRecords.value.map((r) => r.id)) + 1 |
| | | : 1; |
| | | rawRecords.value.push({ ...form, id: newId }); |
| | | ElMessage.success("å æ²¹è®°å½å·²æ°å¢"); |
| | | } |
| | | dialogVisible.value = false; |
| | | recomputeTable(); |
| | | }); |
| | | }; |
| | | |
| | | const handleCancel = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const removeRow = (row) => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å é¤è¯¥å 油记å½ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | rawRecords.value = rawRecords.value.filter((r) => r.id !== row.id); |
| | | recomputeTable(); |
| | | ElMessage.success("å 餿å"); |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | recomputeTable(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | |
| | | :deep(.row-abnormal) { |
| | | background-color: #fff5f5; |
| | | } |
| | | </style> |
| | | |