You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2231 lines
67 KiB
2231 lines
67 KiB
// pages/freight-calculator/index.js
|
|
const API = require('../../utils/api.js');
|
|
|
|
Page({
|
|
data: {
|
|
// 出发地信息
|
|
origin: {
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: ''
|
|
},
|
|
// 目的地信息
|
|
destination: {
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: ''
|
|
},
|
|
// 货物信息
|
|
goodsInfo: {
|
|
weight: '', // 重量(kg)
|
|
volume: '', // 体积(m³)
|
|
quantity: 1, // 数量
|
|
type: 'egg' // 货物类型,默认为鸡蛋
|
|
},
|
|
// 物流人员信息
|
|
logisticsPersonnel: [
|
|
{
|
|
id: 1,
|
|
name: '张三',
|
|
position: '物流经理',
|
|
experience: 5,
|
|
phone: '13800138000'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: '李四',
|
|
position: '物流经理',
|
|
experience: 3,
|
|
phone: '13900139000'
|
|
},
|
|
{
|
|
id: 3,
|
|
name: '王五',
|
|
position: '物流经理',
|
|
experience: 2,
|
|
phone: '13700137000'
|
|
}
|
|
],
|
|
// 选中的货源信息
|
|
selectedGoods: {},
|
|
// 选中的规格索引
|
|
selectedSpecIndex: 0,
|
|
// 计算结果
|
|
calculationResult: null,
|
|
// 地址选择器状态
|
|
showOriginPicker: false,
|
|
showDestinationPicker: false,
|
|
// 加载状态
|
|
loading: false,
|
|
// 历史记录
|
|
historyRecords: [],
|
|
// 地址选择器数据
|
|
regionList: {
|
|
provinces: [],
|
|
cities: [],
|
|
districts: []
|
|
},
|
|
// 地址选择器当前选中
|
|
pickerValues: {
|
|
origin: [0, 0, 0],
|
|
destination: [0, 0, 0]
|
|
},
|
|
// 运输模式
|
|
transportModes: ['整车运输', '零担拼车'],
|
|
transportModeIndex: 0,
|
|
|
|
// 包装类型
|
|
packagingTypes: ['标准包装', '加强包装'],
|
|
packagingIndex: 0,
|
|
|
|
// 车型
|
|
vehicleTypes: ['面包车', '依维柯', '平板', '厢式', '高栏车', '集装箱', '自卸', '冷藏', '保温', '棉被车', '爬梯车', '飞翼车', '高低板'],
|
|
vehicleTypeIndex: 0,
|
|
|
|
// 车长
|
|
truckLengths: ['1.4米', '1.8米', '2.7米', '3.3米', '3.8米', '4.2米'],
|
|
truckLengthIndex: 5,
|
|
|
|
// 天气状况
|
|
weatherTypes: ['晴朗', '多云', '小雨', '中雨', '大雨', '暴雨', '雪', '雾'],
|
|
weatherIndex: 0,
|
|
|
|
// 是否节假日
|
|
holidayOptions: ['否', '是'],
|
|
holidayIndex: 0,
|
|
|
|
// 时间段
|
|
timePeriods: ['上午', '下午', '晚上'],
|
|
timePeriodIndex: 0,
|
|
|
|
// 输入值
|
|
distance: '',
|
|
weight: '',
|
|
volume: '',
|
|
waitTime: '40',
|
|
|
|
// 车型信息
|
|
vehicleInfo: '面包车(1.4米): 0-500kg, 0-5m³',
|
|
|
|
// 计算结果
|
|
showResult: false,
|
|
feeRange: '',
|
|
marketPrice: '',
|
|
complianceStatus: '',
|
|
isCompliant: true,
|
|
detailText: '',
|
|
showDetail: false,
|
|
|
|
// 车型和车长对应关系
|
|
vehicleLengthMap: {
|
|
'面包车': ['1.4米', '1.8米', '2.7米', '3.3米', '3.8米', '4.2米'],
|
|
'依维柯': ['3.3米', '3.8米', '4.2米', '5米'],
|
|
'平板': ['4.2米', '5米', '6.2米', '6.8米', '7.7米', '8.2米', '8.7米', '9.6米', '11.7米', '12.5米', '13米', '13.7米', '15米', '16米', '17.5米'],
|
|
'厢式': ['4.2米', '5米', '6.2米', '6.8米', '7.7米', '8.2米', '8.7米', '9.6米', '11.7米', '12.5米'],
|
|
'高栏车': ['4.2米', '5米', '6.2米', '6.8米', '7.7米', '8.2米', '8.7米', '9.6米', '11.7米', '12.5米', '13米', '13.7米', '15米', '16米', '17.5米'],
|
|
'集装箱': ['9.6米', '11.7米', '12.5米', '13米', '13.7米', '15米', '16米', '17.5米'],
|
|
'自卸': ['4.2米', '5米', '6.2米', '6.8米', '7.7米', '8.2米', '8.7米', '9.6米'],
|
|
'冷藏': ['4.2米', '5米', '6.2米', '6.8米', '7.7米', '8.2米', '8.7米', '9.6米'],
|
|
'保温': ['4.2米', '5米', '6.2米', '6.8米'],
|
|
'棉被车': ['4.2米', '5米', '6.2米', '6.8米', '7.7米'],
|
|
'爬梯车': ['6.8米', '7.7米', '8.2米', '8.7米', '9.6米'],
|
|
'飞翼车': ['4.2米', '5米', '6.2米', '6.8米', '7.7米', '8.2米', '8.7米', '9.6米'],
|
|
'高低板': ['6.8米', '7.7米', '8.2米', '8.7米', '9.6米', '11.7米', '12.5米', '13米', '13.7米', '15米', '16米', '17.5米']
|
|
},
|
|
|
|
// 基础车型信息
|
|
baseInfo: {
|
|
'面包车': ['0-500kg', '0-5m³'],
|
|
'依维柯': ['500-1500kg', '5-12m³'],
|
|
'平板': ['3000-15000kg', '20-60m³'],
|
|
'厢式': ['2000-10000kg', '15-45m³'],
|
|
'高栏车': ['3000-15000kg', '20-60m³'],
|
|
'集装箱': ['8000-20000kg', '40-80m³'],
|
|
'自卸': ['5000-12000kg', '20-45m³'],
|
|
'冷藏': ['2000-8000kg', '15-40m³'],
|
|
'保温': ['2000-6000kg', '15-30m³'],
|
|
'棉被车': ['2000-8000kg', '15-40m³'],
|
|
'爬梯车': ['5000-12000kg', '20-45m³'],
|
|
'飞翼车': ['3000-10000kg', '20-45m³'],
|
|
'高低板': ['8000-20000kg', '40-80m³']
|
|
}
|
|
},
|
|
|
|
onLoad: function (options) {
|
|
// 页面加载时的初始化逻辑
|
|
console.log('物流运费估算页面加载,options:', options);
|
|
|
|
// 初始化地址选择器数据
|
|
this.initRegionData();
|
|
|
|
// 如果从商品详情页跳转过来,获取商品信息
|
|
if (options.goodsId) {
|
|
this.loadGoodsInfo(options.goodsId);
|
|
}
|
|
|
|
// 如果直接传递了货源信息 (goodsInfo)
|
|
if (options.goodsInfo) {
|
|
try {
|
|
const goodsInfo = JSON.parse(options.goodsInfo);
|
|
this.setData({
|
|
selectedGoods: goodsInfo,
|
|
'goodsInfo.weight': goodsInfo.grossWeight || '',
|
|
'goodsInfo.quantity': 1
|
|
});
|
|
} catch (e) {
|
|
console.error('解析货源信息失败:', e);
|
|
}
|
|
}
|
|
|
|
// 如果直接传递了货源信息 (goodsData)
|
|
if (options.goodsData) {
|
|
try {
|
|
// 尝试直接解析
|
|
let goodsData;
|
|
try {
|
|
goodsData = JSON.parse(options.goodsData);
|
|
} catch (e1) {
|
|
// 如果直接解析失败,尝试解码后再解析
|
|
try {
|
|
const decodedGoodsData = decodeURIComponent(options.goodsData);
|
|
goodsData = JSON.parse(decodedGoodsData);
|
|
} catch (e2) {
|
|
console.error('解析货源信息失败 (已尝试解码):', e2);
|
|
throw e2;
|
|
}
|
|
}
|
|
|
|
this.setData({
|
|
selectedGoods: goodsData,
|
|
'goodsInfo.weight': goodsData.grossWeight || '',
|
|
'goodsInfo.quantity': 1
|
|
});
|
|
|
|
// 设置出发地为商品所在地
|
|
const regionToUse = goodsData.fullRegion || goodsData.region;
|
|
if (regionToUse) {
|
|
console.log('商品所在地原始地址:', regionToUse);
|
|
const regionInfo = this.parseRegion(regionToUse);
|
|
console.log('解析后的地址:', regionInfo);
|
|
this.setData({
|
|
'origin.province': regionInfo.province || '',
|
|
'origin.city': regionInfo.city || '',
|
|
'origin.district': regionInfo.district || '',
|
|
'origin.detail': regionInfo.detail || ''
|
|
});
|
|
console.log('已设置出发地为商品所在地:', regionInfo);
|
|
}
|
|
} catch (e) {
|
|
console.error('解析货源信息失败:', e);
|
|
}
|
|
}
|
|
|
|
// 加载历史记录(在设置完 selectedGoods 后)
|
|
this.loadHistoryRecords();
|
|
},
|
|
|
|
// 初始化地址选择器数据
|
|
initRegionData: function () {
|
|
// 这里可以从API获取地址数据,或者使用本地数据
|
|
const provinces = [
|
|
'北京市', '上海市', '广东省', '江苏省', '浙江省', '山东省', '河南省', '四川省', '湖北省', '福建省',
|
|
'湖南省', '安徽省', '河北省', '辽宁省', '陕西省', '江西省', '云南省', '黑龙江省', '山西省', '广西壮族自治区',
|
|
'内蒙古自治区', '吉林省', '贵州省', '新疆维吾尔自治区', '甘肃省', '重庆市', '宁夏回族自治区', '青海省', '西藏自治区', '海南省'
|
|
];
|
|
|
|
this.setData({
|
|
'regionList.provinces': provinces
|
|
});
|
|
},
|
|
|
|
// 加载商品信息
|
|
loadGoodsInfo: function (goodsId) {
|
|
// 从本地存储获取商品信息
|
|
const goods = wx.getStorageSync('goods') || [];
|
|
const goodsItem = goods.find(item => item.id === goodsId || item.productId === goodsId);
|
|
|
|
if (goodsItem) {
|
|
// 设置选中的货源信息
|
|
this.setData({
|
|
selectedGoods: goodsItem
|
|
});
|
|
|
|
// 设置出发地为商品所在地
|
|
if (goodsItem.region) {
|
|
console.log('商品所在地原始地址:', goodsItem.region);
|
|
const regionInfo = this.parseRegion(goodsItem.region);
|
|
console.log('解析后的地址:', regionInfo);
|
|
this.setData({
|
|
'origin.province': regionInfo.province || '',
|
|
'origin.city': regionInfo.city || '',
|
|
'origin.district': regionInfo.district || '',
|
|
'origin.detail': regionInfo.detail || ''
|
|
});
|
|
console.log('已设置出发地为商品所在地:', regionInfo);
|
|
}
|
|
|
|
// 设置货物重量(如果有)
|
|
if (goodsItem.grossWeight) {
|
|
this.setData({
|
|
'goodsInfo.weight': goodsItem.grossWeight
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// 解析地区信息
|
|
parseRegion: function (region) {
|
|
if (!region) return {};
|
|
|
|
// 尝试多种分隔符解析地址
|
|
let parts = region.split(' ');
|
|
if (parts.length === 1) {
|
|
// 尝试使用其他分隔符
|
|
parts = region.split('\n');
|
|
if (parts.length === 1) {
|
|
parts = region.split(',');
|
|
}
|
|
}
|
|
|
|
console.log('地址解析 - 原始地址:', region);
|
|
console.log('地址解析 - 分割后:', parts);
|
|
|
|
// 提取省份、城市、区县
|
|
const province = parts[0] || '';
|
|
const city = parts[1] || '';
|
|
const district = parts[2] || '';
|
|
|
|
// 收集区级后面的详细地址信息
|
|
let detail = '';
|
|
if (parts.length > 3) {
|
|
detail = parts.slice(3).join(' ');
|
|
}
|
|
|
|
return {
|
|
province: province,
|
|
city: city,
|
|
district: district,
|
|
detail: detail
|
|
};
|
|
},
|
|
|
|
// 加载历史记录
|
|
loadHistoryRecords: function () {
|
|
this.setData({ loading: true });
|
|
|
|
const productId = this.data.selectedGoods ? (this.data.selectedGoods.productId || this.data.selectedGoods.id) : undefined;
|
|
|
|
API.getFreightHistory({
|
|
productId: productId
|
|
}).then(res => {
|
|
this.setData({ loading: false });
|
|
if (res.success) {
|
|
this.setData({
|
|
historyRecords: (res.data.records || []).slice(0, 3) // 只显示最近3条记录
|
|
});
|
|
} else {
|
|
console.error('获取历史记录失败:', res.message);
|
|
// 失败时使用本地存储作为 fallback
|
|
const history = wx.getStorageSync('freightCalculatorHistory') || [];
|
|
this.setData({
|
|
historyRecords: history.slice(0, 3) // 只显示最近3条记录
|
|
});
|
|
}
|
|
}).catch(err => {
|
|
this.setData({ loading: false });
|
|
console.error('获取历史记录失败:', err);
|
|
// 失败时使用本地存储作为 fallback
|
|
const history = wx.getStorageSync('freightCalculatorHistory') || [];
|
|
this.setData({
|
|
historyRecords: history.slice(0, 3) // 只显示最近3条记录
|
|
});
|
|
});
|
|
},
|
|
|
|
// 保存历史记录
|
|
saveHistoryRecord: function (result) {
|
|
const history = wx.getStorageSync('freightCalculatorHistory') || [];
|
|
const record = {
|
|
id: Date.now(),
|
|
origin: this.data.origin,
|
|
destination: this.data.destination,
|
|
goodsInfo: this.data.goodsInfo,
|
|
vehicleInfo: this.data.vehicleInfo,
|
|
transportMode: this.data.transportMode,
|
|
result: result,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
// 添加到历史记录开头
|
|
history.unshift(record);
|
|
// 只保留最近20条记录
|
|
const newHistory = history.slice(0, 20);
|
|
wx.setStorageSync('freightCalculatorHistory', newHistory);
|
|
|
|
// 更新页面数据
|
|
this.setData({
|
|
historyRecords: newHistory.slice(0, 3)
|
|
});
|
|
},
|
|
|
|
// 输入出发地
|
|
bindOriginInput: function (e) {
|
|
const { key } = e.currentTarget.dataset;
|
|
this.setData({
|
|
[`origin.${key}`]: e.detail.value
|
|
});
|
|
},
|
|
|
|
// 输入目的地
|
|
bindDestinationInput: function (e) {
|
|
const { key } = e.currentTarget.dataset;
|
|
this.setData({
|
|
[`destination.${key}`]: e.detail.value
|
|
});
|
|
},
|
|
|
|
// 输入货物信息
|
|
bindGoodsInfoInput: function (e) {
|
|
const { key } = e.currentTarget.dataset;
|
|
this.setData({
|
|
[`goodsInfo.${key}`]: e.detail.value
|
|
});
|
|
},
|
|
|
|
// 选择规格组合
|
|
bindSpecChange: function (e) {
|
|
const index = e.detail.value;
|
|
this.setData({
|
|
selectedSpecIndex: index
|
|
});
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 地址选择器值变化
|
|
bindRegionChange: function (e) {
|
|
const { type } = e.currentTarget.dataset;
|
|
const values = e.detail.value;
|
|
|
|
// 更新地址信息
|
|
if (type === 'origin') {
|
|
this.setData({
|
|
'origin.province': values[0],
|
|
'origin.city': values[1],
|
|
'origin.district': values[2]
|
|
});
|
|
} else if (type === 'destination') {
|
|
this.setData({
|
|
'destination.province': values[0],
|
|
'destination.city': values[1],
|
|
'destination.district': values[2]
|
|
});
|
|
}
|
|
},
|
|
|
|
// 打开出发地选择器
|
|
openOriginPicker: function () {
|
|
this.setData({
|
|
showOriginPicker: true
|
|
});
|
|
},
|
|
|
|
// 打开目的地选择器
|
|
openDestinationPicker: function () {
|
|
this.setData({
|
|
showDestinationPicker: true
|
|
});
|
|
},
|
|
|
|
// 关闭地址选择器
|
|
closePicker: function () {
|
|
this.setData({
|
|
showOriginPicker: false,
|
|
showDestinationPicker: false
|
|
});
|
|
},
|
|
|
|
// 选择地址
|
|
selectAddress: function (e) {
|
|
const { type, address } = e.detail;
|
|
if (type === 'origin') {
|
|
this.setData({
|
|
origin: address,
|
|
showOriginPicker: false
|
|
});
|
|
} else if (type === 'destination') {
|
|
this.setData({
|
|
destination: address,
|
|
showDestinationPicker: false
|
|
});
|
|
}
|
|
},
|
|
|
|
// 使用当前位置作为目的地
|
|
useCurrentLocation: function () {
|
|
const that = this;
|
|
wx.getLocation({
|
|
type: 'wgs84',
|
|
success: function (res) {
|
|
// 显示加载提示
|
|
wx.showLoading({
|
|
title: '获取地址中...',
|
|
mask: true
|
|
});
|
|
|
|
// 使用微信的地址解析服务获取详细地址
|
|
wx.request({
|
|
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
data: {
|
|
location: `${res.latitude},${res.longitude}`,
|
|
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77' // 这里使用腾讯地图API密钥,实际项目中应该替换为自己的密钥
|
|
},
|
|
success: function (response) {
|
|
wx.hideLoading();
|
|
if (response.data.status === 0) {
|
|
const result = response.data.result;
|
|
that.setData({
|
|
destination: {
|
|
province: result.address_component.province || '',
|
|
city: result.address_component.city || '',
|
|
district: result.address_component.district || '',
|
|
detail: result.address || '',
|
|
latitude: res.latitude,
|
|
longitude: res.longitude
|
|
}
|
|
});
|
|
wx.showToast({
|
|
title: '地址获取成功',
|
|
icon: 'success'
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
wx.hideLoading();
|
|
console.error('地址解析失败:', err);
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
fail: function (err) {
|
|
console.error('获取位置失败:', err);
|
|
wx.showToast({
|
|
title: '获取位置失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
// 使用当前位置作为出发地
|
|
useCurrentLocationForOrigin: function () {
|
|
const that = this;
|
|
wx.getLocation({
|
|
type: 'wgs84',
|
|
success: function (res) {
|
|
// 显示加载提示
|
|
wx.showLoading({
|
|
title: '获取地址中...',
|
|
mask: true
|
|
});
|
|
|
|
// 使用微信的地址解析服务获取详细地址
|
|
wx.request({
|
|
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
data: {
|
|
location: `${res.latitude},${res.longitude}`,
|
|
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77' // 这里使用腾讯地图API密钥,实际项目中应该替换为自己的密钥
|
|
},
|
|
success: function (response) {
|
|
wx.hideLoading();
|
|
if (response.data.status === 0) {
|
|
const result = response.data.result;
|
|
that.setData({
|
|
origin: {
|
|
province: result.address_component.province || '',
|
|
city: result.address_component.city || '',
|
|
district: result.address_component.district || '',
|
|
detail: result.address || '',
|
|
latitude: res.latitude,
|
|
longitude: res.longitude
|
|
}
|
|
});
|
|
wx.showToast({
|
|
title: '地址获取成功',
|
|
icon: 'success'
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
wx.hideLoading();
|
|
console.error('地址解析失败:', err);
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
fail: function (err) {
|
|
console.error('获取位置失败:', err);
|
|
wx.showToast({
|
|
title: '获取位置失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
// 手动选择出发地位置
|
|
chooseOriginLocation: function () {
|
|
const that = this;
|
|
wx.chooseLocation({
|
|
success(res) {
|
|
console.log('用户选择的出发地位置信息:', res);
|
|
const name = res.name;
|
|
const address = res.address;
|
|
const latitude = res.latitude;
|
|
const longitude = res.longitude;
|
|
|
|
// 使用腾讯地图API进行逆地理编码,获取详细地址信息
|
|
wx.request({
|
|
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
data: {
|
|
location: `${latitude},${longitude}`,
|
|
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
|
|
},
|
|
success: function (response) {
|
|
if (response.data.status === 0) {
|
|
const result = response.data.result;
|
|
that.setData({
|
|
origin: {
|
|
province: result.address_component.province || '',
|
|
city: result.address_component.city || '',
|
|
district: result.address_component.district || '',
|
|
detail: `${name} ${address}`,
|
|
latitude: latitude,
|
|
longitude: longitude
|
|
}
|
|
});
|
|
wx.showToast({
|
|
title: '出发地选择成功',
|
|
icon: 'success'
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
console.error('地址解析失败:', err);
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
fail(err) {
|
|
console.error('选择出发地位置失败:', err);
|
|
if (err.errMsg === 'chooseLocation:fail auth deny') {
|
|
wx.showToast({
|
|
title: '需要位置授权才能选择地点',
|
|
icon: 'none',
|
|
duration: 2000
|
|
});
|
|
// 引导用户重新授权
|
|
that.requestLocationAuth();
|
|
} else {
|
|
wx.showToast({
|
|
title: '选择位置失败',
|
|
icon: 'none',
|
|
duration: 2000
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// 手动选择目的地位置
|
|
chooseDestinationLocation: function () {
|
|
const that = this;
|
|
wx.chooseLocation({
|
|
success(res) {
|
|
console.log('用户选择的目的地位置信息:', res);
|
|
const name = res.name;
|
|
const address = res.address;
|
|
const latitude = res.latitude;
|
|
const longitude = res.longitude;
|
|
|
|
// 使用腾讯地图API进行逆地理编码,获取详细地址信息
|
|
wx.request({
|
|
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
data: {
|
|
location: `${latitude},${longitude}`,
|
|
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
|
|
},
|
|
success: function (response) {
|
|
if (response.data.status === 0) {
|
|
const result = response.data.result;
|
|
that.setData({
|
|
destination: {
|
|
province: result.address_component.province || '',
|
|
city: result.address_component.city || '',
|
|
district: result.address_component.district || '',
|
|
detail: `${name} ${address}`,
|
|
latitude: latitude,
|
|
longitude: longitude
|
|
}
|
|
});
|
|
wx.showToast({
|
|
title: '目的地选择成功',
|
|
icon: 'success'
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
console.error('地址解析失败:', err);
|
|
wx.showToast({
|
|
title: '地址解析失败,请手动输入',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
fail(err) {
|
|
console.error('选择目的地位置失败:', err);
|
|
if (err.errMsg === 'chooseLocation:fail auth deny') {
|
|
wx.showToast({
|
|
title: '需要位置授权才能选择地点',
|
|
icon: 'none',
|
|
duration: 2000
|
|
});
|
|
// 引导用户重新授权
|
|
that.requestLocationAuth();
|
|
} else {
|
|
wx.showToast({
|
|
title: '选择位置失败',
|
|
icon: 'none',
|
|
duration: 2000
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// 请求位置授权
|
|
requestLocationAuth: function () {
|
|
const that = this;
|
|
|
|
console.log('开始请求位置授权');
|
|
|
|
wx.showLoading({
|
|
title: '请求授权中...',
|
|
mask: true
|
|
});
|
|
|
|
// 检查用户是否已经拒绝过授权
|
|
wx.getSetting({
|
|
success(res) {
|
|
wx.hideLoading();
|
|
if (res.authSetting['scope.userLocation'] === false) {
|
|
// 用户已经拒绝过授权,直接引导到设置页面
|
|
wx.showModal({
|
|
title: '需要位置授权',
|
|
content: '请在设置中开启位置授权,以便我们为您提供相关服务',
|
|
showCancel: true,
|
|
cancelText: '取消',
|
|
confirmText: '去授权',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
// 打开设置页面让用户手动开启授权
|
|
wx.openSetting({
|
|
success: (settingRes) => {
|
|
console.log('设置页面返回结果:', settingRes);
|
|
if (settingRes.authSetting['scope.userLocation']) {
|
|
// 用户在设置中开启了位置授权
|
|
wx.showToast({
|
|
title: '授权成功',
|
|
icon: 'success',
|
|
duration: 1500
|
|
});
|
|
} else {
|
|
// 用户在设置中仍未开启位置授权
|
|
wx.showToast({
|
|
title: '您已拒绝位置授权',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
console.error('打开设置失败:', err);
|
|
wx.showToast({
|
|
title: '打开设置失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// 用户未拒绝过授权,调用 authorize
|
|
wx.authorize({
|
|
scope: 'scope.userLocation',
|
|
success() {
|
|
console.log('位置授权成功');
|
|
wx.showToast({
|
|
title: '授权成功',
|
|
icon: 'success',
|
|
duration: 1500
|
|
});
|
|
},
|
|
fail(err) {
|
|
console.log('位置授权失败:', err);
|
|
// 授权失败,弹出模态框引导用户重新授权
|
|
wx.showModal({
|
|
title: '需要位置授权',
|
|
content: '请在设置中开启位置授权,以便我们为您提供相关服务',
|
|
showCancel: true,
|
|
cancelText: '取消',
|
|
confirmText: '去授权',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
// 打开设置页面让用户手动开启授权
|
|
wx.openSetting({
|
|
success: (settingRes) => {
|
|
console.log('设置页面返回结果:', settingRes);
|
|
if (settingRes.authSetting['scope.userLocation']) {
|
|
// 用户在设置中开启了位置授权
|
|
wx.showToast({
|
|
title: '授权成功',
|
|
icon: 'success',
|
|
duration: 1500
|
|
});
|
|
} else {
|
|
// 用户在设置中仍未开启位置授权
|
|
wx.showToast({
|
|
title: '您已拒绝位置授权',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
console.error('打开设置失败:', err);
|
|
wx.showToast({
|
|
title: '打开设置失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
},
|
|
fail(err) {
|
|
wx.hideLoading();
|
|
console.error('获取设置失败:', err);
|
|
wx.showToast({
|
|
title: '获取设置失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
// 计算运费
|
|
calculateFreight: function () {
|
|
// 验证输入
|
|
if (!this.data.origin.province || !this.data.destination.province) {
|
|
wx.showToast({
|
|
title: '请填写完整的出发地和目的地',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 如果没有重量和体积,使用默认值 1
|
|
if (!this.data.goodsInfo.weight && !this.data.goodsInfo.volume) {
|
|
this.setData({
|
|
'goodsInfo.weight': 1
|
|
});
|
|
}
|
|
|
|
this.setData({ loading: true });
|
|
|
|
// 构建请求参数
|
|
const params = {
|
|
origin: this.data.origin,
|
|
destination: this.data.destination,
|
|
goodsInfo: this.data.goodsInfo,
|
|
productId: this.data.selectedGoods.productId || this.data.selectedGoods.id
|
|
};
|
|
|
|
// 调用API计算运费
|
|
API.calculateFreight(params).then(res => {
|
|
this.setData({ loading: false });
|
|
if (res.success) {
|
|
this.setData({ calculationResult: res.data });
|
|
// 保存历史记录
|
|
this.saveHistoryRecord(res.data);
|
|
} else {
|
|
wx.showToast({
|
|
title: res.message || '计算失败,请稍后重试',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
}).catch(err => {
|
|
this.setData({ loading: false });
|
|
console.error('计算运费失败:', err);
|
|
wx.showToast({
|
|
title: '网络错误,请稍后重试',
|
|
icon: 'none'
|
|
});
|
|
});
|
|
},
|
|
|
|
// 清空输入
|
|
clearInput: function () {
|
|
this.setData({
|
|
origin: {
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: ''
|
|
},
|
|
destination: {
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: ''
|
|
},
|
|
goodsInfo: {
|
|
weight: '',
|
|
volume: '',
|
|
quantity: 1
|
|
},
|
|
// 运输参数
|
|
transportModeIndex: 0,
|
|
packagingIndex: 0,
|
|
vehicleTypeIndex: 0,
|
|
truckLengthIndex: 5,
|
|
weight: '',
|
|
volume: '',
|
|
vehicleInfo: '面包车(1.4米): 0-500kg, 0-5m³',
|
|
// 计算结果
|
|
showResult: false,
|
|
feeRange: '',
|
|
marketPrice: '',
|
|
complianceStatus: '',
|
|
isCompliant: true,
|
|
detailText: '',
|
|
calculationResult: null,
|
|
showDetail: false
|
|
});
|
|
},
|
|
|
|
// 使用历史记录
|
|
useHistoryRecord: function (e) {
|
|
const index = e.currentTarget.dataset.index;
|
|
const record = this.data.historyRecords[index];
|
|
|
|
this.setData({
|
|
origin: record.origin,
|
|
destination: record.destination,
|
|
goodsInfo: record.goodsInfo,
|
|
transportMode: record.transportMode,
|
|
calculationResult: record.result
|
|
});
|
|
},
|
|
|
|
// 删除历史记录
|
|
deleteHistoryRecord: function (e) {
|
|
const index = e.currentTarget.dataset.index;
|
|
const history = wx.getStorageSync('freightCalculatorHistory') || [];
|
|
history.splice(index, 1);
|
|
wx.setStorageSync('freightCalculatorHistory', history);
|
|
|
|
this.setData({
|
|
historyRecords: history.slice(0, 10)
|
|
});
|
|
},
|
|
|
|
// 清空历史记录
|
|
clearHistory: function () {
|
|
wx.setStorageSync('freightCalculatorHistory', []);
|
|
this.setData({
|
|
historyRecords: []
|
|
});
|
|
},
|
|
|
|
// 返回上一页
|
|
navigateBack: function () {
|
|
wx.navigateBack({
|
|
delta: 1
|
|
});
|
|
},
|
|
|
|
// 拨打电话
|
|
makePhoneCall: function (e) {
|
|
const phone = e.currentTarget.dataset.phone;
|
|
if (phone) {
|
|
wx.makePhoneCall({
|
|
phoneNumber: phone,
|
|
success: function () {
|
|
console.log('拨打电话成功');
|
|
},
|
|
fail: function (err) {
|
|
console.error('拨打电话失败:', err);
|
|
wx.showToast({
|
|
title: '拨打电话失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
// 将地址转换为经纬度
|
|
convertAddressToCoordinates: function (origin, destination) {
|
|
return new Promise((resolve, reject) => {
|
|
// 转换出发地地址
|
|
const convertOrigin = new Promise((resolveOrigin) => {
|
|
// 如果出发地已经有经纬度,直接返回
|
|
if (origin.latitude && origin.longitude) {
|
|
resolveOrigin(origin);
|
|
return;
|
|
}
|
|
|
|
// 构建出发地完整地址
|
|
const originAddress = `${origin.province}${origin.city}${origin.district}${origin.detail}`;
|
|
if (!originAddress) {
|
|
resolveOrigin(origin);
|
|
return;
|
|
}
|
|
|
|
// 使用腾讯地图地理编码API
|
|
wx.request({
|
|
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
data: {
|
|
address: originAddress,
|
|
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
|
|
},
|
|
success: function (res) {
|
|
if (res.data.status === 0) {
|
|
const location = res.data.result.location;
|
|
resolveOrigin({
|
|
...origin,
|
|
latitude: location.lat,
|
|
longitude: location.lng
|
|
});
|
|
} else {
|
|
console.error('出发地地址转换失败:', res.data);
|
|
resolveOrigin(origin);
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
console.error('出发地地址转换失败:', err);
|
|
resolveOrigin(origin);
|
|
}
|
|
});
|
|
});
|
|
|
|
// 转换目的地地址
|
|
const convertDestination = new Promise((resolveDestination) => {
|
|
// 如果目的地已经有经纬度,直接返回
|
|
if (destination.latitude && destination.longitude) {
|
|
resolveDestination(destination);
|
|
return;
|
|
}
|
|
|
|
// 构建目的地完整地址
|
|
const destinationAddress = `${destination.province}${destination.city}${destination.district}${destination.detail}`;
|
|
if (!destinationAddress) {
|
|
resolveDestination(destination);
|
|
return;
|
|
}
|
|
|
|
// 使用腾讯地图地理编码API
|
|
wx.request({
|
|
url: 'https://apis.map.qq.com/ws/geocoder/v1/',
|
|
data: {
|
|
address: destinationAddress,
|
|
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
|
|
},
|
|
success: function (res) {
|
|
if (res.data.status === 0) {
|
|
const location = res.data.result.location;
|
|
resolveDestination({
|
|
...destination,
|
|
latitude: location.lat,
|
|
longitude: location.lng
|
|
});
|
|
} else {
|
|
console.error('目的地地址转换失败:', res.data);
|
|
resolveDestination(destination);
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
console.error('目的地地址转换失败:', err);
|
|
resolveDestination(destination);
|
|
}
|
|
});
|
|
});
|
|
|
|
// 等待两个地址转换完成
|
|
Promise.all([convertOrigin, convertDestination]).then(([originWithCoords, destinationWithCoords]) => {
|
|
resolve({ originWithCoords, destinationWithCoords });
|
|
}).catch(err => {
|
|
reject(err);
|
|
});
|
|
});
|
|
},
|
|
|
|
// 切换费用明细显示/隐藏
|
|
toggleDetail: function () {
|
|
this.setData({
|
|
showDetail: !this.data.showDetail
|
|
});
|
|
},
|
|
|
|
// 运输模式选择
|
|
bindTransportModeChange: function(e) {
|
|
this.setData({
|
|
transportModeIndex: e.detail.value
|
|
});
|
|
},
|
|
|
|
// 包装类型选择
|
|
bindPackagingChange: function(e) {
|
|
this.setData({
|
|
packagingIndex: e.detail.value
|
|
});
|
|
},
|
|
|
|
// 车型选择
|
|
bindVehicleTypeChange: function(e) {
|
|
const vehicleTypeIndex = e.detail.value;
|
|
const vehicleType = this.data.vehicleTypes[vehicleTypeIndex];
|
|
|
|
// 更新车长选项
|
|
const truckLengths = this.data.vehicleLengthMap[vehicleType];
|
|
|
|
this.setData({
|
|
vehicleTypeIndex: vehicleTypeIndex,
|
|
truckLengths: truckLengths,
|
|
truckLengthIndex: 0
|
|
});
|
|
|
|
// 更新车型信息
|
|
this.updateVehicleInfo();
|
|
},
|
|
|
|
// 车长选择
|
|
bindTruckLengthChange: function(e) {
|
|
this.setData({
|
|
truckLengthIndex: e.detail.value
|
|
});
|
|
|
|
// 更新车型信息
|
|
this.updateVehicleInfo();
|
|
},
|
|
|
|
|
|
|
|
// 输入处理
|
|
bindWeightInput: function(e) {
|
|
this.setData({ weight: e.detail.value });
|
|
},
|
|
|
|
bindVolumeInput: function(e) {
|
|
this.setData({ volume: e.detail.value });
|
|
},
|
|
|
|
// 更新车型信息
|
|
updateVehicleInfo: function() {
|
|
const vehicleType = this.data.vehicleTypes[this.data.vehicleTypeIndex];
|
|
const truckLength = this.data.truckLengths[this.data.truckLengthIndex];
|
|
|
|
if (this.data.baseInfo[vehicleType]) {
|
|
const [weightRange, volumeRange] = this.data.baseInfo[vehicleType];
|
|
this.setData({
|
|
vehicleInfo: `${vehicleType}(${truckLength}): ${weightRange}, ${volumeRange}`
|
|
});
|
|
} else {
|
|
this.setData({ vehicleInfo: '请选择车型' });
|
|
}
|
|
},
|
|
|
|
// 计算运费
|
|
calculate: function(e) {
|
|
try {
|
|
// 获取输入参数
|
|
const transportMode = this.data.transportModes[this.data.transportModeIndex];
|
|
const weight = parseFloat(this.data.weight);
|
|
const volume = parseFloat(this.data.volume);
|
|
const packagingType = this.data.packagingIndex === 0 ? 'standard' : 'enhanced';
|
|
const vehicleType = this.data.vehicleTypes[this.data.vehicleTypeIndex];
|
|
const truckLength = this.data.truckLengths[this.data.truckLengthIndex];
|
|
|
|
// 验证输入
|
|
if (isNaN(weight) || weight <= 0) {
|
|
wx.showToast({ title: '鸡蛋重量必须大于0', icon: 'none' });
|
|
return;
|
|
}
|
|
if (isNaN(volume) || volume <= 0) {
|
|
wx.showToast({ title: '货物体积必须大于0', icon: 'none' });
|
|
return;
|
|
}
|
|
|
|
// 检查是否有出发地和目的地信息
|
|
if (!this.data.origin.province || !this.data.destination.province) {
|
|
wx.showToast({ title: '请选择出发地和目的地', icon: 'none' });
|
|
return;
|
|
}
|
|
|
|
this.setData({ loading: true });
|
|
|
|
// 更新 goodsInfo 中的重量和体积
|
|
this.setData({
|
|
'goodsInfo.weight': weight,
|
|
'goodsInfo.volume': volume
|
|
});
|
|
|
|
// 构建请求参数
|
|
const params = {
|
|
origin: this.data.origin,
|
|
destination: this.data.destination,
|
|
goodsInfo: {
|
|
weight: weight,
|
|
volume: volume,
|
|
quantity: this.data.goodsInfo.quantity || 1,
|
|
type: this.data.goodsInfo.type || 'egg'
|
|
},
|
|
vehicleInfo: this.data.vehicleInfo,
|
|
productId: this.data.selectedGoods.productId || this.data.selectedGoods.id,
|
|
transportMode: transportMode,
|
|
vehicleType: vehicleType,
|
|
truckLength: truckLength,
|
|
packagingType: packagingType
|
|
};
|
|
|
|
// 先将手写地址转换为经纬度
|
|
this.convertAddressToCoordinates(this.data.origin, this.data.destination).then(({ originWithCoords, destinationWithCoords }) => {
|
|
// 更新参数中的地址信息,包含经纬度
|
|
const paramsWithCoords = {
|
|
...params,
|
|
origin: originWithCoords,
|
|
destination: destinationWithCoords
|
|
};
|
|
|
|
// 调用API计算运费,获取距离信息
|
|
API.calculateFreight(paramsWithCoords).then(res => {
|
|
this.setData({ loading: false });
|
|
if (res.success) {
|
|
// 使用API返回的距离
|
|
const distance = res.data.distance || 100;
|
|
|
|
// 自动获取当前时间、天气情况和是否节假日
|
|
const { weather, holiday, timePeriod } = this.getAutoData();
|
|
|
|
// 调用计算函数(使用默认等候时间40分钟)
|
|
const result = this.calculateEggShippingFee(
|
|
transportMode, distance, weight, volume, packagingType, vehicleType, truckLength, 40, weather, holiday, timePeriod
|
|
);
|
|
|
|
// 更新结果
|
|
this.setData({
|
|
showResult: true,
|
|
feeRange: `${result.feeMin} - ${result.feeMax}元/车`,
|
|
marketPrice: `${result.marketPrice}元`,
|
|
complianceStatus: result.compliance.isCompliant ? '✓ 运输方案合规' : '✗ 运输方案不合规',
|
|
isCompliant: result.compliance.isCompliant,
|
|
detailText: this.generateDetailText(result, transportMode, weight, volume, packagingType, 40),
|
|
calculationResult: res.data // 保存API返回的结果
|
|
});
|
|
|
|
// 保存历史记录
|
|
this.saveHistoryRecord({
|
|
freight: (result.feeMin + result.feeMax) / 2,
|
|
distance: distance,
|
|
deliveryTime: Math.ceil(distance / 80), // 简单估算,假设平均速度80km/h
|
|
feeRange: `${result.feeMin} - ${result.feeMax}元/车`,
|
|
marketPrice: result.marketPrice,
|
|
complianceStatus: result.compliance.isCompliant ? '合规' : '不合规'
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: res.message || '计算失败,请稍后重试',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
}).catch(err => {
|
|
this.setData({ loading: false });
|
|
console.error('计算运费失败:', err);
|
|
wx.showToast({
|
|
title: '网络错误,请稍后重试',
|
|
icon: 'none'
|
|
});
|
|
});
|
|
}).catch(err => {
|
|
this.setData({ loading: false });
|
|
console.error('地址转换失败:', err);
|
|
wx.showToast({
|
|
title: '地址转换失败,请稍后重试',
|
|
icon: 'none'
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
this.setData({ loading: false });
|
|
wx.showToast({ title: '计算出错,请检查输入', icon: 'none' });
|
|
console.error(error);
|
|
}
|
|
},
|
|
|
|
// 自动获取当前时间、天气情况和是否节假日
|
|
getAutoData: function() {
|
|
// 获取当前时间
|
|
const now = new Date();
|
|
const hour = now.getHours();
|
|
|
|
// 确定时间段
|
|
let timePeriod;
|
|
if (hour >= 6 && hour < 12) {
|
|
timePeriod = '上午';
|
|
} else if (hour >= 12 && hour < 18) {
|
|
timePeriod = '下午';
|
|
} else {
|
|
timePeriod = '晚上';
|
|
}
|
|
|
|
// 简单判断是否节假日(这里只是示例,实际需要更复杂的逻辑)
|
|
// 这里假设周末为节假日
|
|
const dayOfWeek = now.getDay();
|
|
const holiday = dayOfWeek === 0 || dayOfWeek === 6;
|
|
|
|
// 天气情况(这里只是示例,实际需要调用天气API)
|
|
// 这里使用默认值晴朗
|
|
const weather = '晴朗';
|
|
|
|
return { weather, holiday, timePeriod };
|
|
},
|
|
|
|
// 生成详细文本
|
|
generateDetailText: function(result, transportMode, weight, volume, packagingType, waitTime) {
|
|
let text = `费用明细:\n`;
|
|
text += ` 最低运费: ${result.feeMin}元\n`;
|
|
text += ` 最高运费: ${result.feeMax}元\n`;
|
|
text += ` 平均运费: ${result.feeAvg}元\n\n`;
|
|
|
|
if (result.details.breakdown) {
|
|
text += `费用组成:\n`;
|
|
for (const [key, value] of Object.entries(result.details.breakdown)) {
|
|
text += ` ${key}: ${value}元\n`;
|
|
}
|
|
text += `\n`;
|
|
}
|
|
|
|
text += `运输详情:\n`;
|
|
text += ` 运输模式: ${transportMode}\n`;
|
|
text += ` 车型: ${result.details.vehicleType}\n`;
|
|
text += ` 包装类型: ${packagingType === 'standard' ? '标准包装' : '加强包装'}\n`;
|
|
text += ` 实际重量: ${weight}公斤\n`;
|
|
text += ` 体积: ${volume}立方米\n`;
|
|
if (result.details.volumeWeight) {
|
|
text += ` 体积重量: ${result.details.volumeWeight}公斤\n`;
|
|
}
|
|
if (result.details.chargeWeight) {
|
|
text += ` 计费重量: ${result.details.chargeWeight}公斤\n`;
|
|
}
|
|
text += ` 预计等候时间: ${waitTime}分钟\n`;
|
|
if (result.details.estimatedPackages) {
|
|
text += ` 估算包装数量: ${result.details.estimatedPackages}个\n`;
|
|
}
|
|
// 添加运输距离信息
|
|
text += ` 运输距离: ${result.distance || 0}公里\n`;
|
|
text += `\n`;
|
|
|
|
if (result.details.marketFactor) {
|
|
text += `市场因素:\n`;
|
|
text += ` 天气状况: ${result.details.weather}\n`;
|
|
text += ` 是否节假日: ${result.details.holiday}\n`;
|
|
text += ` 时间段: ${result.details.timePeriod}\n`;
|
|
text += ` 市场因素系数: ${result.details.marketFactor}\n\n`;
|
|
}
|
|
|
|
text += `市场参考:\n`;
|
|
text += ` 市场参考价: ${result.marketPrice}元\n`;
|
|
text += ` 价格合理性: ${result.priceReasonability}\n\n`;
|
|
|
|
if (!result.compliance.isCompliant) {
|
|
text += `不合规原因:\n`;
|
|
for (const issue of result.compliance.issues) {
|
|
text += ` - ${issue}\n`;
|
|
}
|
|
}
|
|
|
|
return text;
|
|
},
|
|
|
|
// 计算鸡蛋运输运费
|
|
calculateEggShippingFee: function(transportMode, distance, weight, volume, packagingType, vehicleType, truckLength, waitTime, weather, holiday, timePeriod) {
|
|
// 计算市场因素系数
|
|
const calculateMarketFactor = (weather, holiday, timePeriod) => {
|
|
let factor = 1.0;
|
|
|
|
// 天气因素
|
|
const weatherFactors = {
|
|
'晴朗': 1.0,
|
|
'多云': 1.05,
|
|
'小雨': 1.1,
|
|
'中雨': 1.2,
|
|
'大雨': 1.3,
|
|
'暴雨': 1.5,
|
|
'雪': 1.4,
|
|
'雾': 1.3
|
|
};
|
|
factor *= weatherFactors[weather] || 1.0;
|
|
|
|
// 节假日因素
|
|
if (holiday) {
|
|
factor *= 1.2;
|
|
}
|
|
|
|
// 时间段因素
|
|
const timeFactors = {
|
|
'上午': 1.0,
|
|
'下午': 1.1,
|
|
'晚上': 1.2
|
|
};
|
|
factor *= timeFactors[timePeriod] || 1.0;
|
|
|
|
return factor;
|
|
};
|
|
|
|
// 计算体积重量(物流行业标准:1立方米 = 333公斤)
|
|
const volumeWeight = volume * 333;
|
|
// 计费重量取实际重量和体积重量的较大值
|
|
const chargeWeight = Math.max(weight, volumeWeight);
|
|
|
|
// 计算市场因素系数
|
|
const marketFactor = calculateMarketFactor(weather, holiday, timePeriod);
|
|
|
|
// 车长系数计算
|
|
const getTruckLengthFactor = (truckLength) => {
|
|
try {
|
|
const length = parseFloat(truckLength.replace('米', ''));
|
|
if (length <= 3.8) {
|
|
return 0.8;
|
|
} else if (length <= 4.2) {
|
|
return 0.9;
|
|
} else if (length <= 5) {
|
|
return 1.0;
|
|
} else if (length <= 6.8) {
|
|
return 1.1;
|
|
} else if (length <= 7.7) {
|
|
return 1.2;
|
|
} else if (length <= 9.6) {
|
|
return 1.3;
|
|
} else if (length <= 13) {
|
|
return 1.4;
|
|
} else {
|
|
return 1.5;
|
|
}
|
|
} catch {
|
|
return 1.0;
|
|
}
|
|
};
|
|
|
|
// 获取车长系数
|
|
const truckLengthFactor = getTruckLengthFactor(truckLength);
|
|
|
|
// 合规性检查参数
|
|
const complianceParams = {
|
|
maxWeightPerVehicle: {
|
|
'面包车': 500,
|
|
'依维柯': 1500,
|
|
'平板': 15000,
|
|
'厢式': 10000,
|
|
'高栏车': 15000,
|
|
'集装箱': 20000,
|
|
'自卸': 12000,
|
|
'冷藏': 8000,
|
|
'保温': 6000,
|
|
'棉被车': 8000,
|
|
'爬梯车': 12000,
|
|
'飞翼车': 10000,
|
|
'高低板': 20000
|
|
},
|
|
maxVolumePerVehicle: {
|
|
'面包车': 5,
|
|
'依维柯': 12,
|
|
'平板': 60,
|
|
'厢式': 45,
|
|
'高栏车': 60,
|
|
'集装箱': 80,
|
|
'自卸': 45,
|
|
'冷藏': 40,
|
|
'保温': 30,
|
|
'棉被车': 40,
|
|
'爬梯车': 45,
|
|
'飞翼车': 45,
|
|
'高低板': 80
|
|
},
|
|
weightPerPackage: 20,
|
|
volumePerPackage: 0.2,
|
|
minDistance: 1,
|
|
maxDistance: 2000
|
|
};
|
|
|
|
// 合规性检查
|
|
const compliance = {
|
|
isCompliant: true,
|
|
issues: []
|
|
};
|
|
|
|
// 检查距离
|
|
if (distance < complianceParams.minDistance) {
|
|
compliance.isCompliant = false;
|
|
compliance.issues.push(`运输距离必须大于等于${complianceParams.minDistance}公里`);
|
|
}
|
|
if (distance > complianceParams.maxDistance) {
|
|
compliance.isCompliant = false;
|
|
compliance.issues.push(`运输距离不能超过${complianceParams.maxDistance}公里`);
|
|
}
|
|
|
|
// 检查车型载重
|
|
if (complianceParams.maxWeightPerVehicle[vehicleType]) {
|
|
// 对于轻泡货物,使用实际重量进行合规性检查,而不是体积重量
|
|
if (weight > complianceParams.maxWeightPerVehicle[vehicleType]) {
|
|
compliance.isCompliant = false;
|
|
compliance.issues.push(`${vehicleType}载重不能超过${complianceParams.maxWeightPerVehicle[vehicleType]}公斤`);
|
|
}
|
|
}
|
|
|
|
// 检查车型体积
|
|
if (complianceParams.maxVolumePerVehicle[vehicleType]) {
|
|
if (volume > complianceParams.maxVolumePerVehicle[vehicleType]) {
|
|
compliance.isCompliant = false;
|
|
compliance.issues.push(`${vehicleType}体积不能超过${complianceParams.maxVolumePerVehicle[vehicleType]}立方米`);
|
|
}
|
|
}
|
|
|
|
// 检查包装要求
|
|
const estimatedPackages = Math.max(weight / complianceParams.weightPerPackage, volume / complianceParams.volumePerPackage);
|
|
if (estimatedPackages > 300) {
|
|
compliance.isCompliant = false;
|
|
compliance.issues.push('包装数量较多,建议考虑多车运输');
|
|
}
|
|
|
|
// 检查是否合规
|
|
const isCompliant = compliance.isCompliant;
|
|
|
|
// 计算运费区间
|
|
let feeMin, feeMax, breakdown;
|
|
if (transportMode === '货拉拉') {
|
|
// 货拉拉收费标准
|
|
[feeMin, feeMax, breakdown] = this.calculateHuolalaFee(distance, vehicleType, truckLength, waitTime, packagingType);
|
|
} else if (transportMode === '整车运输') {
|
|
// 整车运输收费标准
|
|
[feeMin, feeMax, breakdown] = this.calculateYunmanmanFee(distance, weight, volume, vehicleType, truckLength);
|
|
} else {
|
|
// 零担拼车收费标准
|
|
[feeMin, feeMax, breakdown] = this.calculateLessThanTruckloadFee(distance, chargeWeight, packagingType, truckLength);
|
|
}
|
|
|
|
// 如果不合规(货物超过车型限制),调整运费
|
|
if (!isCompliant) {
|
|
// 检查是否是载重或体积超过限制
|
|
const weightExceeded = weight > (complianceParams.maxWeightPerVehicle[vehicleType] || Infinity);
|
|
const volumeExceeded = volume > (complianceParams.maxVolumePerVehicle[vehicleType] || Infinity);
|
|
|
|
if (weightExceeded || volumeExceeded) {
|
|
// 取重量和体积超出比例的最大值
|
|
let maxExceedRatio = 1.0;
|
|
|
|
if (weightExceeded) {
|
|
const maxWeight = complianceParams.maxWeightPerVehicle[vehicleType] || 1;
|
|
const weightRatio = weight / maxWeight;
|
|
maxExceedRatio = Math.max(maxExceedRatio, weightRatio);
|
|
}
|
|
|
|
if (volumeExceeded) {
|
|
const maxVolume = complianceParams.maxVolumePerVehicle[vehicleType] || 1;
|
|
const volumeRatio = volume / maxVolume;
|
|
maxExceedRatio = Math.max(maxExceedRatio, volumeRatio);
|
|
}
|
|
|
|
// 根据最大超出比例计算调整系数
|
|
let exceedFactor;
|
|
if (maxExceedRatio > 5) {
|
|
exceedFactor = 2.0;
|
|
} else if (maxExceedRatio > 3) {
|
|
exceedFactor = 1.8;
|
|
} else if (maxExceedRatio > 2) {
|
|
exceedFactor = 1.6;
|
|
} else {
|
|
exceedFactor = 1.4;
|
|
}
|
|
|
|
// 应用超出系数
|
|
feeMin *= exceedFactor;
|
|
feeMax *= exceedFactor;
|
|
}
|
|
}
|
|
|
|
// 应用市场因素系数
|
|
feeMin *= marketFactor;
|
|
feeMax *= marketFactor;
|
|
|
|
// 计算平均运费
|
|
const feeAvg = (feeMin + feeMax) / 2;
|
|
|
|
// 计算市场参考价
|
|
let marketPrice = this.calculateMarketPrice(transportMode, distance, weight, volume, vehicleType, truckLength);
|
|
|
|
// 如果不合规(货物超过车型限制),调整市场参考价
|
|
if (!isCompliant) {
|
|
// 检查是否是载重或体积超过限制
|
|
const weightExceeded = weight > (complianceParams.maxWeightPerVehicle[vehicleType] || Infinity);
|
|
const volumeExceeded = volume > (complianceParams.maxVolumePerVehicle[vehicleType] || Infinity);
|
|
|
|
if (weightExceeded || volumeExceeded) {
|
|
// 取重量和体积超出比例的最大值
|
|
let maxExceedRatio = 1.0;
|
|
|
|
if (weightExceeded) {
|
|
const maxWeight = complianceParams.maxWeightPerVehicle[vehicleType] || 1;
|
|
const weightRatio = weight / maxWeight;
|
|
maxExceedRatio = Math.max(maxExceedRatio, weightRatio);
|
|
}
|
|
|
|
if (volumeExceeded) {
|
|
const maxVolume = complianceParams.maxVolumePerVehicle[vehicleType] || 1;
|
|
const volumeRatio = volume / maxVolume;
|
|
maxExceedRatio = Math.max(maxExceedRatio, volumeRatio);
|
|
}
|
|
|
|
// 根据最大超出比例计算调整系数
|
|
let exceedFactor;
|
|
if (maxExceedRatio > 5) {
|
|
exceedFactor = 2.0;
|
|
} else if (maxExceedRatio > 3) {
|
|
exceedFactor = 1.8;
|
|
} else if (maxExceedRatio > 2) {
|
|
exceedFactor = 1.6;
|
|
} else {
|
|
exceedFactor = 1.4;
|
|
}
|
|
|
|
// 应用超出系数
|
|
marketPrice *= exceedFactor;
|
|
}
|
|
}
|
|
|
|
// 应用市场因素系数
|
|
marketPrice *= marketFactor;
|
|
|
|
// 评估价格合理性
|
|
let priceReasonability;
|
|
if (feeAvg < marketPrice * 0.8) {
|
|
priceReasonability = '价格偏低,可能存在服务质量风险';
|
|
} else if (feeAvg > marketPrice * 1.2) {
|
|
priceReasonability = '价格偏高,建议重新议价';
|
|
} else {
|
|
priceReasonability = '价格合理,符合市场行情';
|
|
}
|
|
|
|
// 详细信息
|
|
const details = {
|
|
breakdown: breakdown,
|
|
volumeWeight: Math.round(volumeWeight * 100) / 100,
|
|
chargeWeight: Math.round(chargeWeight * 100) / 100,
|
|
estimatedPackages: Math.round(estimatedPackages),
|
|
vehicleType: vehicleType,
|
|
packagingType: packagingType,
|
|
marketFactor: Math.round(marketFactor * 100) / 100,
|
|
weather: weather,
|
|
holiday: holiday ? '是' : '否',
|
|
timePeriod: timePeriod
|
|
};
|
|
|
|
return {
|
|
feeMin: Math.round(feeMin * 100) / 100,
|
|
feeMax: Math.round(feeMax * 100) / 100,
|
|
feeAvg: Math.round(feeAvg * 100) / 100,
|
|
marketPrice: Math.round(marketPrice * 100) / 100,
|
|
priceReasonability: priceReasonability,
|
|
compliance: compliance,
|
|
details: details,
|
|
distance: distance // 添加距离信息
|
|
};
|
|
},
|
|
|
|
// 货拉拉运费计算
|
|
calculateHuolalaFee: function(distance, vehicleType, truckLength, waitTime, packagingType) {
|
|
// 车长系数计算
|
|
const getTruckLengthFactor = (truckLength) => {
|
|
try {
|
|
const length = parseFloat(truckLength.replace('米', ''));
|
|
if (length <= 3.8) {
|
|
return 0.8;
|
|
} else if (length <= 4.2) {
|
|
return 0.9;
|
|
} else if (length <= 5) {
|
|
return 1.0;
|
|
} else if (length <= 6.8) {
|
|
return 1.1;
|
|
} else if (length <= 7.7) {
|
|
return 1.2;
|
|
} else if (length <= 9.6) {
|
|
return 1.3;
|
|
} else if (length <= 13) {
|
|
return 1.4;
|
|
} else {
|
|
return 1.5;
|
|
}
|
|
} catch {
|
|
return 1.0;
|
|
}
|
|
};
|
|
|
|
// 货拉拉收费标准
|
|
const rates = {
|
|
'面包车': {
|
|
startFee: 30,
|
|
startDistance: 5,
|
|
unitPrice: 2.8,
|
|
extraStart: 6,
|
|
freeWaitTime: 40,
|
|
waitFee: 5,
|
|
waitInterval: 10
|
|
},
|
|
'依维柯': {
|
|
startFee: 65,
|
|
startDistance: 5,
|
|
unitPrice: 4,
|
|
extraStart: 6,
|
|
freeWaitTime: 40,
|
|
waitFee: 5,
|
|
waitInterval: 10
|
|
},
|
|
'平板': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPrice: 4.8,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 20,
|
|
waitInterval: 15
|
|
},
|
|
'厢式': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPrice: 5.0,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 20,
|
|
waitInterval: 15
|
|
},
|
|
'高栏车': {
|
|
startFee: 180,
|
|
startDistance: 10,
|
|
unitPrice: 4.5,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 25,
|
|
waitInterval: 15
|
|
},
|
|
'冷藏': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPrice: 6.0,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 30,
|
|
waitInterval: 15
|
|
},
|
|
'保温': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPrice: 5.5,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 25,
|
|
waitInterval: 15
|
|
},
|
|
'棉被车': {
|
|
startFee: 230,
|
|
startDistance: 10,
|
|
unitPrice: 5.2,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 22,
|
|
waitInterval: 15
|
|
},
|
|
'飞翼车': {
|
|
startFee: 240,
|
|
startDistance: 10,
|
|
unitPrice: 5.3,
|
|
extraStart: 11,
|
|
freeWaitTime: 60,
|
|
waitFee: 23,
|
|
waitInterval: 15
|
|
}
|
|
};
|
|
|
|
const rate = rates[vehicleType] || rates['面包车'];
|
|
|
|
// 获取车长系数
|
|
const truckLengthFactor = getTruckLengthFactor(truckLength);
|
|
|
|
// 计算距离费用
|
|
let distanceFee;
|
|
if (distance <= rate.startDistance) {
|
|
distanceFee = rate.startFee;
|
|
} else {
|
|
const extraDistance = distance - rate.startDistance;
|
|
distanceFee = rate.startFee + extraDistance * rate.unitPrice;
|
|
}
|
|
|
|
// 计算等候费用
|
|
let waitFee;
|
|
if (waitTime > rate.freeWaitTime) {
|
|
const extraWaitTime = waitTime - rate.freeWaitTime;
|
|
waitFee = Math.ceil(extraWaitTime / rate.waitInterval) * rate.waitFee;
|
|
} else {
|
|
waitFee = 0;
|
|
}
|
|
|
|
// 应用车长系数
|
|
distanceFee *= truckLengthFactor;
|
|
waitFee *= truckLengthFactor;
|
|
|
|
// 计算包装费用
|
|
const packagingFee = packagingType === 'standard' ? 1 : 2;
|
|
|
|
// 计算最低和最高运费(考虑价格波动10%)
|
|
const baseFee = distanceFee + waitFee;
|
|
const feeMin = baseFee * 0.9;
|
|
const feeMax = baseFee * 1.1;
|
|
|
|
// 费用明细
|
|
const breakdown = {
|
|
'起步费': Math.round(rate.startFee * truckLengthFactor * 100) / 100,
|
|
'距离费': Math.round((distanceFee - rate.startFee * truckLengthFactor) * 100) / 100,
|
|
'等候费': Math.round(waitFee * 100) / 100,
|
|
'包装费': packagingFee
|
|
};
|
|
|
|
return [feeMin, feeMax, breakdown];
|
|
},
|
|
|
|
// 整车运输运费计算
|
|
calculateYunmanmanFee: function(distance, weight, volume, vehicleType, truckLength) {
|
|
// 车长系数计算
|
|
const getTruckLengthFactor = (truckLength) => {
|
|
try {
|
|
const length = parseFloat(truckLength.replace('米', ''));
|
|
if (length <= 3.8) {
|
|
return 0.8;
|
|
} else if (length <= 4.2) {
|
|
return 0.9;
|
|
} else if (length <= 5) {
|
|
return 1.0;
|
|
} else if (length <= 6.8) {
|
|
return 1.1;
|
|
} else if (length <= 7.7) {
|
|
return 1.2;
|
|
} else if (length <= 9.6) {
|
|
return 1.3;
|
|
} else if (length <= 13) {
|
|
return 1.4;
|
|
} else {
|
|
return 1.5;
|
|
}
|
|
} catch {
|
|
return 1.0;
|
|
}
|
|
};
|
|
|
|
// 整车运输收费标准
|
|
const rates = {
|
|
'面包车': {
|
|
startFee: 80,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.0,
|
|
unitPriceMax: 1.3
|
|
},
|
|
'依维柯': {
|
|
startFee: 160,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.5,
|
|
unitPriceMax: 1.8
|
|
},
|
|
'平板': {
|
|
startFee: 300,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.5,
|
|
unitPriceMax: 2.9
|
|
},
|
|
'厢式': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.1,
|
|
unitPriceMax: 2.4
|
|
},
|
|
'高栏车': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.5,
|
|
unitPriceMax: 2.8
|
|
},
|
|
'集装箱': {
|
|
startFee: 300,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.8,
|
|
unitPriceMax: 3.1
|
|
},
|
|
'自卸': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.2,
|
|
unitPriceMax: 2.5
|
|
},
|
|
'冷藏': {
|
|
startFee: 300,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.5,
|
|
unitPriceMax: 2.8
|
|
},
|
|
'保温': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.3,
|
|
unitPriceMax: 2.6
|
|
},
|
|
'棉被车': {
|
|
startFee: 260,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.2,
|
|
unitPriceMax: 2.5
|
|
},
|
|
'爬梯车': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.3,
|
|
unitPriceMax: 2.6
|
|
},
|
|
'飞翼车': {
|
|
startFee: 270,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.2,
|
|
unitPriceMax: 2.5
|
|
},
|
|
'高低板': {
|
|
startFee: 350,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.8,
|
|
unitPriceMax: 3.1
|
|
}
|
|
};
|
|
|
|
const rate = rates[vehicleType] || rates['面包车'];
|
|
|
|
// 获取车长系数
|
|
const truckLengthFactor = getTruckLengthFactor(truckLength);
|
|
|
|
// 计算距离费用
|
|
let feeMin, feeMax;
|
|
if (distance <= rate.startDistance) {
|
|
feeMin = rate.startFee;
|
|
feeMax = rate.startFee;
|
|
} else {
|
|
const extraDistance = distance - rate.startDistance;
|
|
feeMin = rate.startFee + extraDistance * rate.unitPriceMin;
|
|
feeMax = rate.startFee + extraDistance * rate.unitPriceMax;
|
|
}
|
|
|
|
// 应用车长系数
|
|
feeMin *= truckLengthFactor;
|
|
feeMax *= truckLengthFactor;
|
|
|
|
// 根据货物重量和体积调整价格
|
|
if (weight > 1000 || volume > 10) {
|
|
feeMin *= 1.1;
|
|
feeMax *= 1.1;
|
|
}
|
|
|
|
// 添加平台佣金(运费的5%左右)
|
|
feeMin *= 1.05;
|
|
feeMax *= 1.05;
|
|
|
|
// 费用明细
|
|
const breakdown = {
|
|
'起步费': rate.startFee,
|
|
'距离费': Math.round((feeMin / 1.05 - rate.startFee) * 100) / 100,
|
|
'平台佣金': Math.round((feeMin - feeMin / 1.05) * 100) / 100
|
|
};
|
|
|
|
return [feeMin, feeMax, breakdown];
|
|
},
|
|
|
|
// 零担拼车运费计算
|
|
calculateLessThanTruckloadFee: function(distance, chargeWeight, packagingType, truckLength) {
|
|
// 车长系数计算
|
|
const getTruckLengthFactor = (truckLength) => {
|
|
try {
|
|
const length = parseFloat(truckLength.replace('米', ''));
|
|
if (length <= 3.8) {
|
|
return 0.9;
|
|
} else if (length <= 4.2) {
|
|
return 0.95;
|
|
} else if (length <= 5) {
|
|
return 1.0;
|
|
} else if (length <= 6.8) {
|
|
return 1.05;
|
|
} else if (length <= 7.7) {
|
|
return 1.1;
|
|
} else if (length <= 9.6) {
|
|
return 1.15;
|
|
} else if (length <= 13) {
|
|
return 1.2;
|
|
} else {
|
|
return 1.25;
|
|
}
|
|
} catch {
|
|
return 1.0;
|
|
}
|
|
};
|
|
|
|
// 零担拼车收费标准
|
|
const perKgPer1000kmMin = 0.3;
|
|
const perKgPer1000kmMax = 0.5;
|
|
|
|
// 包装费用
|
|
const packagingFeeMin = packagingType === 'standard' ? 0.15 : 0.4;
|
|
const packagingFeeMax = packagingType === 'standard' ? 0.25 : 0.6;
|
|
|
|
// 易碎品加价
|
|
const fragileFactorMin = 1.02;
|
|
const fragileFactorMax = 1.08;
|
|
|
|
// 计算距离因子
|
|
const distanceFactor = distance / 1000;
|
|
|
|
// 获取车长系数
|
|
const truckLengthFactor = getTruckLengthFactor(truckLength);
|
|
|
|
// 计算运费
|
|
let freightFeeMin = chargeWeight * perKgPer1000kmMin * distanceFactor;
|
|
let freightFeeMax = chargeWeight * perKgPer1000kmMax * distanceFactor;
|
|
|
|
const packagingFeeTotalMin = chargeWeight * packagingFeeMin;
|
|
const packagingFeeTotalMax = chargeWeight * packagingFeeMax;
|
|
|
|
// 计算易碎品附加费
|
|
const fragileFeeMin = (freightFeeMin + packagingFeeTotalMin) * (fragileFactorMin - 1);
|
|
const fragileFeeMax = (freightFeeMax + packagingFeeTotalMax) * (fragileFactorMax - 1);
|
|
|
|
// 总运费
|
|
let feeMin = freightFeeMin + packagingFeeTotalMin + fragileFeeMin;
|
|
let feeMax = freightFeeMax + packagingFeeTotalMax + fragileFeeMax;
|
|
|
|
// 应用车长系数
|
|
feeMin *= truckLengthFactor;
|
|
feeMax *= truckLengthFactor;
|
|
|
|
// 费用明细
|
|
const breakdown = {
|
|
'运费': Math.round(freightFeeMin * 100) / 100,
|
|
'包装费': Math.round(packagingFeeTotalMin * 100) / 100,
|
|
'易碎品附加费': Math.round(fragileFeeMin * 100) / 100
|
|
};
|
|
|
|
return [feeMin, feeMax, breakdown];
|
|
},
|
|
|
|
// 计算市场参考价
|
|
calculateMarketPrice: function(transportMode, distance, weight, volume, vehicleType, truckLength) {
|
|
// 车长系数计算
|
|
const getTruckLengthFactor = (truckLength) => {
|
|
try {
|
|
const length = parseFloat(truckLength.replace('米', ''));
|
|
if (length <= 3.8) {
|
|
return 0.8;
|
|
} else if (length <= 4.2) {
|
|
return 0.9;
|
|
} else if (length <= 5) {
|
|
return 1.0;
|
|
} else if (length <= 6.8) {
|
|
return 1.1;
|
|
} else if (length <= 7.7) {
|
|
return 1.2;
|
|
} else if (length <= 9.6) {
|
|
return 1.3;
|
|
} else if (length <= 13) {
|
|
return 1.4;
|
|
} else {
|
|
return 1.5;
|
|
}
|
|
} catch {
|
|
return 1.0;
|
|
}
|
|
};
|
|
|
|
// 获取车长系数
|
|
const truckLengthFactor = getTruckLengthFactor(truckLength);
|
|
|
|
let marketPrice;
|
|
if (transportMode === '货拉拉') {
|
|
// 基于货拉拉真实收费标准
|
|
const rates = {
|
|
'面包车': 30 + Math.max(0, distance - 5) * 2.8,
|
|
'依维柯': 65 + Math.max(0, distance - 5) * 4,
|
|
'平板': 200 + Math.max(0, distance - 10) * 4.8,
|
|
'厢式': 220 + Math.max(0, distance - 10) * 5.0,
|
|
'高栏车': 180 + Math.max(0, distance - 10) * 4.5,
|
|
'冷藏': 280 + Math.max(0, distance - 10) * 6.0,
|
|
'保温': 250 + Math.max(0, distance - 10) * 5.5,
|
|
'棉被车': 230 + Math.max(0, distance - 10) * 5.2,
|
|
'飞翼车': 240 + Math.max(0, distance - 10) * 5.3
|
|
};
|
|
marketPrice = rates[vehicleType] || rates['面包车'];
|
|
// 应用车长系数
|
|
marketPrice *= truckLengthFactor;
|
|
} else if (transportMode === '整车运输') {
|
|
// 基于整车运输真实收费标准
|
|
const rates = {
|
|
'面包车': {
|
|
startFee: 80,
|
|
startDistance: 10,
|
|
unitPrice: 1.15
|
|
},
|
|
'依维柯': {
|
|
startFee: 160,
|
|
startDistance: 10,
|
|
unitPrice: 1.65
|
|
},
|
|
'平板': {
|
|
startFee: 300,
|
|
startDistance: 10,
|
|
unitPrice: 2.7
|
|
},
|
|
'厢式': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPrice: 2.25
|
|
},
|
|
'高栏车': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPrice: 2.65
|
|
},
|
|
'集装箱': {
|
|
startFee: 300,
|
|
startDistance: 10,
|
|
unitPrice: 2.95
|
|
},
|
|
'自卸': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPrice: 2.35
|
|
},
|
|
'冷藏': {
|
|
startFee: 300,
|
|
startDistance: 10,
|
|
unitPrice: 2.65
|
|
},
|
|
'保温': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPrice: 2.45
|
|
},
|
|
'棉被车': {
|
|
startFee: 260,
|
|
startDistance: 10,
|
|
unitPrice: 2.35
|
|
},
|
|
'爬梯车': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPrice: 2.45
|
|
},
|
|
'飞翼车': {
|
|
startFee: 270,
|
|
startDistance: 10,
|
|
unitPrice: 2.35
|
|
},
|
|
'高低板': {
|
|
startFee: 350,
|
|
startDistance: 10,
|
|
unitPrice: 2.95
|
|
}
|
|
};
|
|
|
|
const rate = rates[vehicleType] || rates['面包车'];
|
|
|
|
if (distance <= rate.startDistance) {
|
|
marketPrice = rate.startFee;
|
|
} else {
|
|
const extraDistance = distance - rate.startDistance;
|
|
marketPrice = rate.startFee + extraDistance * rate.unitPrice;
|
|
}
|
|
|
|
// 根据货物重量和体积调整价格
|
|
if (weight > 1000 || volume > 10) {
|
|
marketPrice *= 1.1;
|
|
}
|
|
|
|
// 添加平台佣金
|
|
marketPrice *= 1.05;
|
|
|
|
// 应用车长系数
|
|
marketPrice *= truckLengthFactor;
|
|
} else {
|
|
// 零担拼车市场参考价
|
|
const chargeWeight = Math.max(weight, volume * 333);
|
|
marketPrice = chargeWeight * 0.4 * (distance / 1000) * 1.05; // 包含易碎品附加费
|
|
|
|
// 应用车长系数
|
|
marketPrice *= truckLengthFactor;
|
|
}
|
|
|
|
// 确保最低价格
|
|
const minPrice = 100; // 最低起步价
|
|
marketPrice = Math.max(minPrice, marketPrice);
|
|
|
|
return marketPrice;
|
|
}
|
|
|
|
});
|