记录一个基本的车辆信息管理页面,由豆包撰写完成,只需要微调页面即可。
主要功能是车辆信息的查询、新增、编辑,项目用到了uniapp、vue3、ts、uni-ui、z-paging
页面效果如下:
以上界面均由豆包生成,完成度非常高,增、删、查、改都实现了,加上组件的能力如z-paging、uni-ui组件库,列表的分页查询、弹出窗等为用户带来良好的交互体验。
以下是前端源码(90%豆包+10%人工)
<template>
<z-paging ref="zPageRef" @query="fetchVehicleList" v-model="dataList">
<!-- 搜索和筛选 -->
<template #top>
<!-- 头部导航 -->
<view class="header">
<view class="search-bar">
<uni-search-bar v-model="searchQuery" placeholder="搜索车牌号码、车主姓名或品牌"
@confirm="handleSearch"></uni-search-bar>
</view>
<button class="add-btn" @click="showAddModal">
<uni-icons type="plusempty" size="24" color="#0f80ff"></uni-icons>
<text>新增车辆</text>
</button>
</view>
</template>
<!-- 车辆列表 -->
<view class="vehicle-management">
<view class="vehicle-list">
<view class="vehicle-items">
<view class="vehicle-item" v-for="(vehicle, index) in dataList" :key="index">
<view class="vehicle-info">
<view class="plate-number">
<text>{{ vehicle.carNum }}</text>
<uni-tag :type="vehicle.type=='商户' ? 'primary' : 'success'"
:text="vehicle.type=='商户' ? '商户' : '内部员工'"></uni-tag>
</view>
<view class="basic-info">
<text class="name">车主: {{ vehicle.name }}</text>
<text class="tel">电话: {{ vehicle.tel }}</text>
</view>
<view class="basic-info">
<text class="tel">职务: {{ vehicle.duty }}</text>
</view>
<view class="basic-info">
<text class="tel">商户名称: {{ vehicle.brandName }}</text>
<text class="tel">展位号: {{ vehicle.boothNum }}</text>
</view>
</view>
<view class="vehicle-actions">
<button class="view-btn" @click="viewVehicle(vehicle)">
<uni-icons type="info" size="20" color="#0f80ff"></uni-icons>
</button>
<button class="edit-btn" @click="editVehicle(vehicle)">
<uni-icons type="compose" size="20" color="#0f80ff"></uni-icons>
</button>
<button class="delete-btn" @click="confirmDelete(vehicle)">
<uni-icons type="trash" size="20" color="#ff4500"></uni-icons>
</button>
</view>
</view>
</view>
</view>
<!-- 车辆表单模态框 -->
<uni-popup ref="vehicleFormPopup" type="center" :custom-style="popupStyle">
<view class="vehicle-form">
<view class="form-header">
<text class="form-title">{{ isEditing ? '编辑车辆信息' : '新增车辆信息' }}</text>
<uni-icons type="closeempty" size="24" color="#666" @click="closeFormModal"></uni-icons>
</view>
<view class="form-content">
<!-- 基本信息 -->
<view class="form-section">
<view class="form-item">
<text class="item-label">车牌号码</text>
<view class="plate-input">
<picker :range="provinces" @change="onProvinceChange">
<view class="province-selector">
<text>{{ formData.province }}</text>
<uni-icons type="arrowdown" size="18" color="#999"></uni-icons>
</view>
</picker>
<input v-model="formData.carNum" placeholder="请输入车牌号码" maxlength="6" />
</view>
</view>
<view class="form-item">
<text class="item-label">车主姓名</text>
<input v-model="formData.name" placeholder="请输入车主姓名" />
</view>
<view class="form-item">
<text class="item-label">联系电话</text>
<input v-model="formData.tel" placeholder="请输入联系电话" type="number" />
</view>
<view class="form-item">
<text class="item-label">车辆类型</text>
<picker :range="types" @change="onTypeChange">
<view class="type-selector">
<text>{{ formData.type || '请选择车辆类型' }}</text>
<uni-icons type="arrowdown" size="18" color="#999"></uni-icons>
</view>
</picker>
</view>
</view>
<!-- 商户 -->
<view v-if="formData.type=='商户'" class="form-section">
<view class="form-item">
<text class="item-label">商户名称/品牌:</text>
<input v-model="formData.brandName" placeholder="商户名称/品牌" />
</view>
<view class="form-item">
<text class="item-label">展位号</text>
<input v-model="formData.boothNum" placeholder="请输入展位号" />
</view>
</view>
<!-- 内部员工 -->
<view v-if="formData.type=='内部员工'" class="form-section">
<view class="form-item">
<text class="item-label">职务</text>
<input v-model="formData.duty" placeholder="请输入职务" />
</view>
</view>
<view class="form-actions">
<button class="cancel-btn" @click="closeFormModal">取消</button>
<button class="save-btn" @click="saveVehicle">保存</button>
</view>
</view>
</view>
</uni-popup>
<!-- 车辆详情模态框 -->
<uni-popup ref="vehicleDetailPopup" type="bottom" :custom-style="popupStyle">
<view class="vehicle-detail">
<view class="detail-header">
<text class="detail-title">车辆详情</text>
<uni-icons type="closeempty" size="24" color="#666" @click="closeDetailModal"></uni-icons>
</view>
<view class="detail-content">
<view class="detail-info">
<view class="plate-number">
<text>{{ selectedVehicle.carNum }}</text>
<uni-tag :type="selectedVehicle.brandName ? 'primary' : 'success'"
:text="selectedVehicle.brandName ? '商户' : '内部员工'"></uni-tag>
</view>
<view class="info-group">
<view class="info-item">
<text class="info-label">车主姓名:</text>
<text class="info-value">{{ selectedVehicle.name }}</text>
</view>
<view class="info-item">
<text class="info-label">联系电话:</text>
<text class="info-value">{{ selectedVehicle.tel }}</text>
</view>
<view v-if="selectedVehicle.brandName" class="">
<view class="info-item">
<text class="info-label">商户品牌:</text>
<text class="info-value">{{ selectedVehicle.brand }}</text>
</view>
<view class="info-item">
<text class="info-label">展位号:</text>
<text class="info-value">{{ selectedVehicle.boothNum }}</text>
</view>
</view>
<view v-else class="">
<view class="info-item">
<text class="info-label">职务:</text>
<text class="info-value">{{ selectedVehicle.duty }}</text>
</view>
</view>
<view class="info-item">
<text class="info-label">登记时间:</text>
<text class="info-value">{{ formatDate(selectedVehicle.inputtime*1000) }}</text>
</view>
<view class="info-item">
<text class="info-label">违规次数:</text>
<text class="info-value">{{ selectedVehicle.violationNum || '无' }}</text>
</view>
</view>
</view>
<!-- 违规记录 -->
<view class="violation-records"
v-if="selectedVehicle.violations && selectedVehicle.violations.length > 0">
<text class="records-title">违规记录 ({{ selectedVehicle.violations.length }})</text>
<view class="record-item" v-for="(violation, index) in selectedVehicle.violations"
:key="index">
<view class="record-header">
<text class="record-time">{{ violation.time }}</text>
<text
class="record-type {{ violation.type === '占用多位' ? 'type-multiple' : 'type-forbidden' }}">
{{ violation.type }}
</text>
</view>
<text class="record-location">位置: {{ violation.location }}</text>
<text class="record-punishment">处理: {{ violation.punishment }}</text>
</view>
</view>
</view>
<view class="detail-actions">
<button class="edit-btn" @click="editFromDetail">编辑信息</button>
<button class="violation-btn" @click="reportViolation">上报违规</button>
</view>
</view>
</uni-popup>
<!-- 自定义底部导航 -->
<CustomTabBar :tabList="tabList" :activeColor="'#409EFF'" :normalColor="'#666'" />
</view>
</z-paging>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch, computed } from 'vue';
import { useRouter } from 'vue-router';
import CustomTabBar from '@/components/CustomTabBar.vue'
import request from '../../../utils/http2';
import { formatDate } from '@/utils/date'
// 页面数据
const searchQuery = ref('');
const selectedVehicleType = ref('');
const selectedStatus = ref('');
const zPageRef = ref();
const vehicleFormPopup = ref(null);
const vehicleDetailPopup = ref(null);
const popupStyle = ref('height: 60vh; border-radius: 20rpx 20rpx 0 0;');
const dataList = ref({})
// 数据配置
const provinces = ref([
'京', '津', '冀', '晋', '蒙', '辽', '吉', '黑',
'沪', '苏', '浙', '皖', '闽', '赣', '鲁', '豫',
'鄂', '湘', '粤', '桂', '琼', '渝', '川', '贵',
'云', '藏', '陕', '甘', '青', '宁', '新'
]);
const types = ['商户', '内部员工'];
const statusOptions = ref(['全部', '正常', '违规', '黑名单']);
// 底部导航配置
const tabList = ref([
{
name: 'record',
path: '/pages/ffep/car/car',
text: '过夜登记',
icon: 'checkbox',
activeIcon: 'checkbox-filled'
},
{
name: 'violation',
path: 'parkingViolation',
text: '违规登记',
icon: 'close',
activeIcon: 'clear'
},
{
name: 'manager',
path: 'manager',
text: '车辆管理',
icon: 'calendar',
activeIcon: 'calendar-filled'
},
{
name: 'statistics',
path: '/pages/ffep/car/statistics',
text: '统计',
icon: 'info',
activeIcon: 'info-filled'
}
])
// 表单数据
const formData = reactive({
id: '',
province: '粤',
carNum: '',
name: '',
tel: '',
duty: '',
brandName: '',
boothNum: '',
status: '正常',
remark: '',
registerTime: '',
type: ''
});
// 选中的车辆
const selectedVehicle = reactive({
id: '',
carNum: '',
name: '',
tel: '',
duty: '',
brandName: '',
boothNum: '',
status: '',
violations:[]
});
// 状态
const isEditing = ref(false);
const loadingText = ref('加载中...');
const onTypeChange = (e) => {
formData.type = types[e.detail.value]
}
// 加载车辆列表
const fetchVehicleList = async (pageIndex : number, pageSize : number) => {
try {
// 构建查询参数
const params = {
pageNo: pageIndex,
pageSize: pageSize,
keyword: searchQuery.value,
duty: selectedVehicleType.value || undefined,
status: selectedStatus.value || undefined
};
// 调用API获取车辆列表
const res = await request({
url: '/api/ffep/parking/carlist',
method: 'GET',
data: params
});
if (res.code === 1) {
zPageRef.value.complete(res.data)
} else {
uni.showToast({
title: res.msg || '获取数据失败',
icon: 'none'
});
zPageRef.value.complete(false);
}
} catch (error) {
console.error('获取车辆列表失败', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
zPageRef.value.complete(false);
}
};
// 刷新列表
const onRefresh = () => {
zPageRef.value?.refresh();
};
// 加载更多
const onLoadMore = () => {
zPageRef.value?.loadMore();
};
// 处理搜索
const handleSearch = () => {
onRefresh();
};
// 显示新增车辆模态框
const showAddModal = () => {
// 重置表单
resetForm();
isEditing.value = false;
vehicleFormPopup.value.open();
};
// 查看车辆详情
const viewVehicle = (vehicle : any) => {
// 复制车辆数据到详情对象
Object.assign(selectedVehicle, vehicle);
// 获取车辆违规记录
getVehicleViolations(vehicle.carNum);
vehicleDetailPopup.value.open();
};
// 获取车辆违规记录
const getVehicleViolations = async (carNum : string) => {
const res = await request({
url: `/api/ffep/parking/getVehicleViolations`,
method: 'POST',
data: {
carNum: carNum
}
});
if (res.code === 1) {
selectedVehicle.violations = res.data || [];
} else {
selectedVehicle.violations = [];
uni.showToast({
title: res.msg || '获取违规记录失败',
icon: 'none'
});
}
};
// 编辑车辆
const editVehicle = (vehicle : any) => {
// 复制车辆数据到表单
Object.assign(formData, vehicle);
isEditing.value = true;
vehicleFormPopup.value.open();
};
// 从详情页编辑
const editFromDetail = () => {
// 复制详情数据到表单
Object.assign(formData, selectedVehicle);
isEditing.value = true;
// 关闭详情模态框,打开表单模态框
vehicleDetailPopup.value.close();
setTimeout(() => {
vehicleFormPopup.value.open();
}, 300);
};
// 上报违规
const reportViolation = () => {
// 关闭详情模态框
vehicleDetailPopup.value.close();
// 跳转到违规上报页面,传递车辆ID
uni.navigateTo({
url: `/pages/violation/report?vehicleId=${selectedVehicle.id}`
});
};
// 表单相关事件处理
const onProvinceChange = (e : any) => {
formData.province = provinces.value[e.detail.value];
};
// 重置表单
const resetForm = () => {
formData.id = '';
formData.province = '粤';
formData.carNum = '';
formData.name = '';
formData.tel = '';
formData.duty = '';
formData.brand = '';
formData.color = '';
formData.status = '正常';
formData.remark = '';
formData.registerTime = '';
};
// 关闭表单模态框
const closeFormModal = () => {
vehicleFormPopup.value.close();
};
// 关闭详情模态框
const closeDetailModal = () => {
vehicleDetailPopup.value.close();
};
// 保存车辆信息
const saveVehicle = async () => {
// 表单验证
if (!formData.carNum.trim()) {
uni.showToast({
title: '请输入车牌号码',
icon: 'none'
});
return;
}
if (!formData.name.trim()) {
uni.showToast({
title: '请输入车主姓名',
icon: 'none'
});
return;
}
if (!formData.tel.trim()) {
uni.showToast({
title: '请输入联系电话',
icon: 'none'
});
return;
}
if (!formData.type) {
uni.showToast({
title: '请选择车辆类型',
icon: 'none'
});
return;
}
try {
let apiUrl = '';
let method = '';
if (isEditing.value) {
// 编辑
apiUrl = `/api/ffep/parking/editcar`;
method = 'PUT';
} else {
// 新增
apiUrl = '/api/ffep/parking/addcar';
method = 'POST';
}
// 调用API保存车辆信息
const res = await request({
url: apiUrl,
method,
data: formData
});
if (res.code === 1) {
uni.showToast({
title: res.msg,
icon: 'success'
});
// 关闭模态框
closeFormModal();
// 刷新列表
onRefresh();
} else {
uni.showToast({
title: res.msg || (isEditing.value ? '更新失败' : '添加失败'),
icon: 'none'
});
}
} catch (error) {
console.error('保存车辆信息失败', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
}
};
// 确认删除车辆
const confirmDelete = (vehicle : any) => {
uni.showModal({
title: '确认删除',
content: `确定要删除车牌为${vehicle.carNum}的车辆信息吗?`,
success: async (res) => {
if (res.confirm) {
try {
// 调用API删除车辆
const result = await request({
url: `/api/ffep/parking/delcar`,
method: 'DELETE',
data:{id:vehicle.id}
});
if (result.code === 1) {
uni.showToast({
title: '删除成功',
icon: 'success'
});
// 刷新列表
onRefresh();
} else {
uni.showToast({
title: result.msg || '删除失败',
icon: 'none'
});
}
} catch (error) {
console.error('删除车辆失败', error);
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'none'
});
}
}
}
});
};
// 页面加载时初始化
onMounted(() => {
zPageRef.value.reload()
// 初始化微信JSSDK(如果需要)
// initWecomJssdk();
});
</script>
<style lang="scss" scoped>
.vehicle-management {
min-height: 100vh;
background-color: #f5f5f5;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
height: 100rpx;
background-color: #0f80ff;
padding: 0 30rpx;
.add-btn {
display: flex;
align-items: center;
background-color: #ffffff;
color: #0f80ff;
border-radius: 20rpx;
padding: 8rpx 20rpx;
height: 75%;
text {
margin-left: 10rpx;
font-size: 28rpx;
}
}
}
.vehicle-list {
padding: 0 20rpx;
.vehicle-items {
.vehicle-item {
display: flex;
justify-content: space-between;
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.vehicle-info {
flex: 1;
.plate-number {
display: flex;
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.basic-info {
display: flex;
margin-bottom: 10rpx;
.name,
.tel {
font-size: 26rpx;
color: #666;
margin-right: 20rpx;
}
}
.extra-info {
display: flex;
flex-wrap: wrap;
.type,
.brand,
.status {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
margin-bottom: 10rpx;
}
.status-normal {
color: #00b42a;
}
.status-warning {
color: #ff7d00;
}
}
}
.vehicle-actions {
display: flex;
flex-direction: column;
justify-content: space-around;
button {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border-radius: 50%;
margin-bottom: 10rpx;
}
}
}
}
}
// 表单模态框样式
.vehicle-form {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
height: 100%;
overflow: hidden;
.form-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #e5e6eb;
.form-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.form-content {
padding: 20rpx 30rpx;
height: calc(100% - 160rpx);
overflow-y: auto;
.form-section {
margin-bottom: 30rpx;
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.form-item {
margin-bottom: 25rpx;
.item-label {
display: block;
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
}
.plate-input {
display: flex;
.province-selector {
width: 120rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx solid #e5e6eb;
border-radius: 10rpx;
margin-right: 20rpx;
font-size: 32rpx;
}
input {
flex: 1;
border: 1rpx solid #e5e6eb;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 32rpx;
}
}
input,
textarea {
width: 100%;
border: 1rpx solid #e5e6eb;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
}
.type-selector,
.status-selector {
display: flex;
align-items: center;
justify-content: space-between;
border: 1rpx solid #e5e6eb;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
}
}
}
}
.form-actions {
display: flex;
padding: 30rpx;
border-top: 1rpx solid #e5e6eb;
.cancel-btn,
.save-btn {
flex: 1;
height: 90rpx;
border-radius: 50rpx;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.cancel-btn {
color: #666;
border: 1rpx solid #e5e6eb;
margin-right: 30rpx;
}
.save-btn {
color: #fff;
background-color: #0f80ff;
}
}
}
// 详情模态框样式
.vehicle-detail {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
height: 100%;
overflow: hidden;
.detail-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #e5e6eb;
.detail-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
width: 200rpx;
}
}
.detail-content {
padding: 20rpx 30rpx;
height: calc(100% - 160rpx);
overflow-y: auto;
.detail-info {
margin-bottom: 30rpx;
.plate-number {
display: flex;
align-items: center;
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
.status {
font-size: 26rpx;
font-weight: normal;
margin-left: 20rpx;
padding: 5rpx 15rpx;
border-radius: 50rpx;
}
.status-normal {
color: #00b42a;
background-color: #e8fff3;
}
.status-warning {
color: #ff7d00;
background-color: #fff7e8;
}
}
.info-group {
.info-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.info-label {
width: 200rpx;
font-size: 28rpx;
color: #666;
}
.info-value {
flex: 1;
font-size: 28rpx;
color: #333;
}
}
}
}
.violation-records {
.records-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.record-item {
background-color: #f9f9f9;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.record-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
.record-time {
font-size: 26rpx;
color: #666;
}
.record-type {
font-size: 24rpx;
padding: 5rpx 15rpx;
border-radius: 50rpx;
}
.type-multiple {
color: #ff7d00;
background-color: #fff7e8;
}
.type-forbidden {
color: #f53f3f;
background-color: #ffeded;
}
}
.record-location,
.record-punishment {
font-size: 26rpx;
color: #666;
margin-bottom: 5rpx;
}
}
}
}
.detail-actions {
display: flex;
padding: 30rpx;
border-top: 1rpx solid #e5e6eb;
.edit-btn,
.violation-btn {
flex: 1;
height: 90rpx;
border-radius: 50rpx;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.edit-btn {
color: #0f80ff;
border: 1rpx solid #0f80ff;
margin-right: 30rpx;
}
.violation-btn {
color: #fff;
background-color: #0f80ff;
}
}
}
</style>
生成拿到的代码只需要根据业务需求,删改或调整部分字段,完成api接口数据对接,页面都可以直接投入到业务使用场景了,整个页面耗时(包括API接口后端开发)不到3小时完成,个人来说已经是非常高效,不得不感慨科技改变生活!!
当然不足也是有的,例如细节的地方需要微调,比如布局、样式,AI虽然强大但是会耗费非常多的沟通时间,这部分建议手动修改。其次是AI有时候未必了解组件的使用方式,例如z-paging组件标签的位置,经过多次沟通调整还是没办法正常运行,终归还是得靠自己把控吧!