飞行器管理,飞行器维保记录,飞行器保险记录对接接口;

This commit is contained in:
hr121 2025-07-18 18:07:31 +08:00
parent b37d26ad62
commit ee698bcf45
7 changed files with 1199 additions and 444 deletions

View File

@ -1,42 +1,90 @@
import request from '@/utils/request'
export function add(data) {
// 分页查询飞行器设备
export function get(params) {
return request({
url: 'api/aircraft',
method: 'post',
data
})
}
export function del(ids) {
return request({
url: 'api/aircraft',
method: 'delete',
data: ids
})
}
export function edit(data) {
return request({
url: 'api/aircraft',
method: 'put',
data
})
}
export function get(id) {
return request({
url: 'api/aircraft/' + id,
method: 'get'
})
}
export function getAll(params) {
return request({
url: 'api/aircraft',
url: 'aerocraftAdminApi/aircraft/device/page',
method: 'get',
params
})
}
export default { add, edit, del, get, getAll }
// 删除飞行器设备
export function del(id) {
return request({
url: `aerocraftAdminApi/aircraft/device/${id}`,
method: 'delete'
})
}
// 新增飞行器设备
export function add(data) {
return request({
url: 'aerocraftAdminApi/aircraft/device',
method: 'post',
data
})
}
// 编辑飞行器设备
export function edit(data) {
return request({
url: 'aerocraftAdminApi/aircraft/device',
method: 'put',
data
})
}
// 分页查询飞行器维保记录
export function getMaintenanceRecords(params) {
return request({
url: 'aerocraftAdminApi/aircraft/maintenance/page',
method: 'get',
params
})
}
// 分页查询飞行器保险
export function getInsuranceRecords(params) {
return request({
url: 'aerocraftAdminApi/aircraft/insurance/page',
method: 'get',
params
})
}
// 新增飞行器保险
export function addInsurance(data) {
return request({
url: 'aerocraftAdminApi/aircraft/insurance',
method: 'post',
data
})
}
// 获取飞行器保险详情
export function getInsuranceDetail(id) {
return request({
url: `aerocraftAdminApi/aircraft/insurance/${id}`,
method: 'get'
})
}
// 删除飞行器保险
export function deleteInsurance(id) {
return request({
url: `aerocraftAdminApi/aircraft/insurance/${id}`,
method: 'delete'
})
}
// 编辑飞行器保险
export function editInsurance(data) {
return request({
url: 'aerocraftAdminApi/aircraft/insurance',
method: 'put',
data
})
}
export default { get, del, add, edit, getMaintenanceRecords, getInsuranceRecords, addInsurance, getInsuranceDetail, deleteInsurance, editInsurance }

View File

@ -0,0 +1,35 @@
<template>
<div class="api-links">
<a href="javascript:void(0)" class="api-link" @click="handleClick('data')">飞行器数据记录</a>
<a href="javascript:void(0)" class="api-link" @click="handleClick('fault')">飞行器故障记录</a>
<a href="javascript:void(0)" class="api-link" @click="handleClick('video')">飞行器视频记录</a>
</div>
</template>
<script>
export default {
name: 'ApiLinks',
methods: {
handleClick(type) {
this.$emit('click', type)
}
}
}
</script>
<style lang="scss" scoped>
.api-links {
margin-top: 20px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 30px;
.api-link {
color: #027db4;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span style="color: #027db4;">飞行器基本信息</span>
</div>
<el-form :model="form" label-width="100px" class="detail-form">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="名称">
<el-input v-model="form.name" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="型号">
<el-input v-model="form.model" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类型">
<el-input v-model="form.type" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="区域">
<el-input v-model="form.region" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="景区">
<el-input v-model="form.scenicArea" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="负责人">
<el-input v-model="form.manager" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="备注">
<el-input type="textarea" v-model="form.remarks" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="无人机图片">
<div class="image-preview">
<div v-for="(url, index) in form.imageUrls" :key="index" class="image-item" @click="handlePreview(url)">
<img :src="url" class="preview-image">
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 图片预览 -->
<el-dialog :visible.sync="previewVisible" append-to-body>
<img :src="previewUrl" alt="Preview" style="width: 100%">
</el-dialog>
</el-card>
</template>
<script>
export default {
name: 'BasicInfo',
props: {
form: {
type: Object,
required: true
}
},
data() {
return {
previewVisible: false,
previewUrl: ''
}
},
methods: {
handlePreview(url) {
this.previewUrl = url
this.previewVisible = true
}
}
}
</script>
<style lang="scss" scoped>
.detail-form {
.el-form-item {
margin-bottom: 20px;
}
::v-deep .el-input.is-disabled .el-input__inner {
background-color: white;
color: #686868;
}
::v-deep .el-textarea.is-disabled .el-textarea__inner {
background-color: white;
color: #686868;
}
}
.image-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
.image-item {
width: 120px;
height: 120px;
cursor: pointer;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
&:hover {
border-color: #409EFF;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
.box-card {
margin-bottom: 20px;
box-shadow: none;
border: none;
background: none;
.clearfix {
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,440 @@
<template>
<el-card class="box-card" style="margin-top: 20px">
<div slot="header">
<span style="color: #027db4">飞行器保险记录</span>
<el-button
style="
float: right;
padding: 5px;
background-color: #02a7f0;
color: white;
"
type="text"
icon="el-icon-plus"
@click="handleAdd"
>新增保险
</el-button>
</div>
<el-table :data="data" style="width: 100%">
<el-table-column prop="name" label="保险名称" />
<el-table-column prop="insuranceType" label="保险类型">
<template slot-scope="scope">
{{ getInsuranceTypeName(scope.row.insuranceType) }}
</template>
</el-table-column>
<el-table-column prop="deadlineTime" label="截止日期" width="180">
<template slot-scope="scope">
{{ scope.row.deadlineTime ? scope.row.deadlineTime.split('T')[0] : '' }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleView(scope.row)"
>查看详情</el-button
>
<el-button type="text" size="small" @click="handleEdit(scope.row)"
>编辑</el-button
>
<el-button type="text" size="small" @click="handleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
:current-page.sync="page.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="page.size"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 新增/编辑保险表单 -->
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
width="500px"
append-to-body
:close-on-click-modal="false"
@close="handleDialogClose"
>
<el-form
ref="insuranceForm"
:model="form"
:rules="rules"
label-width="100px"
style="margin-right: 30px"
:disabled="isView"
>
<el-form-item label="保险名称" prop="name">
<el-input v-model="form.name" placeholder="请输入保险名称" />
</el-form-item>
<el-form-item label="保险类型" prop="insuranceType">
<el-select
v-model="form.insuranceType"
placeholder="请选择保险类型"
style="width: 100%"
>
<el-option
v-for="item in insuranceTypes"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="截止日期" prop="deadlineTime">
<el-date-picker
v-model="form.deadlineTime"
type="date"
placeholder="选择日期"
style="width: 100%"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item label="保险附件" prop="files">
<el-upload
ref="uploadRef"
action="#"
:file-list="form.files"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
list-type="text"
multiple
:disabled="isView"
>
<el-button size="small" type="primary">
<i class="el-icon-plus"></i> 上传
</el-button>
</el-upload>
<div slot="tip" class="el-upload__tip">支持上传多种格式文件JPEGPDFDOCX等单个文件不超过10MB</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">{{ isView ? '关闭' : '取 消' }}</el-button>
<el-button v-if="!isView" type="primary" @click="submitForm"> </el-button>
</div>
</el-dialog>
</el-card>
</template>
<script>
import { mapGetters } from 'vuex'
import { upload } from '@/utils/upload'
import { addInsurance, getInsuranceDetail, deleteInsurance, editInsurance } from '@/api/aircraft'
export default {
name: "InsuranceRecord",
props: {
data: {
type: Array,
required: true,
},
page: {
type: Object,
required: true,
},
aircraftId: {
type: [String, Number],
required: true
}
},
computed: {
...mapGetters([
'fileUploadApi'
])
},
data() {
return {
loading: false,
dialogVisible: false,
dialogTitle: "新增保险",
uploadDisabled: false,
isView: false, //
form: {
id: null,
name: "",
insuranceType: "",
deadlineTime: "",
files: [],
attachments: [],
aircraftId: null
},
insuranceTypes: [
{ value: 0, label: "三方险" },
{ value: 1, label: "设备险" },
{ value: 2, label: "运营险" },
],
rules: {
name: [{ required: true, message: "请输入保险名称", trigger: "blur" }],
insuranceType: [
{ required: true, message: "请选择保险类型", trigger: "change" },
],
deadlineTime: [
{ required: true, message: "请选择截止日期", trigger: "change" },
],
files: [
{ required: true, message: "请上传保险附件", trigger: "change" },
],
},
};
},
methods: {
getInsuranceTypeName(type) {
const found = this.insuranceTypes.find(t => t.value === type)
return found ? found.label : '未知类型'
},
handleSizeChange(val) {
this.$emit("size-change", val);
},
handleCurrentChange(val) {
this.$emit("current-change", val);
},
handleAdd() {
this.dialogTitle = "新增保险";
this.isView = false;
this.form = {
id: null,
name: "",
insuranceType: "",
deadlineTime: "",
files: [],
attachments: [],
aircraftId: this.aircraftId
};
this.uploadDisabled = false;
this.dialogVisible = true;
},
async handleView(row) {
try {
const res = await getInsuranceDetail(row.id);
this.dialogTitle = "查看保险详情";
this.isView = true;
this.form = {
name: res.name,
insuranceType: res.insuranceType,
deadlineTime: res.deadlineTime.split(' ')[0],
files: res.insuranceAttachment.map(file => ({
name: file.sourceFileName,
url: file.fileFullPath
})),
attachments: res.insuranceAttachment
};
this.uploadDisabled = true;
this.dialogVisible = true;
} catch (error) {
this.$message.error('获取保险详情失败');
}
},
async handleEdit(row) {
try {
const res = await getInsuranceDetail(row.id);
this.dialogTitle = "编辑保险";
this.isView = false;
this.form = {
id: row.id,
name: res.name,
insuranceType: res.insuranceType,
deadlineTime: res.deadlineTime.split(' ')[0],
files: res.insuranceAttachment.map(file => ({
name: file.sourceFileName,
url: file.fileFullPath
})),
attachments: res.insuranceAttachment,
aircraftId: this.aircraftId
};
this.uploadDisabled = false;
this.dialogVisible = true;
} catch (error) {
this.$message.error('获取保险详情失败');
}
},
handleDelete(row) {
this.$confirm('此操作将永久删除该保险记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await deleteInsurance(row.id);
this.$message({
type: 'success',
message: '删除成功!'
});
this.$emit('refresh');
} catch (error) {
this.$message.error(error.message || '删除失败');
}
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
//
async handleFileChange(file, fileList) {
const isLt10M = file.raw.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.$message.error('文件大小不能超过 10MB');
fileList.pop();
return;
}
this.loading = true;
try {
const res = await upload(this.fileUploadApi, file.raw);
if (res.status === 200) {
const data = res.data;
//
this.form.files = fileList;
//
if (!this.form.attachments) {
this.form.attachments = [];
}
this.form.attachments.push({
fileFullPath: data.fileFullPath || `/file/${data.newFileName}`,
fileSize: file.raw.size,
fileType: file.raw.name.split('.').pop(),
id: 0,
newFileName: data.newFileName,
sourceFileName: file.raw.name
});
this.$message.success(`${file.name} 上传成功`);
} else {
fileList.pop();
this.$message.error('文件上传失败');
}
} catch (error) {
fileList.pop();
this.$message.error('文件上传失败');
} finally {
this.loading = false;
}
},
//
handleFileRemove(file) {
const index = this.form.attachments.findIndex(
item => item.sourceFileName === file.name
);
if (index > -1) {
this.form.attachments.splice(index, 1);
}
},
handleDialogClose() {
this.$refs.insuranceForm.resetFields();
this.$refs.uploadRef.clearFiles();
this.form.attachments = [];
this.uploadDisabled = false;
this.isView = false;
},
submitForm() {
this.$refs.insuranceForm.validate(async (valid) => {
if (valid) {
try {
const baseData = {
aircraftId: this.aircraftId,
name: this.form.name,
insuranceType: this.form.insuranceType,
deadlineTime: this.form.deadlineTime,
insuranceAttachment: this.form.attachments
};
if (this.form.id) {
// id
await editInsurance({
...baseData,
id: this.form.id
});
this.$message.success('保险编辑成功');
} else {
// id
await addInsurance(baseData);
this.$message.success('保险添加成功');
}
this.$emit('refresh');
this.dialogVisible = false;
} catch (error) {
this.$message.error(error.message || '保险添加失败');
}
}
});
},
},
};
</script>
<style lang="scss" scoped>
.box-card {
margin-bottom: 20px;
box-shadow: none;
border: none;
background: none;
.clearfix {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.pagination-container {
text-align: right;
margin-top: 20px;
}
.el-upload {
::v-deep .el-button {
padding: 8px 16px;
i {
margin-right: 4px;
font-weight: bold;
}
}
::v-deep .el-upload-list {
margin-top: 10px;
}
::v-deep .el-upload-list__item {
transition: all 0.3s;
padding: 8px 5px;
border-radius: 4px;
margin-bottom: 5px;
&:hover {
background-color: #f5f7fa;
}
.el-upload-list__item-name {
color: #606266;
font-size: 14px;
margin-left: 5px;
}
.el-icon-upload-success {
color: #67C23A;
}
}
}
.el-upload__tip {
color: #909399;
font-size: 12px;
margin-top: 5px;
margin-left: 2px;
}
.dialog-footer {
text-align: right;
padding-top: 20px;
border-top: 1px solid #e4e4e4;
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<el-card class="box-card" style="margin-top: 20px;">
<div slot="header" class="clearfix">
<span style="color: #027db4;">飞行器维保记录</span>
</div>
<el-table :data="data" style="width: 100%">
<el-table-column prop="manager" label="负责人" />
<el-table-column prop="type" label="维保类型" />
<el-table-column prop="remarks" label="备注" />
<el-table-column prop="time" label="维保时间" width="180" />
</el-table>
<div class="pagination-container">
<el-pagination
:current-page.sync="page.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="page.size"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
</template>
<script>
export default {
name: 'MaintenanceRecord',
props: {
data: {
type: Array,
required: true
},
page: {
type: Object,
required: true
}
},
methods: {
handleSizeChange(val) {
this.$emit('size-change', val)
},
handleCurrentChange(val) {
this.$emit('current-change', val)
}
}
}
</script>
<style lang="scss" scoped>
.box-card {
margin-bottom: 20px;
box-shadow: none;
border: none;
background: none;
.clearfix {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.pagination-container {
text-align: right;
margin-top: 20px;
}
</style>

View File

@ -1,143 +1,49 @@
<template>
<div class="app-container">
<!-- 基本信息 -->
<el-card class="box-card">
<div slot="header" class="clearfix">
<span style="color: #027db4;">飞行器基本信息</span>
</div>
<el-form :model="form" label-width="100px" class="detail-form">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="名称">
<el-input v-model="form.name" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="型号">
<el-input v-model="form.model" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="类型">
<el-input v-model="form.type" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="区域">
<el-input v-model="form.region" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="景区">
<el-input v-model="form.scenicArea" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="负责人">
<el-input v-model="form.manager" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="备注">
<el-input type="textarea" v-model="form.remarks" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="无人机图片">
<div class="image-preview">
<div v-for="(url, index) in form.imageUrls" :key="index" class="image-item" @click="handlePreview(url)">
<img :src="url" class="preview-image">
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<basic-info :form="form" />
<!-- 维护记录表 -->
<el-card class="box-card" style="margin-top: 20px;">
<div slot="header" class="clearfix">
<span style="color: #027db4;">飞行器维护记录</span>
</div>
<el-table :data="maintenanceData" style="width: 100%">
<el-table-column prop="manager" label="负责人" />
<el-table-column prop="type" label="维护类型" />
<el-table-column prop="remarks" label="备注" />
<el-table-column prop="time" label="维护时间" width="180" />
</el-table>
<div class="pagination-container">
<el-pagination
:current-page.sync="maintenancePage.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="maintenancePage.size"
layout="total, sizes, prev, pager, next, jumper"
:total="maintenancePage.total"
@size-change="handleMaintenanceSizeChange"
@current-change="handleMaintenanceCurrentChange"
/>
</div>
</el-card>
<!-- 维护记录 -->
<maintenance-record
:data="maintenanceData"
:page="maintenancePage"
@size-change="handleMaintenanceSizeChange"
@current-change="handleMaintenanceCurrentChange"
/>
<!-- 保险记录表 -->
<el-card class="box-card" style="margin-top: 20px;">
<div slot="header">
<span style="color: #027db4;">飞行器保险记录</span>
<el-button
style="float: right; padding: 5px; background-color: #02a7f0; color: white;"
type="text"
icon="el-icon-plus"
@click="handleAddInsurance"
>新增保险</el-button>
</div>
<el-table :data="insuranceData" style="width: 100%">
<el-table-column prop="name" label="保险名称" />
<el-table-column prop="type" label="保险类型" />
<el-table-column prop="expireTime" label="截至时间" width="180" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleInsuranceView(scope.row)">查看详情</el-button>
<el-button type="text" size="small" @click="handleInsuranceEdit(scope.row)">编辑</el-button>
<el-button type="text" size="small" @click="handleInsuranceDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
:current-page.sync="insurancePage.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="insurancePage.size"
layout="total, sizes, prev, pager, next, jumper"
:total="insurancePage.total"
@size-change="handleInsuranceSizeChange"
@current-change="handleInsuranceCurrentChange"
/>
</div>
</el-card>
<!-- 保险记录 -->
<insurance-record
:data="insuranceData"
:page="insurancePage"
:aircraft-id="Number($route.query.id)"
@size-change="handleInsuranceSizeChange"
@current-change="handleInsuranceCurrentChange"
@view="handleInsuranceView"
@edit="handleInsuranceEdit"
@delete="handleInsuranceDelete"
@refresh="loadInsuranceRecords"
/>
<!-- API模块入口 -->
<div class="api-links">
<a href="javascript:void(0)" class="api-link" @click="handleApiClick('data')">飞行器数据记录</a>
<a href="javascript:void(0)" class="api-link" @click="handleApiClick('fault')">飞行器故障记录</a>
<a href="javascript:void(0)" class="api-link" @click="handleApiClick('video')">飞行器视频记录</a>
</div>
<!-- 图片预览 -->
<el-dialog :visible.sync="previewVisible" append-to-body>
<img :src="previewUrl" alt="Preview" style="width: 100%">
</el-dialog>
<api-links @click="handleApiClick" />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { getMaintenanceRecords, getInsuranceRecords } from '@/api/aircraft'
import BasicInfo from './components/BasicInfo'
import MaintenanceRecord from './components/MaintenanceRecord'
import InsuranceRecord from './components/InsuranceRecord'
import ApiLinks from './components/ApiLinks'
export default {
name: 'aircraftDetail',
components: {
BasicInfo,
MaintenanceRecord,
InsuranceRecord,
ApiLinks
},
data() {
return {
form: {
@ -148,82 +54,116 @@ export default {
scenicArea: '白云山',
manager: '小明',
remarks: '设备状态良好',
imageUrls: Array(6).fill('https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png'),
imageUrls: Array(6).fill('https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png')
},
maintenanceData: Array(10).fill({
manager: '小明',
type: '维修',
remarks: '电池损坏',
time: '2016-09-21 08:50:08'
}),
insuranceData: Array(2).fill({
name: '安全保险',
type: '三方险',
expireTime: '2016-09-21 08:50:08'
}),
maintenanceData: [],
insuranceData: [],
maintenancePage: {
page: 1,
current: 1,
size: 10,
total: 20
total: 0
},
insurancePage: {
page: 1,
current: 1,
size: 10,
total: 2
},
previewVisible: false,
previewUrl: ''
total: 0
}
}
},
computed: {
...mapGetters([
'fileUploadApi',
'baseApi'
])
},
created() {
//
const id = this.$route.query.id
if (id) {
this.getDetail(id)
}
this.loadMaintenanceRecords()
this.loadInsuranceRecords()
},
methods: {
getDetail(id) {
//
console.log('获取详情:', id)
// this.loadInsuranceRecords()
},
handlePreview(url) {
this.previewUrl = url
this.previewVisible = true
async loadInsuranceRecords() {
try {
const params = {
aircraftId: this.$route.query.id || 0,// 0
current: this.insurancePage.current,
size: this.insurancePage.size
}
const response = await getInsuranceRecords(params)
console.log('保险记录:', response)
this.insuranceData = response.records
this.insurancePage.total = response.total
} catch (error) {
console.error('Failed to load insurance records:', error)
this.$message.error('获取保险记录失败')
}
},
async loadMaintenanceRecords() {
try {
const params = {
aircraftId: this.$route.query.id,
current: this.maintenancePage.current,
size: this.maintenancePage.size
}
const response = await getMaintenanceRecords(params)
this.maintenanceData = response.records
this.maintenancePage.total = response.total
} catch (error) {
console.error('Failed to load maintenance records:', error)
this.$message.error('获取维护记录失败')
}
},
handleMaintenanceSizeChange(val) {
this.maintenancePage.size = val
//
this.loadMaintenanceRecords()
},
handleMaintenanceCurrentChange(val) {
this.maintenancePage.page = val
//
this.maintenancePage.current = val
this.loadMaintenanceRecords()
},
handleInsuranceSizeChange(val) {
this.insurancePage.size = val
//
this.loadInsuranceRecords()
},
handleInsuranceCurrentChange(val) {
this.insurancePage.page = val
//
},
handleAddInsurance() {
//
this.insurancePage.current = val
this.loadInsuranceRecords()
},
handleInsuranceView(row) {
//
console.log('查看保险详情:', row)
},
handleInsuranceEdit(row) {
//
console.log('编辑保险信息:', row)
this.handleAdd()
this.dialogTitle = '编辑保险'
this.form = {
name: row.name,
insuranceType: row.insuranceType,
deadlineTime: row.deadlineTime ? row.deadlineTime.split('T')[0] : '',
files: row.insuranceAttachment ? row.insuranceAttachment.map(attachment => ({
name: attachment.sourceFileName,
url: attachment.fileFullPath
})) : []
}
},
handleInsuranceDelete(row) {
//
console.log('删除保险记录:', row)
},
handleInsuranceSubmit(form) {
console.log('保存保险记录:', {
...form,
aircraftId: this.$route.query.id
})
this.$message.success('保存成功')
this.loadInsuranceRecords()
},
handleApiClick(type) {
// API
console.log('跳转到API模块:', type)
}
}
@ -235,67 +175,4 @@ export default {
padding: 0;
margin-bottom: 33px;
}
.detail-form {
.el-form-item {
margin-bottom: 20px;
}
::v-deep .el-input.is-disabled .el-input__inner {
background-color: white;
color: #686868;
}
::v-deep .el-textarea.is-disabled .el-textarea__inner {
background-color: white;
color: #686868;
}
}
.image-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
.image-item {
width: 120px;
height: 120px;
cursor: pointer;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
&:hover {
border-color: #409EFF;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
.box-card {
margin-bottom: 20px;
box-shadow: none;
border: none;
background: none;
.clearfix {
display: flex;
justify-content: space-between;
align-items: center;
}
}
.pagination-container {
text-align: right;
margin-top: 20px;
}
.api-links {
margin-top: 20px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 30px;
.api-link {
color: #027db4;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
</style>

View File

@ -4,14 +4,20 @@
<div class="head-container">
<div class="filter-container">
<el-input v-model="query.name" clearable size="small" placeholder="请输入飞行器名称" style="width: 200px;" class="filter-item" />
<el-input v-model="query.manager" clearable size="small" placeholder="请输入负责人" style="width: 200px;" class="filter-item" />
<el-select v-model="query.scenicArea" placeholder="景区" clearable size="small" style="width: 200px" class="filter-item">
<el-option label="北京" value="北京" />
<el-option label="上海" value="上海" />
<el-option label="广州" value="广州" />
<el-select v-model="query.employeesId" placeholder="请选择负责人" clearable size="small" style="width: 200px" class="filter-item">
<el-option v-for="employee in employeeOptions"
:key="employee.id"
:label="employee.name"
:value="employee.id" />
</el-select>
<el-select v-model="query.areaId" placeholder="请选择区域" clearable size="small" style="width: 200px" class="filter-item">
<el-option v-for="area in areaOptions"
:key="area.id"
:label="area.name"
:value="area.id" />
</el-select>
<el-button class="filter-item" size="small" type="primary" icon="el-icon-search" @click="crud.toQuery">查询</el-button>
<el-button class="filter-item" size="small" type="success" icon="el-icon-plus" @click="crud.toAdd">新增飞行器</el-button>
<el-button v-permission="permission.add" class="filter-item" size="small" type="success" icon="el-icon-plus" @click="crud.toAdd" v-if="checkPer(permission.add)">新增飞行器</el-button>
</div>
</div>
<!--表单渲染-->
@ -23,31 +29,35 @@
<el-form-item label="型号" prop="model">
<el-input v-model="form.model" style="width: 150px;" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type" style="width: 150px;">
<el-option label="载人飞行" value="载人飞行" />
<el-option label="无人机" value="无人机" />
<el-form-item label="类型" prop="useType">
<el-select v-model="form.useType" style="width: 150px;">
<el-option :value="0" label="载物飞行" />
<el-option :value="1" label="载人飞行" />
<el-option :value="2" label="其他" />
</el-select>
</el-form-item>
<el-form-item label="角色" prop="role">
<el-select v-model="form.role" style="width: 150px;">
<el-option label="飞行员" value="飞行员" />
<el-option label="技术员" value="技术员" />
<el-option label="管理员" value="管理员" />
<el-form-item label="区域" prop="areaId">
<el-select v-model="form.areaId" style="width: 150px;">
<el-option v-for="area in areaOptions"
:key="area.id"
:label="area.name"
:value="area.id" />
</el-select>
</el-form-item>
<el-form-item label="区域" prop="region">
<el-select v-model="form.region" style="width: 150px;">
<el-option label="北京" value="北京" />
<el-option label="上海" value="上海" />
<el-option label="广州" value="广州" />
<el-form-item label="景区" prop="scenicId">
<el-select v-model="form.scenicId" style="width: 150px;">
<el-option v-for="scenic in scenicOptions"
:key="scenic.id"
:label="scenic.name"
:value="scenic.id" />
</el-select>
</el-form-item>
<el-form-item label="景区" prop="scenicArea">
<el-select v-model="form.scenicArea" style="width: 150px;">
<el-option label="白云山" value="白云山" />
<el-option label="故宫" value="故宫" />
<el-option label="长城" value="长城" />
<el-form-item label="负责人" prop="employeesId">
<el-select v-model="form.employeesId" style="width: 150px;">
<el-option v-for="employee in employeeOptions"
:key="employee.id"
:label="employee.name"
:value="employee.id" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remarks" :label-width="'100px'">
@ -59,25 +69,33 @@
style="width: 150px;">
</el-input>
</el-form-item>
<el-form-item label="无人机图片" prop="imageUrls" v-if="form.type === '载人飞行'" style="width: 100%;">
<div class="image-list">
<el-upload
class="avatar-uploader"
action="api/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
style="background-color: #fafafa; display: inline-block; margin-right: 10px;"
>
<i class="el-icon-plus avatar-uploader-icon"></i>
<el-form-item label="设备图片" prop="deviceImages" style="width: 100%;">
<div class="upload-container">
<el-upload ref="uploadRef" action="#" list-type="picture-card"
:show-file-list="false" :on-change="handleChange" :auto-upload="false" accept="image/*"
:class="{ 'upload-item': true }">
<div style="display: flex;flex-direction: column;align-items: center;justify-content: center;margin-top: -20px;">
<i class="el-icon-plus" style="font-size: 30px;" />
<span style="margin: 5px 0 -20px;line-height: 20px">上传</span>
</div>
</el-upload>
<div v-for="(url, index) in form.imageUrls" :key="index" class="image-item">
<img :src="url" class="avatar">
<span class="delete-icon" @click="handleRemoveImage(index)">
<i class="el-icon-delete"></i>
</span>
<div v-for="(image, index) in form.deviceImages" :key="index" class="preview-item">
<img :src="image.fileFullPath" class="preview-image" alt="">
<span class="preview-actions">
<span class="preview-action" @click="handlePictureCardPreview(image.fileFullPath)">
<i class="el-icon-zoom-in" />
</span>
<span class="preview-action" @click="handleRemoveImage(index)">
<i class="el-icon-delete" />
</span>
</span>
</div>
</div>
<el-dialog append-to-body :visible.sync="dialogVisible" top="20vh">
<div>
<img :src="dialogImageUrl" style="width: 100%" alt="Preview Image">
</div>
</el-dialog>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@ -98,10 +116,14 @@
<el-table-column type="selection" width="55" />
<el-table-column label="飞行器名称" prop="name" />
<el-table-column label="型号" prop="model" />
<el-table-column label="类型" prop="type" />
<el-table-column label="负责人" prop="manager" />
<el-table-column label="区域" prop="region" />
<el-table-column label="景区" prop="scenicArea" />
<el-table-column label="类型" prop="useType">
<template slot-scope="scope">
{{ scope.row.useType === 0 ? '载物' : scope.row.useType === 1 ? '载人' : '其他' }}
</template>
</el-table-column>
<el-table-column label="负责人" prop="employeeName" />
<el-table-column label="区域" prop="areaName" />
<el-table-column label="景区" prop="scenicName" />
<el-table-column label="创建时间" prop="createTime" width="180" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template slot-scope="scope">
@ -116,6 +138,8 @@
size="mini"
type="text"
@click="crud.toEdit(scope.row)"
v-permission="permission.edit"
v-if="checkPer(permission.edit)"
>
修改
</el-button>
@ -123,6 +147,8 @@
size="mini"
type="text"
@click="handleDelete(scope.row)"
v-permission="permission.del"
v-if="checkPer(permission.del)"
>
删除
</el-button>
@ -149,90 +175,92 @@
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import udOperation from '@crud/UD.operation'
import crudAircraft from '@/api/aircraft'
import areaApi from '@/api/system/area'
import { upload } from '@/utils/upload'
import { mapGetters } from 'vuex'
import rrOperation from '@crud/RR.operation'
// crudpresenter
const defaultForm = {
id: null,
id: 0,
name: null,
brand: null,
model: null,
type: '载人飞行',
manager: null,
role: '飞行员',
region: '北京',
scenicArea: '白云山',
imageUrls: [],
remarks: '',
useType: 0,
areaId: null,
scenicId: null,
employeesId: null,
deviceImages: [],
remark: '',
createTime: null
}
export default {
name: 'aircraft',
components: { udOperation },
cruds() {
return CRUD({ title: '飞行器', url: 'api/aircraft', crudMethod: { ...crudAircraft }})
return CRUD({
title: '飞行器',
url: 'aerocraftAdminApi/aircraft/device',
crudMethod: {
...crudAircraft,
getList: crudAircraft.getDevicePage
},
optShow: {
search: true
},
queryOnPresenterCreated: false, //
query: {
current: 1,
size: 10,
employeesId: undefined,
areaId: undefined,
name: undefined
}
})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
return {
dialogVisible: false,
dialogImageUrl: '',
uploadDisabled: false,
// ini
page: {
page: 1,
size: 10,
total: 5
},
areaOptions: [],
scenicOptions: [],
employeeOptions: [],
//
query: {
name: '',
employeesId: undefined,
areaId: undefined,
current: 1,
size: 10
},
defaultData: [{
id: 1,
name: '大疆',
model: 'T100',
type: '载人飞行',
manager: '小明',
region: '北京',
scenicArea: '白云山',
createTime: '2016-09-21 08:50:08',
imageUrls: ['https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png']
},
{
id: 2,
name: '大疆',
model: 'T100',
type: '载人飞行',
manager: '小明',
region: '北京',
scenicArea: '白云山',
createTime: '2016-09-21 08:50:08',
imageUrls: ['https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png']
},
{
id: 3,
name: '大疆',
model: 'T100',
type: '载人飞行',
manager: '小明',
region: '北京',
scenicArea: '白云山',
createTime: '2016-09-21 08:50:08',
imageUrls: ['https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png']
},
{
id: 4,
name: '大疆',
model: 'T100',
type: '载人飞行',
manager: '小明',
region: '北京',
scenicArea: '白云山',
createTime: '2016-09-21 08:50:08',
imageUrls: ['https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png']
},
{
id: 5,
name: '大疆',
model: 'T100',
type: '载人飞行',
manager: '小明',
region: '北京',
scenicArea: '白云山',
createTime: '2016-09-21 08:50:08',
imageUrls: ['https://axure-file.lanhuapp.com/md5__f344f816278c2a3f5164e4571c580ad9.png']
name: '无人机01',
brand: 'DJI',
model: 'Mavic Air 2',
useType: 0,
employeesId: 1,
employeeName: '张三',
areaId: 1,
areaName: '北京',
scenicId: 1,
scenicName: '故宫',
createTime: '2025-07-17 08:50:08',
deviceImages: [{
fileFullPath: 'https://example.com/image1.jpg',
fileSize: 102400,
fileType: 'jpg',
id: 1,
newFileName: 'drone_20250717.jpg',
sourceFileName: 'DJI_0001.jpg'
}]
}],
permission: {
add: ['admin', 'aircraft:add'],
@ -246,65 +274,173 @@ export default {
model: [
{ required: true, message: '请输入型号', trigger: 'blur' }
],
type: [
useType: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
manager: [
{ required: true, message: '请输入负责人', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
],
region: [
areaId: [
{ required: true, message: '请选择区域', trigger: 'change' }
],
scenicArea: [
scenicId: [
{ required: true, message: '请选择景区', trigger: 'change' }
],
imageUrls: [
{ required: true, message: '请上传无人机图片', trigger: 'change', type: 'array' }
employeesId: [
{ required: true, message: '请选择负责人', trigger: 'change' }
],
deviceImages: [
{ required: true, message: '请上传设备图片', trigger: 'change', type: 'array' }
]
},
query: {
name: '',
manager: '',
scenicArea: '北京'
}
}
},
computed: {
...mapGetters([
'imagesUploadApi',
'baseApi'
])
},
created() {
//
this.getAreas()
//
this.getScenics()
//
this.getEmployees()
},
methods: {
//
async getAreas() {
try {
const res = await areaApi.tree()
if (res) {
this.areaOptions = [
{
id: 0,
name: res.name,
sons: res.sons || []
},
...(res.sons || [])
]
}
} catch (error) {
this.$message.error('获取区域列表失败')
console.error('获取区域列表失败:', error)
this.areaOptions = []
}
},
//
async getScenics() {
// try {
// const test = {
// employeesId: this.query.employeesId || 0
// }
// const res = await crudAircraft.get(test)
// if (res.status === 200) {
// this.scenicOptions = res.data
// }
// } catch (error) {
// this.$message.error('')
// console.error(':', error)
// this.scenicOptions = []
// }
},
//
async getEmployees() {
try {
const test = {
employeesId: this.query.employeesId || 0
}
const res = await crudAircraft.get(test)
if (res.status === 200) {
this.employeeOptions = res.data
}
} catch (error) {
this.$message.error('获取飞行员列表失败')
console.error('获取飞行员列表失败:', error)
this.employeeOptions = []
}
},
[CRUD.HOOK.beforeRefresh]() {
// crud.data
if (this.crud.data.length === 0) {
this.crud.data = this.defaultData
// this.crud.resetDataStatus()
//
if (!this.query.employeesId) {
this.$message.warning('请先选择负责人')
return false
}
//
this.query.current = this.page.page
this.query.size = this.page.size
return true
},
[CRUD.HOOK.afterRefresh]() {
//
if (this.crud.data.total) {
this.page.total = this.crud.data.total
}
//
if (Array.isArray(this.crud.data)) {
this.crud.data = [...this.defaultData, ...this.crud.data]
//
this.page.total += this.defaultData.length
} else if (this.crud.data && this.crud.data.records) {
this.crud.data.records = [...this.defaultData, ...this.crud.data.records]
//
this.page.total += this.defaultData.length
}
},
handleView(row) {
this.$router.push({ path: '/aircraft/aircraftDetail', query: { id: row.id }})
},
handleAvatarSuccess(res, file) {
if (!this.form.imageUrls) {
this.form.imageUrls = []
}
this.form.imageUrls.push(res.url)
handlePictureCardPreview(url) {
this.dialogImageUrl = url
this.dialogVisible = true
},
//
handleRemoveImage(index) {
this.form.imageUrls.splice(index, 1)
this.form.deviceImages.splice(index, 1)
},
beforeAvatarUpload(file) {
const isImage = file.type.startsWith('image/')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('上传文件只能是图片格式!')
//
handleChange(file, uploadFiles) {
if (file.raw.type.includes('image')) {
this.loading = true
try {
upload(this.imagesUploadApi, file.raw).then(res => {
if(res.status === 200){
const data = res.data;
// deviceImages
const deviceImage = {
fileFullPath: this.baseApi + `/file/图片/` + data.newFileName,
fileSize: file.raw.size,
fileType: file.raw.type.split('/')[1],
id: 0,
newFileName: data.newFileName,
sourceFileName: file.raw.name
};
//
if (!this.form.deviceImages) {
this.form.deviceImages = [];
}
this.form.deviceImages.push(deviceImage);
this.$message.success('图片上传成功!')
this.loading = false;
}else{
this.$message.error('图片上传失败!');
this.loading = false;
return;
}
});
} catch (error) {
this.loading = false
this.$message.error('图片上传失败!')
}
} else {
this.$confirm('图片的格式不正确,请重新选择!', '提示', {
confirmButtonText: '确定',
type: 'warning'
})
uploadFiles.pop()
}
if (!isLt2M) {
this.$message.error('上传图片大小不能超过 2MB!')
}
return isImage && isLt2M
},
handleDelete(row) {
this.$confirm(`此操作将永久删除飞行器"${row.name}",是否继续?`, '提示', {
confirmButtonText: '确定',
@ -324,50 +460,65 @@ export default {
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.avatar-uploader {
.el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
&:hover {
border-color: #409EFF;
}
}
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.image-list {
.upload-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: flex-start;
.image-item {
position: relative;
margin-right: 10px;
margin-bottom: 10px;
.avatar {
width: 120px;
height: 120px;
display: block;
}
.upload-item {
::v-deep .el-upload--picture-card {
width: 110px !important;
height: 110px !important;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
}
}
.preview-item {
position: relative;
width: 110px;
height: 110px;
border: 1px solid #c0ccda;
border-radius: 6px;
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 6px;
}
.preview-actions {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 6px;
transition: opacity 0.3s;
&:hover {
opacity: 1;
}
.delete-icon {
position: absolute;
right: 5px;
top: 5px;
// background-color: rgba(0,0,0,0.5);
// color: white;
padding: 5px;
border-radius: 50%;
.preview-action {
color: #fff;
font-size: 18px;
margin: 0 7px;
cursor: pointer;
&:hover {
background-color: rgba(0,0,0,0.7);
opacity: 0.8;
}
}
}