feat:增加汽车事件模块
This commit is contained in:
29
src/apis/car.ts
Normal file
29
src/apis/car.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { cloudService } from './index'
|
||||
import { ICarEvent, ICarEventQuery } from '@/types/car.ts'
|
||||
|
||||
export const addCarEventApi = (carEvent: ICarEvent): Promise<boolean> =>
|
||||
cloudService({
|
||||
url: '/home-api/car',
|
||||
method: 'post',
|
||||
data: carEvent
|
||||
})
|
||||
|
||||
export const updateCarEventApi = (id: number, carEvent: ICarEvent): Promise<boolean> =>
|
||||
cloudService({
|
||||
url: `/home-api/car/${id}`,
|
||||
method: 'put',
|
||||
data: carEvent
|
||||
})
|
||||
|
||||
export const deleteCarEventApi = (id: number): Promise<boolean> =>
|
||||
cloudService({
|
||||
url: `/home-api/car/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
|
||||
export const queryCarEventListApi = (query: ICarEventQuery): Promise<ICarEvent[]> =>
|
||||
cloudService({
|
||||
url: '/home-api/car',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
108
src/components/Car/CarCard.vue
Normal file
108
src/components/Car/CarCard.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div class="life-record-card card-item">
|
||||
<el-image :src="`${props.carItem?.imageUrl}${ThumbImageQuery}`"
|
||||
fit="contain" alt="暂无图片" @click="viewImageClick">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<van-swipe-cell>
|
||||
<div class="record-info" @click="editClick()">
|
||||
<div class="top-container">
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="title">{{ props.carItem.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<i class="fa-solid fa-file-lines" style="color: #8e44ad"></i>
|
||||
{{ props.carItem.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tag type="primary" effect="dark">
|
||||
{{ props.carItem.type }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="bottom-container">
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<i class="fa-solid fa-gauge" style="color: #3498db"></i>
|
||||
{{ props.carItem.mileage }} M
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<i class="fa-solid fa-coins" style="color: #27ae60"></i>
|
||||
{{ props.carItem.cost }} 元
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<i class="fa-solid fa-location-dot" style="color: #e67e22"></i>
|
||||
{{ props.carItem.location }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<i class="fa-solid fa-calendar" style="color: #26a69a"></i>
|
||||
{{ props.carItem.date }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #right>
|
||||
<van-button square type="primary" text="编辑" style="height: 100%;"
|
||||
@click="editClick"/>
|
||||
<van-button square type="danger" text="删除" style="height: 100%;"
|
||||
@click="deleteClick()"/>
|
||||
</template>
|
||||
</van-swipe-cell>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCarStore } from '@/store/car.ts'
|
||||
import { defineProps, PropType } from 'vue'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ThumbImageQuery } from '@/utils'
|
||||
import { showImagePreview } from 'vant'
|
||||
import { ICarEvent } from '@/types/car.ts'
|
||||
|
||||
const carStore = useCarStore()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
carItem: {
|
||||
required: true,
|
||||
type: Object as PropType<ICarEvent>
|
||||
}
|
||||
})
|
||||
|
||||
const viewImageClick = () => {
|
||||
showImagePreview({ images: [props.carItem?.imageUrl], closeable: true })
|
||||
}
|
||||
|
||||
const editClick = async () => {
|
||||
const id = props.carItem?.id
|
||||
carStore.carEventApiType = 'UPDATE'
|
||||
|
||||
carStore.currentCarEvent = props.carItem as ICarEvent
|
||||
await router.push({ name: 'CarDetailId', params: { id } })
|
||||
}
|
||||
|
||||
const deleteClick = () => {
|
||||
carStore.carEventApiType = 'DELETE'
|
||||
commonElMessageBox(`请确认是否删除该事件: ${props.carItem?.description}?`).then(() => {
|
||||
carStore.handleCarEventApi(props.carItem as ICarEvent)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/home/life.record.module";
|
||||
</style>
|
||||
33
src/components/Car/CarQuery.vue
Normal file
33
src/components/Car/CarQuery.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="mobile-common-query card-item">
|
||||
<div class="query-item">
|
||||
<span>类别:</span>
|
||||
<el-segmented v-model="carStore.queryInfo.type"
|
||||
:options="queryTypeList"
|
||||
@change="carStore.queryCarEventList()"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCarStore } from '@/store/car.ts'
|
||||
|
||||
const carStore = useCarStore()
|
||||
|
||||
const queryTypeList = [
|
||||
{
|
||||
label: '全部',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
label: '保养',
|
||||
value: '保养'
|
||||
}, {
|
||||
label: '事故',
|
||||
value: '事故'
|
||||
}, {
|
||||
label: '违章',
|
||||
value: '违章'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
@@ -98,6 +98,12 @@
|
||||
<span>清单</span>
|
||||
</router-link>
|
||||
|
||||
<router-link v-if="path.includes('car')" to="/car/list"
|
||||
@click="routerClick()" class="item">
|
||||
<i class="fa-solid fa-list-ul"></i>
|
||||
<span>清单</span>
|
||||
</router-link>
|
||||
|
||||
<router-link v-if="path.includes('journal')" to="/journal/record"
|
||||
@click="routerClick()" class="item">
|
||||
<i class="fa-solid fa-calendar-days"></i>
|
||||
|
||||
62
src/store/car.ts
Normal file
62
src/store/car.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { IHandleApi } from 'vue3-common/types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ImagePathEnum } from '@/types'
|
||||
import { ICarEvent, ICarEventQuery } from '@/types/car.ts'
|
||||
import { addCarEventApi, deleteCarEventApi, queryCarEventListApi, updateCarEventApi } from '@/apis/car.ts'
|
||||
|
||||
export const useCarStore = defineStore('car', {
|
||||
state: () => ({
|
||||
carEventList: [] as ICarEvent[],
|
||||
queryInfo: {
|
||||
type: ''
|
||||
} as ICarEventQuery,
|
||||
carEventApiType: 'ADD' as IHandleApi,
|
||||
currentCarEvent: {
|
||||
|
||||
} as ICarEvent,
|
||||
carImagePath: ImagePathEnum.CAR,
|
||||
isScrollEnd: false,
|
||||
isRefresh: false
|
||||
}),
|
||||
actions: {
|
||||
async queryCarEventList() {
|
||||
this.carEventList = await queryCarEventListApi(this.queryInfo)
|
||||
},
|
||||
async handleCarEventApi(carEvent: ICarEvent) {
|
||||
switch (this.carEventApiType) {
|
||||
case 'ADD':
|
||||
await addCarEventApi(carEvent)
|
||||
ElMessage.success('新增汽车事件成功')
|
||||
break
|
||||
case 'UPDATE':
|
||||
await updateCarEventApi(carEvent.id as number, carEvent)
|
||||
ElMessage.success('更新汽车事件成功')
|
||||
break
|
||||
case 'DELETE':
|
||||
await deleteCarEventApi(carEvent.id as number)
|
||||
ElMessage.success('删除汽车事件成功')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
async refreshInfo() {
|
||||
await this.queryCarEventList()
|
||||
this.isRefresh = !this.isRefresh
|
||||
},
|
||||
getEmptyCarEvent(): ICarEvent {
|
||||
return {
|
||||
cost: 0,
|
||||
date: '',
|
||||
description: '',
|
||||
imageUrl: '',
|
||||
location: '',
|
||||
mileage: 0,
|
||||
name: '',
|
||||
remark: '',
|
||||
type: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -82,4 +82,4 @@ span, input {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@import "//at.alicdn.com/t/c/font_4793264_mkjt7tcwxmc.css";
|
||||
@import "//at.alicdn.com/t/c/font_4793264_lul7gdj5f2.css";
|
||||
|
||||
16
src/types/car.ts
Normal file
16
src/types/car.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface ICarEvent {
|
||||
id?: number;
|
||||
name: string;
|
||||
type: string;
|
||||
date: string;
|
||||
mileage: number;
|
||||
cost: number;
|
||||
location: string;
|
||||
description: string;
|
||||
imageUrl: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
export interface ICarEventQuery {
|
||||
type: string;
|
||||
}
|
||||
@@ -7,7 +7,8 @@ export enum ImagePathEnum {
|
||||
JOURNAL = 'journal',
|
||||
LEDGER = 'ledger',
|
||||
AWARD = 'award',
|
||||
USER = 'user'
|
||||
USER = 'user',
|
||||
CAR = 'car',
|
||||
}
|
||||
|
||||
export interface IPageRecord<T> {
|
||||
|
||||
@@ -247,6 +247,30 @@ export const productRules = {
|
||||
]
|
||||
}
|
||||
|
||||
export const carRules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入汽车名称', trigger: 'blur' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: '请选择事件类型', trigger: 'change' }
|
||||
],
|
||||
date: [
|
||||
{ required: true, message: '请选择事件日期', trigger: 'change' }
|
||||
],
|
||||
mileage: [
|
||||
{ required: true, message: '请输入当前里程', trigger: 'blur' }
|
||||
],
|
||||
cost: [
|
||||
{ required: true, message: '请输入事件费用', trigger: 'blur' }
|
||||
],
|
||||
location: [
|
||||
{ required: true, message: '请输入事件地点', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入事件描述', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
export const journalRules = {
|
||||
content: [
|
||||
{ required: true, message: '请输入内容', trigger: 'blur' }
|
||||
|
||||
170
src/views/car/detail[id].vue
Normal file
170
src/views/car/detail[id].vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<div class="common-mobile-view">
|
||||
<el-form ref="formRef" :model="carStore.currentCarEvent"
|
||||
:rules="rules" label-position="top" label-width="80px"
|
||||
class="form-item">
|
||||
<el-form-item label="汽车名称" prop="name">
|
||||
<el-input v-model="carStore.currentCarEvent.name" autocomplete="off"
|
||||
placeholder="请输入汽车名称" clearable
|
||||
show-word-limit maxlength="20">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="事件类型" prop="type">
|
||||
<el-radio-group v-model="carStore.currentCarEvent.type"
|
||||
placeholder="请选择事件类型" clearable>
|
||||
<el-radio
|
||||
v-for="(item, index) in ['保养','事故','违章']"
|
||||
:key="index" :label="item" :value="item"
|
||||
/>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="事件日期" prop="date">
|
||||
<el-date-picker
|
||||
v-model="carStore.currentCarEvent.date"
|
||||
type="date" :editable="false" placeholder="请选择事件日期"
|
||||
value-format="YYYY-MM-DD"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="当前里程" prop="mileage">
|
||||
<el-input-number v-model="carStore.currentCarEvent.mileage" :min="0" :max="1000000">
|
||||
<template #suffix>
|
||||
<span>M</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="事件费用" prop="cost">
|
||||
<el-input-number v-model="carStore.currentCarEvent.cost" :min="0" :max="1000000">
|
||||
<template #prefix>
|
||||
<span>¥</span>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="事件地点" prop="location">
|
||||
<el-input
|
||||
v-model="carStore.currentCarEvent.location"
|
||||
placeholder="请输入事件地点" clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="事件描述" prop="description">
|
||||
<el-input
|
||||
v-model="carStore.currentCarEvent.description"
|
||||
placeholder="请输入事件描述" clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="现场图片">
|
||||
<el-upload
|
||||
v-model:file-list="imageFileList"
|
||||
list-type="picture-card"
|
||||
:accept="acceptImageTypes"
|
||||
:limit="1"
|
||||
:before-upload="beforeUpload"
|
||||
:http-request="httpRequest"
|
||||
:on-success="handleSuccess"
|
||||
:on-exceed="handleExceed"
|
||||
:before-remove="beforeRemove"
|
||||
:on-remove="handleRemove"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
请上传小于10M的图片
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="事件备注" prop="remark">
|
||||
<el-input
|
||||
v-model="carStore.currentCarEvent.remark"
|
||||
placeholder="请输入事件备注" clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="form-button">
|
||||
<el-button type="primary" @click="confirmClick">确认</el-button>
|
||||
<el-button type="info" @click="cancelClick">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useCarStore } from '@/store/car.ts'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { ElMessage, ElMessageBox, FormRules, UploadProps, UploadRawFile, UploadRequestOptions, UploadUserFile } from 'element-plus'
|
||||
import { carRules } from '@/utils/element/elRules.ts'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { acceptImageTypes } from '@/utils/file.ts'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { beforeUploadImage, httpRequestFile } from '@/utils/element/elUpload.ts'
|
||||
|
||||
const formRef = ref()
|
||||
const rules = reactive<FormRules>(carRules)
|
||||
const carStore = useCarStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const imageFileList = ref<UploadUserFile[]>([])
|
||||
|
||||
onMounted(async () => {
|
||||
appStore.isShowMobileBack = true
|
||||
|
||||
initImageFileList()
|
||||
})
|
||||
|
||||
const initImageFileList = () => {
|
||||
imageFileList.value = []
|
||||
const { imageUrl } = carStore.currentCarEvent
|
||||
if (imageUrl) {
|
||||
imageFileList.value.push({
|
||||
name: 'image',
|
||||
url: imageUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||
return beforeUploadImage(rawFile)
|
||||
}
|
||||
|
||||
const httpRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
|
||||
return await httpRequestFile(carStore.carImagePath, options)
|
||||
}
|
||||
|
||||
const handleSuccess = (response: string) => {
|
||||
carStore.currentCarEvent.imageUrl = response
|
||||
}
|
||||
|
||||
const handleExceed: UploadProps['onExceed'] = () => {
|
||||
ElMessage.warning('最多上传1张图片')
|
||||
}
|
||||
|
||||
const handleRemove: UploadProps['onRemove'] = () => {
|
||||
carStore.currentCarEvent.imageUrl = ''
|
||||
}
|
||||
|
||||
const beforeRemove: UploadProps['beforeRemove'] = () => {
|
||||
return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击确认按钮
|
||||
*/
|
||||
const confirmClick = () => {
|
||||
formRef.value.validate().then(async () => {
|
||||
await carStore.handleCarEventApi(carStore.currentCarEvent)
|
||||
history.back()
|
||||
appStore.isShowMobileBack = false
|
||||
})
|
||||
}
|
||||
|
||||
const cancelClick = () => {
|
||||
history.back()
|
||||
appStore.isShowMobileBack = false
|
||||
}
|
||||
</script>
|
||||
36
src/views/car/list.vue
Normal file
36
src/views/car/list.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="common-mobile-view">
|
||||
<car-query />
|
||||
|
||||
<el-empty v-if="carStore.carEventList.length === 0" description="暂无记录"/>
|
||||
|
||||
<div v-else class="content-list">
|
||||
<car-card v-for="(item, index) in carStore.carEventList"
|
||||
:key="index" :car-item="item"/>
|
||||
</div>
|
||||
|
||||
<van-floating-bubble icon="plus" @click="addClick" style="top: -60px;"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import CarQuery from '@/components/Car/CarQuery.vue'
|
||||
import CarCard from '@/components/Car/CarCard.vue'
|
||||
import { useCarStore } from '@/store/car.ts'
|
||||
import { onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const carStore = useCarStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(async () => {
|
||||
await carStore.queryCarEventList()
|
||||
})
|
||||
|
||||
const addClick = () => {
|
||||
carStore.carEventApiType = 'ADD'
|
||||
carStore.currentCarEvent = carStore.getEmptyCarEvent()
|
||||
|
||||
router.push({ name: 'CarDetailId', params: { id: 0 } })
|
||||
}
|
||||
</script>
|
||||
@@ -30,6 +30,10 @@
|
||||
title="物品记录" sub-title="收纳生活琐碎"
|
||||
router="/product/list"/>
|
||||
|
||||
<module-card icon="iconfont icon-qiche" color="#8E44AD"
|
||||
title="汽车管理" sub-title="留存行车轨迹"
|
||||
router="/car/list"/>
|
||||
|
||||
<module-card icon="iconfont icon-paiban" color="#9933FF"
|
||||
title="日程安排" sub-title="规划每日时光"
|
||||
router="/plan/calendar"/>
|
||||
@@ -147,6 +151,7 @@ const titleClick = () => {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.note-container {
|
||||
|
||||
@@ -249,10 +249,24 @@ export const mobileHomeMeta: IRouteMetaConfig = {
|
||||
title: '内容',
|
||||
icon: ''
|
||||
},
|
||||
'/car': {
|
||||
title: '汽车管理',
|
||||
icon: 'home-car',
|
||||
order: 15,
|
||||
redirect: '/car/list'
|
||||
},
|
||||
'/car/list': {
|
||||
title: '清单',
|
||||
icon: ''
|
||||
},
|
||||
'/car/detail/id': {
|
||||
title: '内容',
|
||||
icon: ''
|
||||
},
|
||||
'/profile': {
|
||||
title: '个人信息',
|
||||
icon: '',
|
||||
order: 15,
|
||||
order: 16,
|
||||
hidden: true,
|
||||
redirect: '/profile/index'
|
||||
},
|
||||
|
||||
@@ -4,9 +4,6 @@ import path from 'path'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import { viteMockServe } from 'vite-plugin-mock'
|
||||
|
||||
// const API_HOST = '192.168.1.7'
|
||||
const API_HOST = '127.0.0.1'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -41,15 +38,10 @@ export default defineConfig({
|
||||
// 服务器代理 服务器没有部署Nginx的情况下使用
|
||||
proxy: {
|
||||
'/home-api': {
|
||||
target: `http://${API_HOST}:8081`,
|
||||
target: 'http://127.0.0.1:8081',
|
||||
changeOrigin: true,
|
||||
rewrite: (item) => item.replace(/^\/home-api/, '')
|
||||
},
|
||||
'/blog-api': {
|
||||
target: `http://${API_HOST}:8082`,
|
||||
changeOrigin: true,
|
||||
rewrite: (item) => item.replace(/^\/blog-api/, '')
|
||||
},
|
||||
'/alist-api': {
|
||||
target: 'http://192.168.1.7:5244',
|
||||
changeOrigin: true,
|
||||
@@ -61,7 +53,7 @@ export default defineConfig({
|
||||
rewrite: (item) => item.replace(/^\/image-api/, '')
|
||||
},
|
||||
'/agent-api': {
|
||||
target: `http://${API_HOST}:8000`,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
changeOrigin: true,
|
||||
rewrite: (item) => item.replace(/^\/agent-api/, '')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user