Files
stamp-ant/src/views/contract/launch/components/1.vue
T
2026-07-10 17:03:55 +08:00

598 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<a-modal
v-model:visible="visible"
centered
title="公路运输产品新增"
width="800px"
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formState"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
:rules="rules"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="短驳类型" name="busType">
<a-select
v-model:value="formState.busType"
placeholder="请选择短驳类型"
allow-clear
>
<a-select-option value="集装箱">集装箱</a-select-option>
<a-select-option value="散杂货">散杂货</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="计价方式" name="priceType">
<a-select
v-model:value="formState.priceType"
placeholder="请选择计价方式"
allow-clear
>
<a-select-option value="1">一口价</a-select-option>
<a-select-option value="2">阶梯价</a-select-option>
</a-select>
</a-form-item>
</a-col>
<!-- 一口价时显示起运地类型和目的地类型 -->
<template v-if="formState.priceType === '1'">
<!-- 起运地类型前端UI控制字段 -->
<a-col :span="12">
<a-form-item label="起运地类型" name="fromLocUIType">
<a-select
v-model:value="formState.fromLocUIType"
placeholder="请选择起运地类型"
allow-clear
@change="handleFromLocTypeChange"
>
<a-select-option value="railway">铁路场站</a-select-option>
<a-select-option value="dock">码头</a-select-option>
<a-select-option value="door">门点地址</a-select-option>
</a-select>
</a-form-item>
</a-col>
<!-- 目的地类型前端UI控制字段 -->
<a-col :span="12">
<a-form-item label="目的地类型" name="toLocUIType">
<a-select
v-model:value="formState.toLocUIType"
placeholder="请选择目的地类型"
allow-clear
@change="handleToLocTypeChange"
>
<a-select-option value="railway">铁路场站</a-select-option>
<a-select-option value="dock">码头</a-select-option>
<a-select-option value="door">门点地址</a-select-option>
</a-select>
</a-form-item>
</a-col>
<!-- 起运地 -->
<a-col :span="12">
<a-form-item label="起运地" name="fromLoc">
<a-form-item-rest>
<div style="display: flex; align-items: center">
<!-- 铁路场站或码头使用下拉选择 -->
<a-select
v-if="formState.fromLocUIType === 'railway' || formState.fromLocUIType === 'dock'"
v-model:value="formState.fromLoc"
:placeholder="formState.fromLocUIType === 'railway' ? '请选择铁路场站' : '请选择码头'"
allow-clear
show-search
:filter-option="filterOption"
@change="handleFromStationSelect"
>
<a-select-option
v-for="item in fromStationOptions"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
<!-- 门点地址使用搜索选择 -->
<a-select
v-else
v-model:value="formState.fromLoc"
show-search
placeholder="请输入地点"
:filter-option="false"
@search="handleLocationSearch"
@select="handleRealSelect"
@dropdownVisibleChange="handleDropdownHide"
>
<a-select-option
v-for="item in locationOptions"
:key="item.uid"
:value="item.title"
:data="item"
>
<div>{{ item.title }}</div>
</a-select-option>
</a-select>
<!-- 只有门点地址才显示定位按钮 -->
<a-button
v-if="formState.fromLocUIType === 'door'"
@click="handleSelectCoordinates('from')"
type="primary"
shape="circle"
size="small"
style="margin-left: 8px"
>
<template #icon>
<EnvironmentOutlined />
</template>
</a-button>
</div>
</a-form-item-rest>
</a-form-item>
</a-col>
<!-- 目的地 -->
<a-col :span="12">
<a-form-item label="目的地" name="toLoc">
<a-form-item-rest>
<div style="display: flex; align-items: center">
<!-- 铁路场站或码头使用下拉选择 -->
<a-select
v-if="formState.toLocUIType === 'railway' || formState.toLocUIType === 'dock'"
v-model:value="formState.toLoc"
:placeholder="formState.toLocUIType === 'railway' ? '请选择铁路场站' : '请选择码头'"
allow-clear
show-search
:filter-option="filterOption"
@change="handleToStationSelect"
>
<a-select-option
v-for="item in toStationOptions"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
<!-- 门点地址使用下拉选择 -->
<a-select
v-else
v-model:value="formState.toLoc"
placeholder="请选择目的地"
allow-clear
show-search
:filter-option="filterOption"
@change="handleDoorToChange"
>
<a-select-option
v-for="item in stationOptions"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
<!-- 只有门点地址才显示定位按钮 -->
<a-button
v-if="formState.toLocUIType === 'door'"
@click="handleSelectCoordinates('to')"
type="primary"
shape="circle"
size="small"
style="margin-left: 8px"
>
<template #icon>
<EnvironmentOutlined />
</template>
</a-button>
</div>
</a-form-item-rest>
</a-form-item>
</a-col>
<!-- 价格 -->
<a-col :span="12">
<a-form-item label="价格(元)" name="basePrice">
<a-form-item-rest>
<div style="display: flex; align-items: center">
<a-input-number
style="flex: 1; margin-right: 10px"
v-model:value="formState.basePrice"
placeholder="请输入运价"
/>
<a-input disabled value="元" style="width: 100px" placeholder="单位" />
</div>
</a-form-item-rest>
</a-form-item>
</a-col>
</template>
<a-col :span="12">
<a-form-item label="运价备注" name="priceRemark">
<a-input
style="width: 100%"
v-model:value="formState.priceRemark"
placeholder="请输入运价备注"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="预计时长" name="estTime">
<a-form-item-rest>
<div style="display: flex; align-items: center">
<a-input-number
style="flex: 1; margin-right: 10px"
v-model:value="formState.estTime"
type="number"
min="0"
placeholder="请输入预计时长"
/>
<a-select
v-model:value="formState.estTimeUnit"
style="width: 100px"
placeholder="单位"
>
<a-select-option value="D"></a-select-option>
<a-select-option value="H">小时</a-select-option>
</a-select>
</div>
</a-form-item-rest>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="揽货备注" name="cargoRemark">
<a-input v-model:value="formState.cargoRemark" placeholder="请输入揽货备注" />
</a-form-item>
</a-col>
<gridientComp
v-if="formState.priceType === '2'"
v-model:modelValue="formState.elementTieredPriceList"
/>
</a-row>
</a-form>
<locationSelect ref="locationSelectRef" @location-selected="changeLocation" />
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick, watch } from 'vue';
import { FormInstance, message } from 'ant-design-vue';
import locationSelect from '../../../commonComponents/locationSelect.vue';
import { addTruckInfo, editTruckInfo, getTruckInfoById, getBaiduPlace } from '@/api/qcwl';
import * as commonApi from '@/api/common';
import type { RuleObject } from 'ant-design-vue/lib/form/interface';
import { noLoginlistStation } from '@/api/sysOrgRelation';
import { commonMixin } from '@/mixins/commonMixin';
import gridientComp from './gridientComp.vue';
const { filterOption } = commonMixin();
interface StationOption {
label: string;
value: string;
}
const emit = defineEmits(['refresh']);
const formRef = ref<FormInstance>();
const locationSelectRef = ref();
const visible = ref(false);
// 起运地站点选项(铁路场站/码头)
const fromStationOptions = ref<StationOption[]>([]);
// 目的地站点选项(铁路场站/码头)
const toStationOptions = ref<StationOption[]>([]);
// 门点地址选项
const stationOptions = ref<StationOption[]>([]);
// 位置搜索选项
const locationOptions = ref<any[]>([]);
let realInputValue = '';
// 当前操作的坐标类型(用于定位回调)
let currentCoordType: 'from' | 'to' = 'from';
const formState = reactive<any>({
id: undefined,
busType: '集装箱',
// 后端接口字段(保持不变)
fromLocType: 'city',
toLocType: 'railway',
// 前端UI控制字段(不提交给后端)
fromLocUIType: 'door',
toLocUIType: 'door',
fromLoc: undefined,
toLoc: undefined,
basePrice: undefined,
priceRemark: undefined,
estTime: undefined,
estTimeUnit: 'D',
cargoRemark: undefined,
fromLocPosition: '',
toLocPosition: '',
priceType: '1',
elementTieredPriceList: [],
});
const rules: Record<string, RuleObject | RuleObject[]> = {
busType: [{ required: true, message: '请选择短驳类型' }],
fromLocUIType: [{ required: true, message: '请选择起运地类型' }],
toLocUIType: [{ required: true, message: '请选择目的地类型' }],
fromLoc: [{ required: true, message: '请选择起运地' }],
toLoc: [{ required: true, message: '请选择目的地' }],
basePrice: [{ required: true, message: '请输入运价' }],
estTime: [{ required: true, message: '请输入预计时长' }],
estTimeUnit: [{ required: true, message: '请选择预计时长单位' }],
priceType: [{ required: true, message: '请选择计价方式' }],
};
const showModal = (row?: any) => {
// 初始化站点选项
initStationOptions();
// 初始化门点地址选项
initDoorStationOptions();
// 清空位置搜索
locationOptions.value = [];
if (row && row.id) {
getTruckInfoById(row.id).then((res) => {
// 从后端获取数据,UI类型默认设置为door
Object.assign(formState, {
...res.result,
fromLocUIType: 'door',
toLocUIType: 'door',
});
});
} else {
Object.assign(formState, {
id: undefined,
busType: '集装箱',
fromLocType: 'city',
toLocType: 'railway',
fromLocUIType: 'door',
toLocUIType: 'door',
fromLoc: undefined,
toLoc: undefined,
basePrice: undefined,
priceRemark: undefined,
estTime: undefined,
estTimeUnit: 'D',
cargoRemark: undefined,
fromLocPosition: '',
toLocPosition: '',
priceType: '1',
elementTieredPriceList: [],
});
}
visible.value = true;
};
// 初始化站点选项(铁路场站和码头)- 占位数据
const initStationOptions = () => {
// 铁路场站占位数据(待接口对接)
const railwayStations = [
{ label: '上海铁路场站', value: 'shanghai_railway' },
{ label: '北京铁路场站', value: 'beijing_railway' },
{ label: '广州铁路场站', value: 'guangzhou_railway' },
{ label: '深圳铁路场站', value: 'shenzhen_railway' },
{ label: '成都铁路场站', value: 'chengdu_railway' },
{ label: '武汉铁路场站', value: 'wuhan_railway' },
];
// 码头占位数据(待接口对接)
const dockStations = [
{ label: '上海港码头', value: 'shanghai_dock' },
{ label: '宁波港码头', value: 'ningbo_dock' },
{ label: '深圳港码头', value: 'shenzhen_dock' },
{ label: '广州港码头', value: 'guangzhou_dock' },
{ label: '青岛港码头', value: 'qingdao_dock' },
{ label: '天津港码头', value: 'tianjin_dock' },
];
// 合并所有站点供选择(实际使用时可区分铁路和码头)
fromStationOptions.value = [...railwayStations, ...dockStations];
toStationOptions.value = [...railwayStations, ...dockStations];
};
// 初始化门点地址选项
const initDoorStationOptions = () => {
noLoginlistStation({}).then((res) => {
if (res.code !== '0000') {
return Promise.reject(res);
}
stationOptions.value = res.result.map((item: any) => ({
label: item,
value: item,
}));
});
};
// 起运地类型变更
const handleFromLocTypeChange = (val: string) => {
formState.fromLoc = undefined;
formState.fromLocPosition = '';
// 如果不是门点地址,清空搜索选项
if (val !== 'door') {
locationOptions.value = [];
}
};
// 目的地类型变更
const handleToLocTypeChange = (val: string) => {
formState.toLoc = undefined;
formState.toLocPosition = '';
};
// 起运地铁路/码头选择
const handleFromStationSelect = (val: string) => {
// 从站点列表中查找对应的经纬度
const found = fromStationOptions.value.find((item) => item.value === val);
if (found) {
// TODO: 从后端获取对应站点的经纬度,目前使用示例坐标
formState.fromLocPosition = '121.4737,31.2304';
}
};
// 目的地铁路/码头选择
const handleToStationSelect = (val: string) => {
const found = toStationOptions.value.find((item) => item.value === val);
if (found) {
// TODO: 从后端获取对应站点的经纬度,目前使用示例坐标
formState.toLocPosition = '121.4737,31.2304';
}
};
// 起运地门点地址搜索选择
const handleRealSelect = (selectedTitle: string, option: any) => {
const selectedItem = option.data;
formState.fromLocPosition = `${selectedItem.point.lng},${selectedItem.point.lat}`;
};
// 起运地搜索
const handleLocationSearch = async (value: string) => {
realInputValue = value;
if (!value.trim()) {
locationOptions.value = [];
return;
}
try {
getBaiduPlace(value).then((res) => {
locationOptions.value = res.result.results.map((item: any) => ({
title: item.name,
point: { lat: item.location.lat, lng: item.location.lng },
uid: item.uid,
}));
});
} catch (error) {
console.error('搜索失败:', error);
}
};
// 起运地下拉隐藏
const handleDropdownHide = (visible: boolean) => {
if (!visible) {
nextTick(() => {
formState.fromLoc = realInputValue;
});
}
};
// 目的地门点地址选择
const handleDoorToChange = (val: string) => {
if (val) {
// TODO: 根据实际数据获取经纬度
formState.toLocPosition = '121.4737,31.2304';
}
};
// 选择坐标(起运地或目的地)
const handleSelectCoordinates = (type: 'from' | 'to') => {
currentCoordType = type;
locationSelectRef.value.showModal(formState);
};
// 坐标选择回调
const changeLocation = (data: { lng: number; lat: number }) => {
const position = `${data.lng},${data.lat}`;
if (currentCoordType === 'from') {
formState.fromLocPosition = position;
} else {
formState.toLocPosition = position;
}
};
const handleCancel = () => {
visible.value = false;
};
// 提交前处理:将UI类型映射到后端字段
const prepareSubmitData = () => {
const submitData = { ...formState };
// 根据UI类型设置后端字段(如果需要映射)
// 注意:fromLocType 和 toLocType 保持后端支持的值
// 如果 UI 类型是 railway,可以设置 fromLocType = 'railway' 等
// 但根据你的说明,后端不支持其他值,所以保持原样
// 删除前端UI控制字段(不提交给后端)
delete submitData.fromLocUIType;
delete submitData.toLocUIType;
return submitData;
};
const handleOk = async () => {
// 阶梯价校验
if (formState.priceType === '2') {
if (!formState.elementTieredPriceList || formState.elementTieredPriceList.length === 0) {
message.error('请设置梯度价格');
return;
}
const invalidRows = formState.elementTieredPriceList
.map((item: any, index: number) => ({
index: index + 1,
hasError: !item.distance || !item.price,
}))
.filter((item: any) => item.hasError);
if (invalidRows.length > 0) {
message.error(`第 ${invalidRows.map((r: any) => r.index).join(', ')} 行的距离或价格未填写`);
return;
}
}
// 一口价校验
if (formState.priceType === '1') {
if (!formState.fromLocPosition) {
message.error('请选择起运地经纬度');
return;
}
if (!formState.toLocPosition) {
message.error('请选择目的地经纬度');
return;
}
}
try {
await formRef.value?.validateFields();
const submitData = prepareSubmitData();
if (formState.id) {
editTruckInfo(submitData).then((res) => {
if (res.code === '0000') {
message.success('更新成功');
emit('refresh');
visible.value = false;
}
});
} else {
addTruckInfo(submitData).then((res) => {
if (res.code === '0000') {
message.success('新增成功');
emit('refresh');
visible.value = false;
}
});
}
} catch (error) {
console.log('验证失败:', error);
message.error('请检查表单数据');
}
};
defineExpose({ showModal });
</script>
<style scoped></style>