13 changed files with 2505 additions and 11 deletions
@ -0,0 +1,846 @@ |
|||
// pages/freight-calculator/index.js
|
|||
const API = require('../../utils/api.js'); |
|||
|
|||
Page({ |
|||
data: { |
|||
// 出发地信息
|
|||
origin: { |
|||
province: '', |
|||
city: '', |
|||
district: '', |
|||
detail: '' |
|||
}, |
|||
// 目的地信息
|
|||
destination: { |
|||
province: '', |
|||
city: '', |
|||
district: '', |
|||
detail: '' |
|||
}, |
|||
// 货物信息
|
|||
goodsInfo: { |
|||
weight: '', // 重量(kg)
|
|||
volume: '', // 体积(m³)
|
|||
quantity: 1, // 数量
|
|||
type: 'egg' // 货物类型,默认为鸡蛋
|
|||
}, |
|||
// 物流车辆信息
|
|||
vehicleInfo: { |
|||
type: '', // 车辆类型
|
|||
capacity: '', // 载重能力(吨)
|
|||
number: '', // 车辆编号
|
|||
driver: '', // 司机姓名
|
|||
phone: '' // 联系电话
|
|||
}, |
|||
// 物流人员信息
|
|||
logisticsPersonnel: [ |
|||
{ |
|||
id: 1, |
|||
name: '张三', |
|||
position: '物流经理', |
|||
experience: 5, |
|||
phone: '13800138000' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
name: '李四', |
|||
position: '配送专员', |
|||
experience: 3, |
|||
phone: '13900139000' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
name: '王五', |
|||
position: '调度员', |
|||
experience: 2, |
|||
phone: '13700137000' |
|||
} |
|||
], |
|||
// 车辆类型列表
|
|||
vehicleTypes: ['厢式货车', '冷藏车', '平板车', '高栏车', '自卸车'], |
|||
// 选中的车辆类型索引
|
|||
selectedVehicleTypeIndex: 0, |
|||
// 选中的货源信息
|
|||
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] |
|||
} |
|||
}, |
|||
|
|||
onLoad: function (options) { |
|||
// 页面加载时的初始化逻辑
|
|||
console.log('物流运费估算页面加载,options:', options); |
|||
|
|||
// 加载历史记录
|
|||
this.loadHistoryRecords(); |
|||
|
|||
// 初始化地址选择器数据
|
|||
this.initRegionData(); |
|||
|
|||
// 如果从商品详情页跳转过来,获取商品信息
|
|||
if (options.goodsId) { |
|||
this.loadGoodsInfo(options.goodsId); |
|||
} |
|||
|
|||
// 如果直接传递了货源信息 (goodsInfo)
|
|||
if (options.goodsInfo) { |
|||
try { |
|||
const goodsInfo = JSON.parse(options.goodsInfo); |
|||
this.setData({ |
|||
selectedGoods: goodsInfo, |
|||
'goodsInfo.weight': goodsInfo.grossWeight || '', |
|||
'goodsInfo.quantity': 1 |
|||
}); |
|||
} catch (e) { |
|||
console.error('解析货源信息失败:', e); |
|||
} |
|||
} |
|||
|
|||
// 如果直接传递了货源信息 (goodsData)
|
|||
if (options.goodsData) { |
|||
try { |
|||
// 尝试直接解析
|
|||
let goodsData; |
|||
try { |
|||
goodsData = JSON.parse(options.goodsData); |
|||
} catch (e1) { |
|||
// 如果直接解析失败,尝试解码后再解析
|
|||
try { |
|||
const decodedGoodsData = decodeURIComponent(options.goodsData); |
|||
goodsData = JSON.parse(decodedGoodsData); |
|||
} catch (e2) { |
|||
console.error('解析货源信息失败 (已尝试解码):', e2); |
|||
throw e2; |
|||
} |
|||
} |
|||
|
|||
this.setData({ |
|||
selectedGoods: goodsData, |
|||
'goodsInfo.weight': goodsData.grossWeight || '', |
|||
'goodsInfo.quantity': 1 |
|||
}); |
|||
|
|||
// 设置出发地为商品所在地
|
|||
if (goodsData.region) { |
|||
const regionInfo = this.parseRegion(goodsData.region); |
|||
this.setData({ |
|||
'origin.province': regionInfo.province || '', |
|||
'origin.city': regionInfo.city || '', |
|||
'origin.district': regionInfo.district || '' |
|||
}); |
|||
} |
|||
} catch (e) { |
|||
console.error('解析货源信息失败:', e); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 初始化地址选择器数据
|
|||
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) { |
|||
const regionInfo = this.parseRegion(goodsItem.region); |
|||
this.setData({ |
|||
'origin.province': regionInfo.province || '', |
|||
'origin.city': regionInfo.city || '', |
|||
'origin.district': regionInfo.district || '' |
|||
}); |
|||
} |
|||
|
|||
// 设置货物重量(如果有)
|
|||
if (goodsItem.grossWeight) { |
|||
this.setData({ |
|||
'goodsInfo.weight': goodsItem.grossWeight |
|||
}); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 解析地区信息
|
|||
parseRegion: function (region) { |
|||
if (!region) return {}; |
|||
|
|||
// 简单的地区解析逻辑,实际项目中可能需要更复杂的解析
|
|||
const parts = region.split(' '); |
|||
return { |
|||
province: parts[0] || '', |
|||
city: parts[1] || '', |
|||
district: parts[2] || '' |
|||
}; |
|||
}, |
|||
|
|||
// 加载历史记录
|
|||
loadHistoryRecords: function () { |
|||
const history = wx.getStorageSync('freightCalculatorHistory') || []; |
|||
this.setData({ |
|||
historyRecords: history.slice(0, 10) // 只显示最近10条记录
|
|||
}); |
|||
}, |
|||
|
|||
// 保存历史记录
|
|||
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, 10) |
|||
}); |
|||
}, |
|||
|
|||
// 输入出发地
|
|||
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 |
|||
}); |
|||
}, |
|||
|
|||
// 选择车辆类型
|
|||
bindVehicleTypeChange: function (e) { |
|||
const index = e.detail.value; |
|||
this.setData({ |
|||
selectedVehicleTypeIndex: index, |
|||
'vehicleInfo.type': this.data.vehicleTypes[index] |
|||
}); |
|||
}, |
|||
|
|||
// 输入车辆信息
|
|||
bindVehicleInfoInput: function (e) { |
|||
const { key } = e.currentTarget.dataset; |
|||
this.setData({ |
|||
[`vehicleInfo.${key}`]: e.detail.value |
|||
}); |
|||
}, |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
// 地址选择器值变化
|
|||
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' |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 手动选择出发地位置
|
|||
chooseOriginLocation: function () { |
|||
const that = this; |
|||
wx.chooseLocation({ |
|||
success(res) { |
|||
console.log('用户选择的出发地位置信息:', res); |
|||
const name = res.name; |
|||
const address = res.address; |
|||
const latitude = res.latitude; |
|||
const longitude = res.longitude; |
|||
|
|||
// 使用腾讯地图API进行逆地理编码,获取详细地址信息
|
|||
wx.request({ |
|||
url: 'https://apis.map.qq.com/ws/geocoder/v1/', |
|||
data: { |
|||
location: `${latitude},${longitude}`, |
|||
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77' |
|||
}, |
|||
success: function (response) { |
|||
if (response.data.status === 0) { |
|||
const result = response.data.result; |
|||
that.setData({ |
|||
origin: { |
|||
province: result.address_component.province || '', |
|||
city: result.address_component.city || '', |
|||
district: result.address_component.district || '', |
|||
detail: `${name} ${address}`, |
|||
latitude: latitude, |
|||
longitude: longitude |
|||
} |
|||
}); |
|||
wx.showToast({ |
|||
title: '出发地选择成功', |
|||
icon: 'success' |
|||
}); |
|||
} else { |
|||
wx.showToast({ |
|||
title: '地址解析失败,请手动输入', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}, |
|||
fail: function (err) { |
|||
console.error('地址解析失败:', err); |
|||
wx.showToast({ |
|||
title: '地址解析失败,请手动输入', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
fail(err) { |
|||
console.error('选择出发地位置失败:', err); |
|||
if (err.errMsg === 'chooseLocation:fail auth deny') { |
|||
wx.showToast({ |
|||
title: '需要位置授权才能选择地点', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
// 引导用户重新授权
|
|||
that.requestLocationAuth(); |
|||
} else { |
|||
wx.showToast({ |
|||
title: '选择位置失败', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 手动选择目的地位置
|
|||
chooseDestinationLocation: function () { |
|||
const that = this; |
|||
wx.chooseLocation({ |
|||
success(res) { |
|||
console.log('用户选择的目的地位置信息:', res); |
|||
const name = res.name; |
|||
const address = res.address; |
|||
const latitude = res.latitude; |
|||
const longitude = res.longitude; |
|||
|
|||
// 使用腾讯地图API进行逆地理编码,获取详细地址信息
|
|||
wx.request({ |
|||
url: 'https://apis.map.qq.com/ws/geocoder/v1/', |
|||
data: { |
|||
location: `${latitude},${longitude}`, |
|||
key: 'OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77' |
|||
}, |
|||
success: function (response) { |
|||
if (response.data.status === 0) { |
|||
const result = response.data.result; |
|||
that.setData({ |
|||
destination: { |
|||
province: result.address_component.province || '', |
|||
city: result.address_component.city || '', |
|||
district: result.address_component.district || '', |
|||
detail: `${name} ${address}`, |
|||
latitude: latitude, |
|||
longitude: longitude |
|||
} |
|||
}); |
|||
wx.showToast({ |
|||
title: '目的地选择成功', |
|||
icon: 'success' |
|||
}); |
|||
} else { |
|||
wx.showToast({ |
|||
title: '地址解析失败,请手动输入', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}, |
|||
fail: function (err) { |
|||
console.error('地址解析失败:', err); |
|||
wx.showToast({ |
|||
title: '地址解析失败,请手动输入', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
fail(err) { |
|||
console.error('选择目的地位置失败:', err); |
|||
if (err.errMsg === 'chooseLocation:fail auth deny') { |
|||
wx.showToast({ |
|||
title: '需要位置授权才能选择地点', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
// 引导用户重新授权
|
|||
that.requestLocationAuth(); |
|||
} else { |
|||
wx.showToast({ |
|||
title: '选择位置失败', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 请求位置授权
|
|||
requestLocationAuth: function () { |
|||
const that = this; |
|||
|
|||
console.log('开始请求位置授权'); |
|||
|
|||
wx.showLoading({ |
|||
title: '请求授权中...', |
|||
mask: true |
|||
}); |
|||
|
|||
// 检查用户是否已经拒绝过授权
|
|||
wx.getSetting({ |
|||
success(res) { |
|||
wx.hideLoading(); |
|||
if (res.authSetting['scope.userLocation'] === false) { |
|||
// 用户已经拒绝过授权,直接引导到设置页面
|
|||
wx.showModal({ |
|||
title: '需要位置授权', |
|||
content: '请在设置中开启位置授权,以便我们为您提供相关服务', |
|||
showCancel: true, |
|||
cancelText: '取消', |
|||
confirmText: '去授权', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
// 打开设置页面让用户手动开启授权
|
|||
wx.openSetting({ |
|||
success: (settingRes) => { |
|||
console.log('设置页面返回结果:', settingRes); |
|||
if (settingRes.authSetting['scope.userLocation']) { |
|||
// 用户在设置中开启了位置授权
|
|||
wx.showToast({ |
|||
title: '授权成功', |
|||
icon: 'success', |
|||
duration: 1500 |
|||
}); |
|||
} else { |
|||
// 用户在设置中仍未开启位置授权
|
|||
wx.showToast({ |
|||
title: '您已拒绝位置授权', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
console.error('打开设置失败:', err); |
|||
wx.showToast({ |
|||
title: '打开设置失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
// 用户未拒绝过授权,调用 authorize
|
|||
wx.authorize({ |
|||
scope: 'scope.userLocation', |
|||
success() { |
|||
console.log('位置授权成功'); |
|||
wx.showToast({ |
|||
title: '授权成功', |
|||
icon: 'success', |
|||
duration: 1500 |
|||
}); |
|||
}, |
|||
fail(err) { |
|||
console.log('位置授权失败:', err); |
|||
// 授权失败,弹出模态框引导用户重新授权
|
|||
wx.showModal({ |
|||
title: '需要位置授权', |
|||
content: '请在设置中开启位置授权,以便我们为您提供相关服务', |
|||
showCancel: true, |
|||
cancelText: '取消', |
|||
confirmText: '去授权', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
// 打开设置页面让用户手动开启授权
|
|||
wx.openSetting({ |
|||
success: (settingRes) => { |
|||
console.log('设置页面返回结果:', settingRes); |
|||
if (settingRes.authSetting['scope.userLocation']) { |
|||
// 用户在设置中开启了位置授权
|
|||
wx.showToast({ |
|||
title: '授权成功', |
|||
icon: 'success', |
|||
duration: 1500 |
|||
}); |
|||
} else { |
|||
// 用户在设置中仍未开启位置授权
|
|||
wx.showToast({ |
|||
title: '您已拒绝位置授权', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
console.error('打开设置失败:', err); |
|||
wx.showToast({ |
|||
title: '打开设置失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
fail(err) { |
|||
wx.hideLoading(); |
|||
console.error('获取设置失败:', err); |
|||
wx.showToast({ |
|||
title: '获取设置失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 计算运费
|
|||
calculateFreight: function () { |
|||
// 验证输入
|
|||
if (!this.data.origin.province || !this.data.destination.province) { |
|||
wx.showToast({ |
|||
title: '请填写完整的出发地和目的地', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 如果没有重量和体积,使用默认值 1
|
|||
if (!this.data.goodsInfo.weight && !this.data.goodsInfo.volume) { |
|||
this.setData({ |
|||
'goodsInfo.weight': 1 |
|||
}); |
|||
} |
|||
|
|||
this.setData({ loading: true }); |
|||
|
|||
// 构建请求参数
|
|||
const params = { |
|||
origin: this.data.origin, |
|||
destination: this.data.destination, |
|||
goodsInfo: this.data.goodsInfo, |
|||
vehicleInfo: this.data.vehicleInfo |
|||
}; |
|||
|
|||
// 调用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 |
|||
}, |
|||
vehicleInfo: { |
|||
type: '', |
|||
capacity: '', |
|||
number: '', |
|||
driver: '', |
|||
phone: '' |
|||
}, |
|||
selectedVehicleTypeIndex: 0, |
|||
|
|||
calculationResult: null |
|||
}); |
|||
}, |
|||
|
|||
// 使用历史记录
|
|||
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, |
|||
vehicleInfo: record.vehicleInfo || {}, |
|||
transportMode: record.transportMode, |
|||
calculationResult: record.result |
|||
}); |
|||
|
|||
// 恢复车辆类型选择
|
|||
if (record.vehicleInfo && record.vehicleInfo.type) { |
|||
const vehicleTypeIndex = this.data.vehicleTypes.indexOf(record.vehicleInfo.type); |
|||
if (vehicleTypeIndex !== -1) { |
|||
this.setData({ selectedVehicleTypeIndex: vehicleTypeIndex }); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 删除历史记录
|
|||
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' |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
}); |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"navigationBarTitleText": "物流运费估算", |
|||
"navigationBarBackgroundColor": "#ffffff", |
|||
"navigationBarTextStyle": "black" |
|||
} |
|||
@ -0,0 +1,240 @@ |
|||
<!-- pages/freight-calculator/index.wxml --> |
|||
<view class="container"> |
|||
<view class="main-content"> |
|||
<!-- 出发地信息 --> |
|||
<view class="section"> |
|||
<text class="section-title">出发地</text> |
|||
<view class="address-form"> |
|||
<view class="form-item"> |
|||
<text class="label">省份</text> |
|||
<input class="input" bindinput="bindOriginInput" data-key="province" value="{{origin.province}}" placeholder="请输入省份" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">城市</text> |
|||
<input class="input" bindinput="bindOriginInput" data-key="city" value="{{origin.city}}" placeholder="请输入城市" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">区县</text> |
|||
<input class="input" bindinput="bindOriginInput" data-key="district" value="{{origin.district}}" placeholder="请输入区县" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">详细地址</text> |
|||
<input class="input" bindinput="bindOriginInput" data-key="detail" value="{{origin.detail}}" placeholder="请输入详细地址" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">快速选择</text> |
|||
<picker |
|||
mode="region" |
|||
bindchange="bindRegionChange" |
|||
data-type="origin" |
|||
value="{{[origin.province, origin.city, origin.district]}}" |
|||
> |
|||
<view class="picker"> |
|||
{{origin.province && origin.city && origin.district ? origin.province + origin.city + origin.district : '请选择'}} |
|||
</view> |
|||
</picker> |
|||
</view> |
|||
<button class="location-btn" bindtap="chooseOriginLocation" style="background-color: #1677ff;"> |
|||
<text>手动选择位置</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 目的地信息 --> |
|||
<view class="section"> |
|||
<text class="section-title">目的地</text> |
|||
<view class="address-form"> |
|||
<view class="form-item"> |
|||
<text class="label">省份</text> |
|||
<input class="input" bindinput="bindDestinationInput" data-key="province" value="{{destination.province}}" placeholder="请输入省份" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">城市</text> |
|||
<input class="input" bindinput="bindDestinationInput" data-key="city" value="{{destination.city}}" placeholder="请输入城市" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">区县</text> |
|||
<input class="input" bindinput="bindDestinationInput" data-key="district" value="{{destination.district}}" placeholder="请输入区县" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">详细地址</text> |
|||
<input class="input" bindinput="bindDestinationInput" data-key="detail" value="{{destination.detail}}" placeholder="请输入详细地址" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">快速选择</text> |
|||
<picker |
|||
mode="region" |
|||
bindchange="bindRegionChange" |
|||
data-type="destination" |
|||
value="{{[destination.province, destination.city, destination.district]}}" |
|||
> |
|||
<view class="picker"> |
|||
{{destination.province && destination.city && destination.district ? destination.province + destination.city + destination.district : '请选择'}} |
|||
</view> |
|||
</picker> |
|||
</view> |
|||
<view style="display: flex; justify-content: space-between; gap: 12rpx;"> |
|||
<button class="location-btn" bindtap="useCurrentLocation"> |
|||
<text>使用当前位置</text> |
|||
</button> |
|||
<button class="location-btn" bindtap="chooseDestinationLocation" style="background-color: #1677ff;"> |
|||
<text>手动选择位置</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 物流车辆信息 --> |
|||
<view class="section"> |
|||
<text class="section-title">物流车辆信息</text> |
|||
<view class="vehicle-form"> |
|||
<view class="form-item"> |
|||
<text class="label">车辆类型</text> |
|||
<picker bindchange="bindVehicleTypeChange" range="{{vehicleTypes}}" value="{{selectedVehicleTypeIndex}}"> |
|||
<view class="picker"> |
|||
{{vehicleTypes[selectedVehicleTypeIndex]}} |
|||
</view> |
|||
</picker> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">载重能力 (吨)</text> |
|||
<input class="input" bindinput="bindVehicleInfoInput" data-key="capacity" value="{{vehicleInfo.capacity}}" placeholder="请输入载重能力" type="number" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">车辆编号</text> |
|||
<input class="input" bindinput="bindVehicleInfoInput" data-key="number" value="{{vehicleInfo.number}}" placeholder="请输入车辆编号" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">司机姓名</text> |
|||
<input class="input" bindinput="bindVehicleInfoInput" data-key="driver" value="{{vehicleInfo.driver}}" placeholder="请输入司机姓名" /> |
|||
</view> |
|||
<view class="form-item"> |
|||
<text class="label">联系电话</text> |
|||
<input class="input" bindinput="bindVehicleInfoInput" data-key="phone" value="{{vehicleInfo.phone}}" placeholder="请输入联系电话" type="number" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 物流人员信息 --> |
|||
<view class="section"> |
|||
<text class="section-title">物流人员信息</text> |
|||
<view class="logistics-intro"> |
|||
<text class="intro-text">我们拥有专业的物流团队,为您提供高效、安全的运输服务。以下是我们的物流专家,他们具有丰富的行业经验和专业知识,能够为您的货物运输提供专业的解决方案。</text> |
|||
</view> |
|||
<view class="logistics-personnel"> |
|||
<view class="personnel-card" wx:for="{{logisticsPersonnel}}" wx:key="item.id"> |
|||
<view class="personnel-header-section"> |
|||
<view class="personnel-avatar"> |
|||
<text class="avatar-text">{{item.name.charAt(0)}}</text> |
|||
</view> |
|||
<view class="personnel-header-info"> |
|||
<view class="personnel-name">{{item.name}}</view> |
|||
<view class="personnel-position">{{item.position}}</view> |
|||
<view class="personnel-tags"> |
|||
<view class="personnel-tag">{{item.experience}}年物流经验</view> |
|||
<view class="personnel-tag">认证物流师</view> |
|||
<view class="personnel-tag">专业配送</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="personnel-description"> |
|||
<view class="description-text"> |
|||
{{item.description || '拥有丰富的物流行业经验,熟悉各种货物的运输要求和流程,能够为客户提供专业的物流解决方案和优质的服务。'}} |
|||
</view> |
|||
</view> |
|||
<view class="personnel-contact-section"> |
|||
<view class="personnel-contact-info"> |
|||
<view class="contact-label">联系电话:</view> |
|||
<view class="contact-value">{{item.phone}}</view> |
|||
</view> |
|||
<button class="contact-btn" bindtap="makePhoneCall" data-phone="{{item.phone}}"> |
|||
<text>立即联系</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
<view wx:if="{{logisticsPersonnel.length === 0}}" class="empty-state"> |
|||
<text>暂无物流人员信息</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
|
|||
<!-- 计算按钮 --> |
|||
<view class="calculate-section"> |
|||
<button class="calculate-btn" bindtap="calculateFreight" loading="{{loading}}"> |
|||
<text>计算运费</text> |
|||
</button> |
|||
<button class="clear-btn" bindtap="clearInput"> |
|||
<text>清空输入</text> |
|||
</button> |
|||
</view> |
|||
|
|||
<!-- 计算结果 --> |
|||
<view class="result-section" wx:if="{{calculationResult}}"> |
|||
<text class="section-title">计算结果</text> |
|||
<view class="result-card"> |
|||
<view class="result-item"> |
|||
<text class="result-label">预估运费</text> |
|||
<text class="result-value freight">{{calculationResult.freight}} 元</text> |
|||
</view> |
|||
<view class="result-item"> |
|||
<text class="result-label">运输距离</text> |
|||
<text class="result-value">{{calculationResult.distance}} km</text> |
|||
</view> |
|||
<view class="result-item"> |
|||
<text class="result-label">预计时效</text> |
|||
<text class="result-value">{{calculationResult.deliveryTime}} 天</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 历史记录 --> |
|||
<view class="section" wx:if="{{historyRecords.length > 0}}"> |
|||
<text class="section-title">历史记录</text> |
|||
<view class="history-list"> |
|||
<view class="history-item" wx:for="{{historyRecords}}" wx:key="item.id"> |
|||
<view class="history-content"> |
|||
<text class="history-origin">{{item.origin.province}}{{item.origin.city}}</text> |
|||
<text class="history-arrow">→</text> |
|||
<text class="history-destination">{{item.destination.province}}{{item.destination.city}}</text> |
|||
<text class="history-freight">¥{{item.result.freight}}</text> |
|||
</view> |
|||
<view class="history-actions"> |
|||
<button class="history-use-btn" bindtap="useHistoryRecord" data-index="{{index}}"> |
|||
<text>使用</text> |
|||
</button> |
|||
<button class="history-delete-btn" bindtap="deleteHistoryRecord" data-index="{{index}}"> |
|||
<text>删除</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
<button class="clear-history-btn" bindtap="clearHistory"> |
|||
<text>清空历史记录</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="section" wx:if="{{historyRecords.length === 0}}"> |
|||
<text class="section-title">历史记录</text> |
|||
<view class="empty-state"> |
|||
<text>暂无历史记录</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 地址选择器 --> |
|||
<view class="picker-overlay" wx:if="{{showOriginPicker || showDestinationPicker}}" bindtap="closePicker"> |
|||
<view class="picker-popup-container" catchtap=""> |
|||
<view class="picker-header"> |
|||
<text class="picker-title">{{showOriginPicker ? '选择出发地' : '选择目的地'}}</text> |
|||
<button class="picker-close" bindtap="closePicker">关闭</button> |
|||
</view> |
|||
<view class="picker-content"> |
|||
<!-- 这里可以集成地址选择器组件 --> |
|||
<text>地址选择器功能待实现</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
@ -0,0 +1,877 @@ |
|||
/* pages/freight-calculator/index.wxss */ |
|||
/* 全局样式 */ |
|||
.container { |
|||
padding: 0; |
|||
background-color: #f8f9fa; |
|||
min-height: 100vh; |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
|||
width: 100%; |
|||
max-width: 100%; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 确保所有主要元素都有box-sizing */ |
|||
.main-content, |
|||
.section, |
|||
.form-item, |
|||
.input, |
|||
.location-btn, |
|||
.picker, |
|||
.calculate-section, |
|||
.calculate-btn, |
|||
.clear-btn, |
|||
.result-section, |
|||
.result-card, |
|||
.result-item, |
|||
.history-list, |
|||
.history-item, |
|||
.history-content, |
|||
.history-actions, |
|||
.clear-history-btn, |
|||
.picker-overlay, |
|||
.picker-popup-container, |
|||
.picker-header, |
|||
.picker-content, |
|||
.empty-state, |
|||
.divider { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 物流人员信息样式 */ |
|||
.logistics-intro { |
|||
background-color: #f8f9fa; |
|||
border-radius: 12rpx; |
|||
padding: 16rpx; |
|||
margin-bottom: 20rpx; |
|||
border-left: 4rpx solid #1989fa; |
|||
} |
|||
|
|||
.intro-text { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.logistics-personnel { |
|||
width: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.personnel-card { |
|||
background-color: #fff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); |
|||
border: 1rpx solid #e8e8e8; |
|||
width: 100%; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.personnel-card:hover { |
|||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12); |
|||
transform: translateY(-2rpx); |
|||
border-color: #1989fa; |
|||
} |
|||
|
|||
.personnel-header-section { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.personnel-avatar { |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
border-radius: 50%; |
|||
background: linear-gradient(135deg, #1989fa 0%, #1890ff 100%); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-right: 24rpx; |
|||
flex-shrink: 0; |
|||
box-shadow: 0 4rpx 12rpx rgba(25, 137, 250, 0.3); |
|||
} |
|||
|
|||
.avatar-text { |
|||
font-size: 40rpx; |
|||
font-weight: 700; |
|||
color: #fff; |
|||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); |
|||
} |
|||
|
|||
.personnel-header-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.personnel-name { |
|||
font-size: 30rpx; |
|||
font-weight: 700; |
|||
color: #333; |
|||
line-height: 1.2; |
|||
margin: 0; |
|||
} |
|||
|
|||
.personnel-position { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.personnel-tags { |
|||
display: flex; |
|||
gap: 12rpx; |
|||
flex-wrap: wrap; |
|||
margin-top: 4rpx; |
|||
} |
|||
|
|||
.personnel-tag { |
|||
font-size: 18rpx; |
|||
color: #1989fa; |
|||
background-color: rgba(25, 137, 250, 0.1); |
|||
padding: 6rpx 14rpx; |
|||
border-radius: 16rpx; |
|||
border: 1rpx solid rgba(25, 137, 250, 0.2); |
|||
} |
|||
|
|||
.personnel-description { |
|||
margin-bottom: 20rpx; |
|||
padding: 16rpx; |
|||
background-color: #f8f9fa; |
|||
border-radius: 12rpx; |
|||
border-left: 4rpx solid #e3f2fd; |
|||
} |
|||
|
|||
.description-text { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
line-height: 1.5; |
|||
margin: 0; |
|||
} |
|||
|
|||
.personnel-contact-section { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding-top: 20rpx; |
|||
border-top: 1rpx solid #f0f0f0; |
|||
} |
|||
|
|||
.personnel-contact-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.contact-label { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.contact-value { |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.contact-btn { |
|||
background: linear-gradient(135deg, #1989fa 0%, #1890ff 100%); |
|||
color: #fff; |
|||
border: none; |
|||
border-radius: 8rpx; |
|||
padding: 14rpx 28rpx; |
|||
font-size: 22rpx; |
|||
font-weight: 600; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
gap: 8rpx; |
|||
box-shadow: 0 4rpx 12rpx rgba(25, 137, 250, 0.3); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.contact-btn:hover { |
|||
background: linear-gradient(135deg, #1890ff 0%, #177ddc 100%); |
|||
box-shadow: 0 6rpx 16rpx rgba(25, 137, 250, 0.4); |
|||
transform: translateY(-2rpx); |
|||
} |
|||
|
|||
.contact-btn:active { |
|||
transform: translateY(0); |
|||
box-shadow: 0 2rpx 8rpx rgba(25, 137, 250, 0.3); |
|||
} |
|||
|
|||
/* 主内容区域 */ |
|||
.main-content { |
|||
padding: 16rpx; |
|||
box-sizing: border-box; |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 区块样式 */ |
|||
.section { |
|||
background-color: #fff; |
|||
border-radius: 12rpx; |
|||
padding: 20rpx; |
|||
margin-bottom: 16rpx; |
|||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); |
|||
width: 100%; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 24rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
margin-bottom: 16rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.section-title::before { |
|||
content: ''; |
|||
width: 6rpx; |
|||
height: 24rpx; |
|||
background-color: #1989fa; |
|||
border-radius: 3rpx; |
|||
margin-right: 10rpx; |
|||
} |
|||
|
|||
/* 表单项目样式 */ |
|||
.form-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
margin-bottom: 16rpx; |
|||
position: relative; |
|||
width: 100%; |
|||
} |
|||
|
|||
.label { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
margin-bottom: 8rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.input { |
|||
height: 68rpx; |
|||
border: 1rpx solid #e5e5e5; |
|||
border-radius: 8rpx; |
|||
padding: 0 20rpx; |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
box-sizing: border-box; |
|||
line-height: 68rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.input:focus { |
|||
border-color: #1989fa; |
|||
outline: none; |
|||
} |
|||
|
|||
/* 只读输入框 */ |
|||
.input.readonly { |
|||
background-color: #f8f9fa; |
|||
border-color: #e9ecef; |
|||
color: #6c757d; |
|||
} |
|||
|
|||
/* 位置按钮 */ |
|||
.location-btn { |
|||
width: 100%; |
|||
height: 68rpx; |
|||
background-color: #f8f9fa; |
|||
border: 1rpx solid #e9ecef; |
|||
border-radius: 8rpx; |
|||
font-size: 22rpx; |
|||
color: #495057; |
|||
margin-top: 8rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-sizing: border-box; |
|||
line-height: 68rpx; |
|||
flex: 1; |
|||
} |
|||
|
|||
.location-btn::before { |
|||
content: '📍'; |
|||
margin-right: 8rpx; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
/* 选择器样式 */ |
|||
.picker { |
|||
height: 68rpx; |
|||
border: 1rpx solid #e5e5e5; |
|||
border-radius: 8rpx; |
|||
padding: 0 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
background-color: #fff; |
|||
width: 100%; |
|||
font-size: 24rpx; |
|||
color: #333; |
|||
position: relative; |
|||
box-sizing: border-box; |
|||
line-height: 68rpx; |
|||
} |
|||
|
|||
.picker::after { |
|||
content: '▼'; |
|||
position: absolute; |
|||
right: 20rpx; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
font-size: 16rpx; |
|||
color: #666; |
|||
} |
|||
|
|||
/* 计算按钮区域 */ |
|||
.calculate-section { |
|||
display: flex; |
|||
flex-direction: column; |
|||
margin-top: 24rpx; |
|||
margin-bottom: 24rpx; |
|||
gap: 12rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.calculate-btn { |
|||
width: 100%; |
|||
height: 76rpx; |
|||
background-color: #1989fa; |
|||
color: #fff; |
|||
border: none; |
|||
border-radius: 8rpx; |
|||
font-size: 26rpx; |
|||
font-weight: 600; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-sizing: border-box; |
|||
line-height: 76rpx; |
|||
} |
|||
|
|||
.clear-btn { |
|||
width: 100%; |
|||
height: 76rpx; |
|||
background-color: #fff; |
|||
color: #666; |
|||
border: 1rpx solid #e5e5e5; |
|||
border-radius: 8rpx; |
|||
font-size: 26rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-sizing: border-box; |
|||
line-height: 76rpx; |
|||
} |
|||
|
|||
/* 结果区域 */ |
|||
.result-section { |
|||
background-color: #fff; |
|||
border-radius: 12rpx; |
|||
padding: 20rpx; |
|||
margin-bottom: 16rpx; |
|||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); |
|||
width: 100%; |
|||
} |
|||
|
|||
.result-card { |
|||
background: linear-gradient(135deg, #f0f9ff 0%, #e3f2fd 100%); |
|||
border-radius: 8rpx; |
|||
padding: 20rpx; |
|||
border-left: 4rpx solid #1989fa; |
|||
width: 100%; |
|||
} |
|||
|
|||
.result-item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 12rpx; |
|||
padding: 8rpx 0; |
|||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05); |
|||
width: 100%; |
|||
} |
|||
|
|||
.result-item:last-child { |
|||
margin-bottom: 0; |
|||
border-bottom: none; |
|||
} |
|||
|
|||
.result-label { |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
font-weight: 500; |
|||
flex: 1; |
|||
} |
|||
|
|||
.result-value { |
|||
font-size: 24rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: right; |
|||
} |
|||
|
|||
.result-value.freight { |
|||
color: #ff4d4f; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
/* 历史记录 */ |
|||
.history-list { |
|||
margin-top: 12rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.history-item { |
|||
background-color: #f8f9fa; |
|||
border-radius: 8rpx; |
|||
padding: 16rpx; |
|||
margin-bottom: 10rpx; |
|||
border-left: 4rpx solid #e9ecef; |
|||
width: 100%; |
|||
} |
|||
|
|||
.history-content { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 8rpx; |
|||
flex-wrap: wrap; |
|||
width: 100%; |
|||
} |
|||
|
|||
.history-origin { |
|||
font-size: 22rpx; |
|||
color: #333; |
|||
font-weight: 500; |
|||
margin-right: 8rpx; |
|||
flex: 0 0 auto; |
|||
} |
|||
|
|||
.history-arrow { |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
margin: 0 8rpx; |
|||
flex: 0 0 auto; |
|||
} |
|||
|
|||
.history-destination { |
|||
font-size: 22rpx; |
|||
color: #333; |
|||
font-weight: 500; |
|||
margin-right: 16rpx; |
|||
flex: 1; |
|||
min-width: 120rpx; |
|||
} |
|||
|
|||
.history-freight { |
|||
font-size: 24rpx; |
|||
font-weight: 600; |
|||
color: #ff4d4f; |
|||
margin-left: auto; |
|||
flex: 0 0 auto; |
|||
} |
|||
|
|||
.history-actions { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 12rpx; |
|||
margin-top: 8rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.history-use-btn { |
|||
font-size: 20rpx; |
|||
color: #1989fa; |
|||
background: none; |
|||
border: none; |
|||
padding: 0 8rpx; |
|||
} |
|||
|
|||
.history-delete-btn { |
|||
font-size: 20rpx; |
|||
color: #ff4d4f; |
|||
background: none; |
|||
border: none; |
|||
padding: 0 8rpx; |
|||
} |
|||
|
|||
.clear-history-btn { |
|||
width: 100%; |
|||
height: 56rpx; |
|||
background: none; |
|||
border: 1rpx solid #e5e5e5; |
|||
border-radius: 8rpx; |
|||
font-size: 22rpx; |
|||
color: #666; |
|||
margin-top: 12rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 选择器弹窗 */ |
|||
.picker-overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: flex-end; |
|||
z-index: 1000; |
|||
width: 100%; |
|||
} |
|||
|
|||
.picker-popup-container { |
|||
background-color: #fff; |
|||
border-radius: 16rpx 16rpx 0 0; |
|||
width: 100%; |
|||
max-height: 70vh; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.picker-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx; |
|||
border-bottom: 1rpx solid #e5e5e5; |
|||
background-color: #f8f9fa; |
|||
width: 100%; |
|||
} |
|||
|
|||
.picker-title { |
|||
font-size: 26rpx; |
|||
font-weight: 600; |
|||
color: #333; |
|||
flex: 1; |
|||
text-align: center; |
|||
} |
|||
|
|||
.picker-close { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
background: none; |
|||
border: none; |
|||
padding: 16rpx; |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 50%; |
|||
position: absolute; |
|||
right: 20rpx; |
|||
} |
|||
|
|||
.picker-content { |
|||
padding: 24rpx; |
|||
text-align: center; |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 空状态 */ |
|||
.empty-state { |
|||
text-align: center; |
|||
padding: 48rpx 16rpx; |
|||
color: #999; |
|||
font-size: 22rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.empty-state::before { |
|||
content: '📦'; |
|||
font-size: 48rpx; |
|||
display: block; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
/* 分隔线 */ |
|||
.divider { |
|||
height: 1rpx; |
|||
background-color: #e5e5e5; |
|||
margin: 16rpx 0; |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 标签样式 */ |
|||
.tag { |
|||
display: inline-block; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 12rpx; |
|||
font-size: 20rpx; |
|||
font-weight: 500; |
|||
margin-right: 8rpx; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.tag-primary { |
|||
background-color: #e6f7ff; |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.tag-success { |
|||
background-color: #f6ffed; |
|||
color: #52c41a; |
|||
} |
|||
|
|||
.tag-warning { |
|||
background-color: #fff7e6; |
|||
color: #fa8c16; |
|||
} |
|||
|
|||
.tag-danger { |
|||
background-color: #fff1f0; |
|||
color: #ff4d4f; |
|||
} |
|||
|
|||
/* 动画效果 */ |
|||
@keyframes fadeInUp { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(16rpx); |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
@keyframes slideUp { |
|||
from { |
|||
transform: translateY(100%); |
|||
} |
|||
to { |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
@keyframes fadeIn { |
|||
from { |
|||
opacity: 0; |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
/* 响应式调整 */ |
|||
@media (min-width: 375px) { |
|||
.main-content { |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.section { |
|||
padding: 24rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 26rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.form-item { |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.input { |
|||
height: 72rpx; |
|||
font-size: 26rpx; |
|||
padding: 0 24rpx; |
|||
} |
|||
|
|||
.picker { |
|||
height: 72rpx; |
|||
font-size: 26rpx; |
|||
padding: 0 24rpx; |
|||
} |
|||
|
|||
.location-btn { |
|||
height: 72rpx; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.calculate-btn, |
|||
.clear-btn { |
|||
height: 80rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.result-item { |
|||
margin-bottom: 16rpx; |
|||
padding: 12rpx 0; |
|||
} |
|||
|
|||
.result-label { |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.result-value { |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.result-value.freight { |
|||
font-size: 32rpx; |
|||
} |
|||
|
|||
.history-content { |
|||
margin-bottom: 12rpx; |
|||
} |
|||
|
|||
.history-origin, |
|||
.history-destination, |
|||
.history-arrow { |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.history-freight { |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.history-use-btn, |
|||
.history-delete-btn { |
|||
font-size: 22rpx; |
|||
} |
|||
|
|||
.clear-history-btn { |
|||
height: 60rpx; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.empty-state { |
|||
padding: 64rpx 24rpx; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.empty-state::before { |
|||
font-size: 64rpx; |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.divider { |
|||
margin: 24rpx 0; |
|||
} |
|||
} |
|||
|
|||
/* 大屏幕设备调整 */ |
|||
@media (min-width: 750rpx) { |
|||
.container { |
|||
max-width: 750rpx; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
.header { |
|||
max-width: 750rpx; |
|||
margin: 0 auto; |
|||
} |
|||
} |
|||
|
|||
/* 小屏幕设备调整 */ |
|||
@media (max-width: 320px) { |
|||
.main-content { |
|||
padding: 12rpx; |
|||
} |
|||
|
|||
.section { |
|||
padding: 16rpx; |
|||
margin-bottom: 12rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 22rpx; |
|||
margin-bottom: 12rpx; |
|||
} |
|||
|
|||
.input { |
|||
height: 64rpx; |
|||
font-size: 22rpx; |
|||
padding: 0 16rpx; |
|||
} |
|||
|
|||
.picker { |
|||
height: 64rpx; |
|||
font-size: 22rpx; |
|||
padding: 0 16rpx; |
|||
} |
|||
|
|||
.location-btn { |
|||
height: 64rpx; |
|||
font-size: 20rpx; |
|||
} |
|||
|
|||
.calculate-btn, |
|||
.clear-btn { |
|||
height: 72rpx; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.result-item { |
|||
margin-bottom: 8rpx; |
|||
padding: 6rpx 0; |
|||
} |
|||
|
|||
.result-label { |
|||
font-size: 20rpx; |
|||
} |
|||
|
|||
.result-value { |
|||
font-size: 22rpx; |
|||
} |
|||
|
|||
.result-value.freight { |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.history-content { |
|||
margin-bottom: 6rpx; |
|||
} |
|||
|
|||
.history-origin, |
|||
.history-destination, |
|||
.history-arrow { |
|||
font-size: 20rpx; |
|||
} |
|||
|
|||
.history-freight { |
|||
font-size: 22rpx; |
|||
} |
|||
|
|||
.history-use-btn, |
|||
.history-delete-btn { |
|||
font-size: 18rpx; |
|||
} |
|||
|
|||
.clear-history-btn { |
|||
height: 52rpx; |
|||
font-size: 20rpx; |
|||
} |
|||
|
|||
.empty-state { |
|||
padding: 32rpx 12rpx; |
|||
font-size: 20rpx; |
|||
} |
|||
|
|||
.empty-state::before { |
|||
font-size: 32rpx; |
|||
margin-bottom: 12rpx; |
|||
} |
|||
|
|||
.divider { |
|||
margin: 12rpx 0; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue