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.
2371 lines
74 KiB
2371 lines
74 KiB
// pages/freight-calculator/index.js
|
|
const API = require('../../utils/api.js');
|
|
|
|
Page({
|
|
data: {
|
|
// 出发地信息
|
|
origin: {
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: ''
|
|
},
|
|
// 目的地信息
|
|
destination: {
|
|
province: '',
|
|
city: '',
|
|
district: '',
|
|
detail: ''
|
|
},
|
|
// 是否从货源详情页进入(携带产品ID)
|
|
isFromGoodsDetail: false,
|
|
// 货物信息
|
|
goodsInfo: {
|
|
weight: '', // 重量(kg)
|
|
volume: '', // 体积(m³)
|
|
quantity: 1, // 数量
|
|
type: 'egg' // 货物类型,默认为鸡蛋
|
|
},
|
|
// 物流人员信息
|
|
logisticsPersonnel: [
|
|
{
|
|
id: 1,
|
|
name: '张顺玟',
|
|
position: '物流经理',
|
|
experience: 5,
|
|
phone: '19123901316',
|
|
description: '拥有5年专业物流管理经验,擅长整车运输和零担拼车方案设计,对鸡蛋的运输要求有深入了解,能够为客户提供高效、安全的物流解决方案。'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: '刘敏',
|
|
position: '物流经理',
|
|
experience: 5,
|
|
phone: '15528115321',
|
|
description: '5年物流行业经验,专注于配送路线优化,熟悉各种车型的载重和体积限制,能够根据货物特性制定最适合的运输方案,确保货物安全准时送达。'
|
|
}
|
|
],
|
|
// 选中的货源信息
|
|
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 || options.goodsInfo || options.goodsData) {
|
|
this.setData({ isFromGoodsDetail: true });
|
|
|
|
if (options.goodsId) {
|
|
this.loadGoodsInfo(options.goodsId);
|
|
}
|
|
|
|
// 如果直接传递了货源信息 (goodsInfo)
|
|
if (options.goodsInfo) {
|
|
try {
|
|
const goodsInfo = JSON.parse(options.goodsInfo);
|
|
let weight = goodsInfo.grossWeight || '';
|
|
if (!weight && goodsInfo.netWeight && goodsInfo.quantity) {
|
|
// 计算总重量:净重(斤) * 件数 / 2(换算为公斤)
|
|
weight = (parseFloat(goodsInfo.netWeight) * parseInt(goodsInfo.quantity)) / 2;
|
|
} else if (!weight && (goodsInfo.spec || goodsInfo.specification || goodsInfo.specs) && goodsInfo.quantity) {
|
|
// 从规格中提取重量数字并计算总重量
|
|
weight = this.calculateWeightFromSpec(goodsInfo);
|
|
}
|
|
this.setData({
|
|
selectedGoods: goodsInfo,
|
|
'goodsInfo.weight': weight,
|
|
'goodsInfo.quantity': goodsInfo.quantity || 1,
|
|
weight: weight // 同步到运输参数的重量输入框
|
|
});
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
let weight = goodsData.grossWeight || '';
|
|
if (!weight && goodsData.netWeight && goodsData.quantity) {
|
|
// 计算总重量:净重(斤) * 件数 / 2(换算为公斤)
|
|
weight = (parseFloat(goodsData.netWeight) * parseInt(goodsData.quantity)) / 2;
|
|
} else if (!weight && (goodsData.spec || goodsData.specification || goodsData.specs) && goodsData.quantity) {
|
|
// 从规格中提取重量数字并计算总重量
|
|
weight = this.calculateWeightFromSpec(goodsData);
|
|
}
|
|
|
|
this.setData({
|
|
selectedGoods: goodsData,
|
|
'goodsInfo.weight': weight,
|
|
'goodsInfo.quantity': goodsData.quantity || 1,
|
|
weight: weight // 同步到运输参数的重量输入框
|
|
});
|
|
|
|
// 设置出发地为商品所在地
|
|
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,
|
|
weight: goodsItem.grossWeight // 同步到运输参数的重量输入框
|
|
});
|
|
} else if (goodsItem.netWeight && goodsItem.quantity) {
|
|
// 计算总重量:净重(斤) * 件数 / 2(换算为公斤)
|
|
const totalWeight = (parseFloat(goodsItem.netWeight) * parseInt(goodsItem.quantity)) / 2;
|
|
this.setData({
|
|
'goodsInfo.weight': totalWeight,
|
|
weight: totalWeight // 同步到运输参数的重量输入框
|
|
});
|
|
} else if ((goodsItem.spec || goodsItem.specification || goodsItem.specs) && goodsItem.quantity) {
|
|
// 从规格中提取重量数字并计算总重量
|
|
const totalWeight = this.calculateWeightFromSpec(goodsItem);
|
|
this.setData({
|
|
'goodsInfo.weight': totalWeight,
|
|
weight: totalWeight // 同步到运输参数的重量输入框
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// 从规格中提取重量数字并计算总重量
|
|
calculateWeightFromSpec: function (goodsData) {
|
|
// 获取规格信息
|
|
const spec = goodsData.spec || goodsData.specification || goodsData.specs || '';
|
|
const quantity = parseInt(goodsData.quantity) || 1;
|
|
|
|
console.log('从规格计算重量:', { spec, quantity });
|
|
|
|
// 从规格字符串中提取数字
|
|
const weightMatch = spec.match(/(\d+(?:\.\d+)?)/);
|
|
if (weightMatch) {
|
|
const weightPerUnit = parseFloat(weightMatch[1]);
|
|
// 计算总重量:重量(斤) * 件数 / 2(换算为公斤)
|
|
const totalWeight = (weightPerUnit * quantity) / 2;
|
|
console.log('计算得到的总重量:', totalWeight, '公斤');
|
|
return totalWeight;
|
|
}
|
|
|
|
console.log('无法从规格中提取重量');
|
|
return '';
|
|
},
|
|
|
|
// 解析地区信息
|
|
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.origin.city || !this.data.destination.province || !this.data.destination.city) {
|
|
wx.showToast({
|
|
title: '请完整填写出发地和目的地信息',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 检查详细地址 - 目的地需要详细地址,出发地可以不需要
|
|
if (!this.data.destination.detail) {
|
|
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, rejectOrigin) => {
|
|
// 如果出发地已经有经纬度,直接返回
|
|
if (origin.latitude && origin.longitude) {
|
|
resolveOrigin(origin);
|
|
return;
|
|
}
|
|
|
|
// 构建出发地完整地址 - 即使没有详细地址,也可以使用省市区信息
|
|
const originAddress = `${origin.province}${origin.city}${origin.district}${origin.detail || ''}`;
|
|
if (!origin.province || !origin.city || !origin.district) {
|
|
rejectOrigin(new Error('出发地省市区信息不能为空,请输入地址或使用手动选择位置'));
|
|
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);
|
|
rejectOrigin(new Error('出发地地址无效,请检查输入或使用手动选择位置'));
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
console.error('出发地地址转换失败:', err);
|
|
rejectOrigin(new Error('出发地地址转换失败,请稍后重试'));
|
|
}
|
|
});
|
|
});
|
|
|
|
// 转换目的地地址
|
|
const convertDestination = new Promise((resolveDestination, rejectDestination) => {
|
|
// 如果目的地已经有经纬度,直接返回
|
|
if (destination.latitude && destination.longitude) {
|
|
resolveDestination(destination);
|
|
return;
|
|
}
|
|
|
|
// 构建目的地完整地址
|
|
const destinationAddress = `${destination.province}${destination.city}${destination.district}${destination.detail}`;
|
|
if (!destinationAddress) {
|
|
rejectDestination(new Error('目的地地址不能为空,请输入地址或使用手动选择位置'));
|
|
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);
|
|
rejectDestination(new Error('目的地地址无效,请检查输入或使用手动选择位置'));
|
|
}
|
|
},
|
|
fail: function (err) {
|
|
console.error('目的地地址转换失败:', err);
|
|
rejectDestination(new Error('目的地地址转换失败,请稍后重试'));
|
|
}
|
|
});
|
|
});
|
|
|
|
// 等待两个地址转换完成
|
|
Promise.all([convertOrigin, convertDestination]).then(([originWithCoords, destinationWithCoords]) => {
|
|
// 验证两个地址都有经纬度
|
|
if (!originWithCoords.latitude || !originWithCoords.longitude) {
|
|
reject(new Error('出发地地址无效,请检查输入'));
|
|
return;
|
|
}
|
|
if (!destinationWithCoords.latitude || !destinationWithCoords.longitude) {
|
|
reject(new Error('目的地地址无效,请检查输入'));
|
|
return;
|
|
}
|
|
resolve({ originWithCoords, destinationWithCoords });
|
|
}).catch(err => {
|
|
reject(err);
|
|
});
|
|
});
|
|
},
|
|
|
|
|
|
|
|
// 运输模式选择
|
|
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 weightNum = parseFloat(this.data.weight) || 0;
|
|
const volumeNum = parseFloat(this.data.volume) || 0;
|
|
|
|
if (weightNum > 0 || volumeNum > 0) {
|
|
const [weightRange, volumeRange] = this.data.baseInfo[vehicleType];
|
|
const [minWeight, maxWeight] = weightRange.split('-').map(w => parseFloat(w.replace('kg', '')));
|
|
const [minVolume, maxVolume] = volumeRange.split('-').map(v => parseFloat(v.replace('m³', '')));
|
|
|
|
// 检查重量和体积是否在车型的范围内
|
|
const isWeightSuitable = weightNum === 0 || (weightNum >= minWeight && weightNum <= maxWeight);
|
|
const isVolumeSuitable = volumeNum === 0 || (volumeNum >= minVolume && volumeNum <= maxVolume);
|
|
|
|
if (!isWeightSuitable || !isVolumeSuitable) {
|
|
wx.showToast({
|
|
title: '选择的车型不适合当前重量和体积',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 更新车长选项
|
|
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) {
|
|
const weight = e.detail.value;
|
|
this.setData({ weight: weight });
|
|
// 自动匹配车型和车长
|
|
this.autoMatchVehicle(weight, this.data.volume);
|
|
},
|
|
|
|
bindVolumeInput: function(e) {
|
|
const volume = e.detail.value;
|
|
this.setData({ volume: volume });
|
|
// 自动匹配车型和车长
|
|
this.autoMatchVehicle(this.data.weight, volume);
|
|
},
|
|
|
|
// 根据重量和体积自动匹配车型和车长
|
|
autoMatchVehicle: function(weight, volume) {
|
|
const weightNum = parseFloat(weight) || 0;
|
|
const volumeNum = parseFloat(volume) || 0;
|
|
|
|
if (weightNum <= 0 && volumeNum <= 0) {
|
|
return; // 没有有效输入,不进行匹配
|
|
}
|
|
|
|
// 过滤适合的车型并计算每个车型的最大载重和体积
|
|
const suitableVehicles = [];
|
|
// 使用兼容的方式遍历对象
|
|
for (const vehicleType in this.data.baseInfo) {
|
|
if (this.data.baseInfo.hasOwnProperty(vehicleType)) {
|
|
const [weightRange, volumeRange] = this.data.baseInfo[vehicleType];
|
|
const weightRangeParts = weightRange.split('-');
|
|
const volumeRangeParts = volumeRange.split('-');
|
|
if (weightRangeParts.length === 2 && volumeRangeParts.length === 2) {
|
|
const minWeight = parseFloat(weightRangeParts[0].replace('kg', ''));
|
|
const maxWeight = parseFloat(weightRangeParts[1].replace('kg', ''));
|
|
const minVolume = parseFloat(volumeRangeParts[0].replace('m³', ''));
|
|
const maxVolume = parseFloat(volumeRangeParts[1].replace('m³', ''));
|
|
|
|
// 检查重量和体积是否在车型的范围内
|
|
if ((weightNum === 0 || (weightNum >= minWeight && weightNum <= maxWeight)) &&
|
|
(volumeNum === 0 || (volumeNum >= minVolume && volumeNum <= maxVolume))) {
|
|
suitableVehicles.push({
|
|
type: vehicleType,
|
|
maxWeight: maxWeight,
|
|
maxVolume: maxVolume,
|
|
// 为鸡蛋货物添加优先级:高栏车和平板车优先
|
|
priority: (vehicleType === '高栏车' || vehicleType === '平板') ? 1 : 2
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (suitableVehicles.length > 0) {
|
|
// 按照优先级、最大载重和体积排序
|
|
suitableVehicles.sort((a, b) => {
|
|
// 优先按优先级排序(1 > 2)
|
|
if (a.priority !== b.priority) {
|
|
return a.priority - b.priority;
|
|
}
|
|
// 然后按最大载重排序
|
|
if (a.maxWeight !== b.maxWeight) {
|
|
return a.maxWeight - b.maxWeight;
|
|
}
|
|
// 最后按最大体积排序
|
|
return a.maxVolume - b.maxVolume;
|
|
});
|
|
|
|
// 选择第一个合适的车型
|
|
const selectedVehicle = suitableVehicles[0].type;
|
|
const vehicleIndex = this.data.vehicleTypes.indexOf(selectedVehicle);
|
|
|
|
if (vehicleIndex !== -1) {
|
|
// 更新车型
|
|
this.setData({ vehicleTypeIndex: vehicleIndex });
|
|
|
|
// 更新车长选项
|
|
const truckLengths = this.data.vehicleLengthMap[selectedVehicle];
|
|
if (truckLengths) {
|
|
// 根据重量和体积选择最合适的车长
|
|
let bestTruckLengthIndex = 0;
|
|
// 简单逻辑:如果重量或体积较大,选择较长的车
|
|
if (weightNum > 5000 || volumeNum > 20) {
|
|
// 选择较长的车
|
|
bestTruckLengthIndex = Math.min(truckLengths.length - 1, 2);
|
|
} else if (weightNum > 1000 || volumeNum > 10) {
|
|
// 选择中等长度的车
|
|
bestTruckLengthIndex = Math.min(truckLengths.length - 1, 1);
|
|
}
|
|
|
|
this.setData({
|
|
truckLengths: truckLengths,
|
|
truckLengthIndex: bestTruckLengthIndex
|
|
});
|
|
|
|
// 更新车型信息
|
|
this.updateVehicleInfo();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// 更新车型信息
|
|
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.origin.city || !this.data.destination.province || !this.data.destination.city) {
|
|
wx.showToast({ title: '请完整填写出发地和目的地信息', icon: 'none' });
|
|
return;
|
|
}
|
|
|
|
// 检查详细地址 - 目的地需要详细地址,出发地可以不需要
|
|
if (!this.data.destination.detail) {
|
|
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,
|
|
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: err.message || '地址转换失败,请检查输入',
|
|
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 };
|
|
},
|
|
|
|
|
|
|
|
// 计算鸡蛋运输运费
|
|
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: 200,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.8,
|
|
unitPriceMax: 2.2
|
|
},
|
|
'厢式': {
|
|
startFee: 180,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.7,
|
|
unitPriceMax: 2.0
|
|
},
|
|
'高栏车': {
|
|
startFee: 150,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.8,
|
|
unitPriceMax: 2.2
|
|
},
|
|
'集装箱': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.0,
|
|
unitPriceMax: 2.4
|
|
},
|
|
'自卸': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.9,
|
|
unitPriceMax: 2.3
|
|
},
|
|
'冷藏': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.0,
|
|
unitPriceMax: 2.4
|
|
},
|
|
'保温': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.8,
|
|
unitPriceMax: 2.2
|
|
},
|
|
'棉被车': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.7,
|
|
unitPriceMax: 2.1
|
|
},
|
|
'爬梯车': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.8,
|
|
unitPriceMax: 2.2
|
|
},
|
|
'飞翼车': {
|
|
startFee: 210,
|
|
startDistance: 10,
|
|
unitPriceMin: 1.7,
|
|
unitPriceMax: 2.1
|
|
},
|
|
'高低板': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPriceMin: 2.0,
|
|
unitPriceMax: 2.4
|
|
}
|
|
};
|
|
|
|
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 > 3000 || volume > 15) {
|
|
feeMin *= 1.05;
|
|
feeMax *= 1.05;
|
|
} else if (weight > 1000 || volume > 10) {
|
|
feeMin *= 1.02;
|
|
feeMax *= 1.02;
|
|
}
|
|
|
|
// 添加平台佣金(运费的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 === '整车运输') {
|
|
// 基于整车运输真实收费标准 - 与 calculateYunmanmanFee 使用相同的费率
|
|
const rates = {
|
|
'面包车': {
|
|
startFee: 80,
|
|
startDistance: 10,
|
|
unitPrice: 1.15
|
|
},
|
|
'依维柯': {
|
|
startFee: 160,
|
|
startDistance: 10,
|
|
unitPrice: 1.65
|
|
},
|
|
'平板': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPrice: 2.0
|
|
},
|
|
'厢式': {
|
|
startFee: 180,
|
|
startDistance: 10,
|
|
unitPrice: 1.85
|
|
},
|
|
'高栏车': {
|
|
startFee: 150,
|
|
startDistance: 10,
|
|
unitPrice: 2.0
|
|
},
|
|
'集装箱': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPrice: 2.2
|
|
},
|
|
'自卸': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPrice: 2.1
|
|
},
|
|
'冷藏': {
|
|
startFee: 250,
|
|
startDistance: 10,
|
|
unitPrice: 2.2
|
|
},
|
|
'保温': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPrice: 2.0
|
|
},
|
|
'棉被车': {
|
|
startFee: 200,
|
|
startDistance: 10,
|
|
unitPrice: 1.9
|
|
},
|
|
'爬梯车': {
|
|
startFee: 220,
|
|
startDistance: 10,
|
|
unitPrice: 2.0
|
|
},
|
|
'飞翼车': {
|
|
startFee: 210,
|
|
startDistance: 10,
|
|
unitPrice: 1.9
|
|
},
|
|
'高低板': {
|
|
startFee: 280,
|
|
startDistance: 10,
|
|
unitPrice: 2.2
|
|
}
|
|
};
|
|
|
|
const rate = rates[vehicleType] || rates['面包车'];
|
|
|
|
if (distance <= rate.startDistance) {
|
|
marketPrice = rate.startFee;
|
|
} else {
|
|
const extraDistance = distance - rate.startDistance;
|
|
marketPrice = rate.startFee + extraDistance * rate.unitPrice;
|
|
}
|
|
|
|
// 应用车长系数
|
|
marketPrice *= truckLengthFactor;
|
|
|
|
// 根据货物重量和体积调整价格 - 与 calculateYunmanmanFee 使用相同的系数
|
|
if (weight > 3000 || volume > 15) {
|
|
marketPrice *= 1.05;
|
|
} else if (weight > 1000 || volume > 10) {
|
|
marketPrice *= 1.02;
|
|
}
|
|
|
|
// 添加平台佣金
|
|
marketPrice *= 1.05;
|
|
} 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;
|
|
}
|
|
|
|
});
|