Files
FuelLog/fuellog-web/src/pages/Vehicles.vue
T
KennethCheng 9cec8a7f66 feat(frontend): 车辆管理页(列表 + 新增/编辑/删除/默认切换)
把 Vehicles 页从空壳升级成真业务:
- 卡片式列表,移动端单列、桌面端两列网格
- 每张卡片展示名称/默认标签/品牌+型号+车牌/能源类型/油箱/里程
- 操作:编辑 / 设为默认 / 删除(NPopconfirm 二次确认;走 NDialog.warning)
- 顶部「+ 添加」按钮,弹 NModal 表单
- 表单:名称必填、5 个可选字段、NSwitch 切默认
- 提交走 vehiclesApi.create/update,刷新 store
- 删除用 NDialog.warning(避免 Popconfirm 误触)
- Loading / Empty / Error 状态完整

新增/改动:
- src/components/VehicleFormModal.vue  NModal + NForm + NInput/NInputNumber/NSwitch
  * watch props.show / editing 重置表单
  * isEdit 复用同一份表单(create / update 双模式)
  * 内联 FuelSelect(简单 NSelect 包装)
  * useMessage 反馈成功/失败
- src/pages/Vehicles.vue  替换占位 Empty
  * onMounted 触发 store.loadList
  * openCreate / openEdit 控制 showForm + editing
  * onSaved 自动切到刚保存的车(UX:加完车立刻能进 Dashboard 看到统计)
  * setDefault -> store.setDefault
  * remove -> dialog.warning + store.remove
- src/stores/vehicle.ts  暴露 setCurrentId(id) 给 UI 切换当前车

未做(留待后续):
- 搜索 / 排序 / 分页:单车辆量 < 100,前两版没必要
- 列表项的「最近一次加油」摘要:留给 Dashboard 拿

验证:
  pnpm typecheck   0 errors
  pnpm build       12.5s,dist 重新生成,Dashboard / Vehicles 各自分 chunk
2026-09-13 16:44:42 +08:00

135 lines
3.8 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.
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import {
NButton,
NCard,
NEmpty,
NPopconfirm,
NSpin,
NTag,
useDialog,
useMessage,
} from 'naive-ui'
import { vehiclesApi } from '@/api/vehicles'
import { useVehicleStore } from '@/stores/vehicle'
import type { Vehicle } from '@/api/types'
import VehicleFormModal from '@/components/VehicleFormModal.vue'
const store = useVehicleStore()
const message = useMessage()
const dialog = useDialog()
const showForm = ref(false)
const editing = ref<Vehicle | null>(null)
onMounted(() => store.loadList())
function openCreate() {
editing.value = null
showForm.value = true
}
function openEdit(v: Vehicle) {
editing.value = v
showForm.value = true
}
async function onSaved(v: Vehicle) {
await store.loadList()
store.setCurrentId(v.id)
}
async function setDefault(v: Vehicle) {
try {
await store.setDefault(v.id)
message.success('已设为默认车辆')
} catch (err) {
message.error((err as Error).message || '设置失败')
}
}
async function remove(v: Vehicle) {
dialog.warning({
title: '确认删除',
content: `车辆「${v.name}」删除后无法恢复,确定吗?`,
positiveText: '删除',
negativeText: '取消',
onPositiveClick: async () => {
try {
await store.remove(v.id)
message.success('已删除')
} catch (err) {
message.error((err as Error).message || '删除失败')
}
},
})
}
</script>
<template>
<div class="max-w-screen-md mx-auto space-y-3">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold">车辆</h2>
<NButton type="primary" @click="openCreate">+ 添加</NButton>
</div>
<NSpin :show="store.loading">
<NEmpty
v-if="!store.loading && !store.list.length"
description="还没有车辆,点右上角加一辆"
/>
<div v-else class="grid gap-3 sm:grid-cols-2">
<NCard
v-for="v in store.list"
:key="v.id"
hoverable
class="!rounded-2xl"
>
<div class="flex items-start justify-between gap-2">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
<span class="text-lg font-semibold truncate">{{ v.name }}</span>
<NTag v-if="v.default_vehicle" type="success" size="small">默认</NTag>
</div>
<div class="text-sm text-ink-soft mt-1 truncate">
{{ v.brand || '—' }} {{ v.model }}
<span v-if="v.plate_number"> · {{ v.plate_number }}</span>
</div>
<div class="text-xs text-ink-soft mt-2 flex gap-3 flex-wrap">
<span>{{ v.fuel_type }}</span>
<span v-if="v.tank_capacity">油箱 {{ v.tank_capacity }} L</span>
<span v-if="v.current_odometer">{{ v.current_odometer }} km</span>
</div>
</div>
</div>
<div class="flex gap-2 mt-3 flex-wrap">
<NButton size="small" @click="openEdit(v)">编辑</NButton>
<NButton
v-if="!v.default_vehicle"
size="small"
type="primary"
ghost
@click="setDefault(v)"
>
设为默认
</NButton>
<NPopconfirm
@positive-click="remove(v)"
positive-text="删除"
negative-text="取消"
>
<template #trigger>
<NButton size="small" type="error" ghost>删除</NButton>
</template>
确认删除「{{ v.name }}」?
</NPopconfirm>
</div>
</NCard>
</div>
</NSpin>
<VehicleFormModal v-model:show="showForm" :editing="editing" @saved="onSaved" />
</div>
</template>