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.
 
 

2273 lines
70 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: '15528115321',
description: '拥有5年专业物流管理经验,擅长整车运输和零担拼车方案设计,对鸡蛋的运输要求有深入了解,能够为客户提供高效、安全的物流解决方案。'
},
{
id: 2,
name: '刘敏',
position: '物流经理',
experience: 5,
phone: '19123901316',
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;
}
this.setData({
selectedGoods: goodsInfo,
'goodsInfo.weight': weight,
'goodsInfo.quantity': 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;
}
}
let weight = goodsData.grossWeight || '';
if (!weight && goodsData.netWeight && goodsData.quantity) {
// 计算总重量:净重(斤) * 件数 / 2(换算为公斤)
weight = (parseFloat(goodsData.netWeight) * parseInt(goodsData.quantity)) / 2;
}
this.setData({
selectedGoods: goodsData,
'goodsInfo.weight': weight,
'goodsInfo.quantity': goodsData.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
});
} else if (goodsItem.netWeight && goodsItem.quantity) {
// 计算总重量:净重(斤) * 件数 / 2(换算为公斤)
const totalWeight = (parseFloat(goodsItem.netWeight) * parseInt(goodsItem.quantity)) / 2;
this.setData({
'goodsInfo.weight': totalWeight
});
}
}
},
// 解析地区信息
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.origin.detail || !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 (!originAddress) {
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);
});
});
},
// 切换费用明细显示/隐藏
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.origin.city || !this.data.destination.province || !this.data.destination.city) {
wx.showToast({ title: '请完整填写出发地和目的地信息', icon: 'none' });
return;
}
// 检查详细地址
if (!this.data.origin.detail || !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,
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: 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 };
},
// 生成详细文本
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;
}
});