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