把 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
135 lines
3.8 KiB
Vue
135 lines
3.8 KiB
Vue
<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>
|