2025-09-18 10:57:17 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="port-page">
|
|
|
|
|
<div class="port-toolbar boxshadow">
|
|
|
|
|
<div class="toolbar-left">
|
|
|
|
|
<ElButton @click="showDialog('add')" v-ripple v-if="hasAuth('port@add')" type="primary"
|
|
|
|
|
>创建港口</ElButton
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="toolbar-right">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
placeholder="请输入客户名称"
|
|
|
|
|
clearable
|
|
|
|
|
style="width: 260px"
|
|
|
|
|
@keyup.enter="getPortList"
|
|
|
|
|
>
|
|
|
|
|
<template #suffix>
|
|
|
|
|
<el-icon class="clickable" @click="getPortList"><Search /></el-icon>
|
|
|
|
|
</template>
|
|
|
|
|
</el-input>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="boxshadow">
|
|
|
|
|
<ArtTable
|
|
|
|
|
ref="tableRef"
|
|
|
|
|
row-key="id"
|
|
|
|
|
:loading="loading"
|
|
|
|
|
:data="tableData"
|
|
|
|
|
:currentPage="pagination.currentPage"
|
|
|
|
|
:pageSize="pagination.pageSize"
|
|
|
|
|
:total="pagination.total"
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
>
|
|
|
|
|
<template #default>
|
2025-10-10 10:14:26 +08:00
|
|
|
<!-- <ElTableColumn label="港口代码" prop="port_code" min-width="120" /> -->
|
2025-09-18 10:57:17 +08:00
|
|
|
<ElTableColumn label="港口名称" prop="port_name" min-width="150" />
|
2025-10-10 10:14:26 +08:00
|
|
|
<ElTableColumn label="英文名称" prop="port_name_en" min-width="150" />
|
2025-09-18 10:57:17 +08:00
|
|
|
<ElTableColumn label="国家" prop="country_id" min-width="140">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<span>{{ scope.row.country_name }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<!-- <ElTableColumn label="纬度" prop="lat" min-width="100" /> -->
|
|
|
|
|
<!-- <ElTableColumn label="经度" prop="lng" min-width="100" /> -->
|
|
|
|
|
<ElTableColumn label="排序" prop="sort" width="80" />
|
|
|
|
|
<ElTableColumn label="状态" prop="status" width="100">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<ElTag :type="scope.row.status ? 'success' : 'info'">{{
|
|
|
|
|
scope.row.status ? '启用' : '停用'
|
|
|
|
|
}}</ElTag>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
<ElTableColumn label="创建时间" prop="created_at" min-width="160" />
|
|
|
|
|
<ElTableColumn fixed="right" label="操作" width="200px">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<ElRow>
|
|
|
|
|
<el-button link @click="showDialog('edit', scope.row)" v-if="hasAuth('port@update')"
|
|
|
|
|
>编辑</el-button
|
|
|
|
|
>
|
|
|
|
|
<el-button
|
|
|
|
|
type="danger"
|
|
|
|
|
link
|
|
|
|
|
@click="deletePort(scope.row)"
|
|
|
|
|
v-if="hasAuth('port@destroy')"
|
|
|
|
|
>删除</el-button
|
|
|
|
|
>
|
|
|
|
|
</ElRow>
|
|
|
|
|
</template>
|
|
|
|
|
</ElTableColumn>
|
|
|
|
|
</template>
|
|
|
|
|
</ArtTable>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ElDialog
|
|
|
|
|
v-model="dialogVisible"
|
|
|
|
|
:title="dialogType === 'add' ? '新增港口' : '编辑港口'"
|
|
|
|
|
align-center
|
|
|
|
|
width="720px"
|
|
|
|
|
>
|
|
|
|
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="110px" class="form">
|
|
|
|
|
<ElFormItem label="港口代码" prop="port_code">
|
|
|
|
|
<ElInput v-model="form.port_code" placeholder="请输入港口代码" />
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
<ElFormItem label="港口名称" prop="port_name">
|
|
|
|
|
<ElInput v-model="form.port_name" placeholder="请输入港口名称" />
|
|
|
|
|
</ElFormItem>
|
2025-10-10 10:14:26 +08:00
|
|
|
<ElFormItem label="英文名称" prop="port_name_en">
|
|
|
|
|
<ElInput v-model="form.port_name_en" placeholder="请输入港口英文名称" />
|
|
|
|
|
</ElFormItem>
|
2025-09-18 10:57:17 +08:00
|
|
|
<ElFormItem label="国家" prop="country_id">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="form.country_id"
|
|
|
|
|
filterable
|
|
|
|
|
placeholder="请选择国家"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="item in countryOptions"
|
|
|
|
|
:key="item.id"
|
|
|
|
|
:label="item.cn_addr"
|
|
|
|
|
:value="item.id"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
<!-- <ElFormItem label="纬度" prop="lat">
|
|
|
|
|
<ElInput v-model="form.lat" placeholder="请输入纬度" />
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
<ElFormItem label="经度" prop="lng">
|
|
|
|
|
<ElInput v-model="form.lng" placeholder="请输入经度" />
|
|
|
|
|
</ElFormItem> -->
|
|
|
|
|
<ElFormItem label="排序" prop="sort">
|
|
|
|
|
<ElInputNumber v-model="form.sort" :min="0" :max="9999" style="width: 100%" />
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
<ElFormItem label="港口描述" prop="description">
|
|
|
|
|
<ElInput
|
|
|
|
|
v-model="form.description"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="3"
|
|
|
|
|
maxlength="300"
|
|
|
|
|
show-word-limit
|
|
|
|
|
placeholder="请输入港口描述"
|
|
|
|
|
/>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
<ElFormItem label="状态">
|
|
|
|
|
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElForm>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
<ElButton @click="dialogVisible = false">取消</ElButton>
|
|
|
|
|
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</ElDialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import type { FormInstance, FormRules } from 'element-plus'
|
|
|
|
|
import { vehicleService } from '@/api/vehicleApi'
|
|
|
|
|
import { commonService } from '@/api/commonApi'
|
|
|
|
|
import { useAuth } from '@/composables/useAuth'
|
|
|
|
|
const { hasAuth } = useAuth()
|
|
|
|
|
defineOptions({ name: 'AdminVehiclePort' })
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
const dialogType = ref<'add' | 'edit'>('add')
|
|
|
|
|
const formRef = ref<FormInstance>()
|
|
|
|
|
const keyword = ref<string>('')
|
|
|
|
|
const countryFilter = ref<number | undefined>(undefined)
|
|
|
|
|
const statusFilter = ref<number | undefined>(undefined)
|
|
|
|
|
|
|
|
|
|
const countryOptions = ref<any[]>([])
|
|
|
|
|
const loadCountryOptions = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await commonService.getCountrySelect()
|
|
|
|
|
countryOptions.value = res || []
|
|
|
|
|
} catch (e) {
|
|
|
|
|
countryOptions.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const form = reactive<any>({
|
|
|
|
|
id: '',
|
|
|
|
|
status: 1,
|
|
|
|
|
sort: 0,
|
|
|
|
|
port_code: '',
|
|
|
|
|
port_name: '',
|
2025-10-10 10:14:26 +08:00
|
|
|
port_name_en: '',
|
2025-09-18 10:57:17 +08:00
|
|
|
lat: '',
|
|
|
|
|
lng: '',
|
|
|
|
|
description: '',
|
|
|
|
|
country_id: undefined
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const rules = reactive<FormRules>({
|
|
|
|
|
port_code: [{ required: true, message: '请输入港口代码', trigger: 'blur' }],
|
|
|
|
|
port_name: [{ required: true, message: '请输入港口名称', trigger: 'blur' }],
|
|
|
|
|
country_id: [{ required: true, message: '请选择国家', trigger: 'change' }]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const pagination = reactive({
|
|
|
|
|
currentPage: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
total: 0
|
|
|
|
|
})
|
|
|
|
|
const tableData = ref<any[]>([])
|
|
|
|
|
const tableRef = ref()
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadCountryOptions()
|
|
|
|
|
getPortList()
|
|
|
|
|
})
|
|
|
|
|
watch([keyword, countryFilter, statusFilter], () => {
|
|
|
|
|
pagination.currentPage = 1
|
|
|
|
|
getPortList()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const getPortList = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const { currentPage, pageSize } = pagination
|
|
|
|
|
const params = {
|
|
|
|
|
current: currentPage,
|
|
|
|
|
size: pageSize,
|
|
|
|
|
keyword: keyword.value
|
|
|
|
|
}
|
|
|
|
|
const { records, current, size, total } = await vehicleService.getPortList(params)
|
|
|
|
|
tableData.value = records || []
|
|
|
|
|
Object.assign(pagination, { currentPage: current, pageSize: size, total })
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取港口列表失败:', error)
|
|
|
|
|
ElMessage.error('获取港口列表失败')
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSizeChange = (newPageSize: number) => {
|
|
|
|
|
pagination.pageSize = newPageSize
|
|
|
|
|
getPortList()
|
|
|
|
|
}
|
|
|
|
|
const handleCurrentChange = (newCurrentPage: number) => {
|
|
|
|
|
pagination.currentPage = newCurrentPage
|
|
|
|
|
getPortList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showDialog = (type: 'add' | 'edit', row?: any) => {
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
dialogType.value = type
|
|
|
|
|
if (type === 'edit' && row) {
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
id: row.id,
|
|
|
|
|
status: row.status,
|
|
|
|
|
sort: row.sort ?? 0,
|
|
|
|
|
port_code: row.port_code,
|
|
|
|
|
port_name: row.port_name,
|
2025-10-10 10:14:26 +08:00
|
|
|
port_name_en: row.port_name_en,
|
2025-09-18 10:57:17 +08:00
|
|
|
lat: row.lat,
|
|
|
|
|
lng: row.lng,
|
|
|
|
|
description: row.description,
|
|
|
|
|
country_id: row.country_id
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
id: '',
|
|
|
|
|
status: 1,
|
|
|
|
|
sort: 0,
|
|
|
|
|
port_code: '',
|
|
|
|
|
port_name: '',
|
2025-10-10 10:14:26 +08:00
|
|
|
port_name_en: '',
|
2025-09-18 10:57:17 +08:00
|
|
|
lat: '',
|
|
|
|
|
lng: '',
|
|
|
|
|
description: '',
|
|
|
|
|
country_id: undefined
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deletePort = (row: any) => {
|
|
|
|
|
ElMessageBox.confirm('确定删除该港口吗?', '删除确认', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'error'
|
|
|
|
|
}).then(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const { message } = await vehicleService.portDelete({ id: row.id })
|
|
|
|
|
ElMessage.success(message)
|
|
|
|
|
getPortList()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除失败:', error)
|
|
|
|
|
ElMessage.error('删除失败')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
|
|
|
|
if (!formEl) return
|
|
|
|
|
await formEl.validate(async (valid) => {
|
|
|
|
|
if (!valid) return
|
|
|
|
|
try {
|
|
|
|
|
if (dialogType.value === 'add') {
|
|
|
|
|
const { message } = await vehicleService.portAdd({
|
|
|
|
|
status: form.status,
|
|
|
|
|
sort: form.sort,
|
|
|
|
|
port_code: form.port_code,
|
|
|
|
|
port_name: form.port_name,
|
2025-10-10 10:14:26 +08:00
|
|
|
port_name_en: form.port_name_en,
|
2025-09-18 10:57:17 +08:00
|
|
|
lat: form.lat,
|
|
|
|
|
lng: form.lng,
|
|
|
|
|
description: form.description,
|
|
|
|
|
country_id: form.country_id
|
|
|
|
|
})
|
|
|
|
|
ElMessage.success(message)
|
|
|
|
|
} else {
|
|
|
|
|
const { message } = await vehicleService.portEdit({
|
|
|
|
|
id: form.id,
|
|
|
|
|
status: form.status,
|
|
|
|
|
sort: form.sort,
|
|
|
|
|
port_code: form.port_code,
|
|
|
|
|
port_name: form.port_name,
|
2025-10-10 10:14:26 +08:00
|
|
|
port_name_en: form.port_name_en,
|
2025-09-18 10:57:17 +08:00
|
|
|
lat: form.lat,
|
|
|
|
|
lng: form.lng,
|
|
|
|
|
description: form.description,
|
|
|
|
|
country_id: form.country_id
|
|
|
|
|
})
|
|
|
|
|
ElMessage.success(message)
|
|
|
|
|
}
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
getPortList()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('操作失败:', error)
|
|
|
|
|
ElMessage.error('操作失败')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCountryLabel = (id: number | undefined) => {
|
|
|
|
|
if (!id) return '-'
|
|
|
|
|
const found = countryOptions.value.find((c: any) => c.id === id)
|
|
|
|
|
return found ? found.name : id
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
:deep(.el-table__empty-block) {
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
.port-page {
|
|
|
|
|
background: var(--art-main-bg-color);
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
.port-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
padding: 20px 20px;
|
|
|
|
|
background: var(--art-main-bg-color);
|
|
|
|
|
}
|
|
|
|
|
.port-search-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.form {
|
|
|
|
|
padding: 20px 0;
|
|
|
|
|
}
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
.el-col2 {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|