Просмотр исходного кода

feat:历史遗留未提交代码

颜琼丽 1 месяц назад
Родитель
Сommit
f5e0bb99e4

+ 266 - 0
jd_logistics-app/components/RegionPicker.vue

@@ -0,0 +1,266 @@
+<template>
+	<view class="region-picker" @click="showPicker">
+		<view class="picker-display">
+			<text :class="['region-text', selectedText ? '' : 'placeholder']">
+				{{ selectedText || placeholder }}
+			</text>
+			<u-icon class="arrow" name='arrow-right' size="18"></u-icon>
+		</view>
+		<picker mode="multiSelector" :range="range" range-key="name" :value="indexes" @change="onPickerChange"
+			@columnchange="onColumnChange" :disabled="disabled">
+			<view class="picker-mask"></view>
+		</picker>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		computed,
+		watch,
+		nextTick,
+		onMounted
+	} from 'vue'
+	import areaData from '@/utils/area-data.js'
+
+	const props = defineProps({
+		province: {
+			type: String,
+			default: ''
+		},
+		city: {
+			type: String,
+			default: ''
+		},
+		district: {
+			type: String,
+			default: ''
+		},
+		placeholder: {
+			type: String,
+			default: '省市区'
+		},
+		disabled: {
+			type: Boolean,
+			default: false
+		}
+	})
+
+	const emit = defineEmits(['change', 'update:province', 'update:city', 'update:district'])
+
+	// 省市区列表
+	const provinces = ref([])
+	const cities = ref([])
+	const districts = ref([])
+
+	// 选中索引
+	const provinceIndex = ref(0)
+	const cityIndex = ref(0)
+	const districtIndex = ref(0)
+
+	// picker range
+	const range = computed(() => [provinces.value, cities.value, districts.value])
+	const indexes = computed(() => [provinceIndex.value, cityIndex.value, districtIndex.value])
+
+	// 显示文本
+	const selectedText = computed(() => {
+		if (provinceIndex.value >= 0 && provinces.value[provinceIndex.value]) {
+			const p = provinces.value[provinceIndex.value]?.name || ''
+			const c = cities.value[cityIndex.value]?.name || ''
+			const d = districts.value[districtIndex.value]?.name || ''
+			return `${p} ${c} ${d}`.trim()
+		}
+		return ''
+	})
+
+	// 初始化省份数据
+	function initProvinces() {
+		provinces.value = areaData.filter(item => item.type === 0).map(item => ({
+			code: item.code,
+			name: item.name
+		}))
+		if (provinces.value.length > 0) {
+			provinceIndex.value = 0
+			// 默认加载第一个省份的城市
+			updateCities(provinces.value[0].code)
+		}
+	}
+
+	// 根据省份代码更新城市列表
+	function updateCities(provinceCode) {
+		cities.value = areaData.filter(item => item.parent_code === provinceCode && item.type === 1).map(item => ({
+			code: item.code,
+			name: item.name
+		}))
+		if (cities.value.length > 0) {
+			cityIndex.value = 0
+			updateDistricts(cities.value[0].code)
+		} else {
+			cities.value = []
+			districts.value = []
+		}
+	}
+
+	// 根据城市代码更新区县列表
+	function updateDistricts(cityCode) {
+		districts.value = areaData.filter(item => item.parent_code === cityCode && item.type === 2).map(item => ({
+			code: item.code,
+			name: item.name
+		}))
+		if (districts.value.length > 0) {
+			districtIndex.value = 0
+		} else {
+			districts.value = []
+		}
+	}
+
+	// 根据省市区名称设置索引(用于回显)
+	function setIndexesByNames(provinceName, cityName, districtName) {
+		if (!provinceName || provinces.value.length === 0) return
+
+		// 查找省份
+		const provinceItem = provinces.value.find(p => p.name === provinceName)
+		if (!provinceItem) return
+
+		provinceIndex.value = provinces.value.indexOf(provinceItem)
+
+		// 更新城市列表(同步执行)
+		updateCities(provinceItem.code)
+
+		// 等待城市列表更新后(由于 updateCities 是同步,但 cities 已更新),设置城市索引
+		if (cityName) {
+			const cityItem = cities.value.find(c => c.name === cityName)
+			if (cityItem) {
+				cityIndex.value = cities.value.indexOf(cityItem)
+				// 更新区县列表
+				updateDistricts(cityItem.code)
+				// 等待区县列表更新后设置区县索引
+				if (districtName) {
+					const districtItem = districts.value.find(d => d.name === districtName)
+					if (districtItem) {
+						districtIndex.value = districts.value.indexOf(districtItem)
+					}
+				}
+			}
+		}
+	}
+
+	// 列变化
+	function onColumnChange(e) {
+		const {
+			column,
+			value
+		} = e.detail
+		if (column === 0) {
+			provinceIndex.value = value
+			const provinceCode = provinces.value[value].code
+			updateCities(provinceCode)
+		} else if (column === 1) {
+			cityIndex.value = value
+			const cityCode = cities.value[value].code
+			updateDistricts(cityCode)
+		}
+	}
+
+	// 确认选择
+	function onPickerChange(e) {
+		const values = e.detail.value
+		provinceIndex.value = values[0]
+		cityIndex.value = values[1]
+		districtIndex.value = values[2]
+
+		const province = provinces.value[provinceIndex.value]
+		const city = cities.value[cityIndex.value]
+		const district = districts.value[districtIndex.value]
+
+		emit('update:province', province?.name || '')
+		emit('update:city', city?.name || '')
+		emit('update:district', district?.name || '')
+		emit('change', {
+			province: province?.name || '',
+			city: city?.name || '',
+			district: district?.name || '',
+			provinceCode: province?.code || '',
+			cityCode: city?.code || '',
+			districtCode: district?.code || ''
+		})
+	}
+
+	function showPicker() {}
+
+	// 监听外部传入的省市区名称变化(用于编辑回显)
+	watch(
+		() => [props.province, props.city, props.district],
+		([newProvince, newCity, newDistrict]) => {
+			if (newProvince && provinces.value.length > 0) {
+				setIndexesByNames(newProvince, newCity, newDistrict)
+			}
+		}, {
+			immediate: true,
+			deep: true
+		}
+	)
+
+	onMounted(() => {
+		initProvinces()
+	})
+</script>
+
+<style scoped lang="less">
+	
+	.region-picker {
+		position: relative;
+		width: 100%;
+		height: 100rpx;
+		/* 与 input 高度一致 */
+	}
+
+	.picker-display {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		height: 100%;
+		/* 与输入框背景一致 */
+		border-radius: 8rpx;
+		/* 圆角一致 */
+		font-size: 28rpx;
+		color: #333;
+
+		.region-text {
+			flex: 1;
+			font-size: 28rpx;
+			line-height: 100rpx;
+			color: #333;
+
+			&.placeholder {
+				color: #999;
+			}
+		}
+
+		.arrow {
+			margin-left: 16rpx;
+			color: #999;
+			font-size: 28rpx;
+		}
+	}
+
+	.region-text {
+		flex: 1;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.region-text.placeholder {
+		color: #999;
+	}
+
+	.picker-mask {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		opacity: 0;
+	}
+</style>

+ 234 - 0
jd_logistics-app/pages/mine/setting.vue

@@ -0,0 +1,234 @@
+<template>
+	<view class="setting-container">
+		<!-- 自定义导航,可复用headerInfo组件,这里简单写一个 -->
+<!-- 		<view class="nav-header">
+			<view class="back-btn" @click="goBack">
+				<image src="/static/img/arrow-left.png" class="back-icon"></image>
+			</view>
+			<text class="nav-title">设置</text>
+			<view class="placeholder"></view>
+		</view> -->
+
+		<!-- 设置项卡片 -->
+		<view class="menu-section">
+			<view class="menu-card">
+				<!-- 用户协议 -->
+				<view class="menu-item" @click="toUserAgreement">
+					<view class="menu-item-left">
+						<!-- <image src="/static/img/mine/icon-mine-policy.png" class="menu-icon"></image> -->
+						<text class="menu-text">用户协议</text>
+					</view>
+					<image src="/static/img/arrow-right.png" class="arrow-icon"></image>
+				</view>
+
+				<!-- 隐私条款(隐私政策) -->
+				<view class="menu-item" @click="toPrivacyPolicy">
+					<view class="menu-item-left">
+						<!-- <image src="/static/img/mine/icon-mine-policy.png" class="menu-icon"></image> -->
+						<text class="menu-text">隐私条款</text>
+					</view>
+					<image src="/static/img/arrow-right.png" class="arrow-icon"></image>
+				</view>
+
+				<!-- 个人信息已收集清单 -->
+				<view class="menu-item" @click="toUserInfoList">
+					<view class="menu-item-left">
+						<!-- <image src="/static/img/mine/icon-mine-policy.png" class="menu-icon"></image> -->
+						<text class="menu-text">个人信息已收集清单</text>
+					</view>
+					<image src="/static/img/arrow-right.png" class="arrow-icon"></image>
+				</view>
+
+				<!-- 第三方信息共享清单 -->
+				<view class="menu-item" @click="toThirdPartyList">
+					<view class="menu-item-left">
+						<!-- <image src="/static/img/mine/icon-mine-policy.png" class="menu-icon"></image> -->
+						<text class="menu-text">第三方信息共享清单</text>
+					</view>
+					<image src="/static/img/arrow-right.png" class="arrow-icon"></image>
+				</view>
+
+				<!-- 注销账号 -->
+<!-- 				<view class="menu-item" @click="cancelAccount">
+					<view class="menu-item-left">
+						<text class="menu-text">注销账号</text>
+					</view>
+					<image src="/static/img/arrow-right.png" class="arrow-icon"></image>
+				</view> -->
+
+				<!-- 退出登录 -->
+<!-- 				<view class="menu-item logout-item" @click="userLogoutFn">
+					<text class="menu-text logout-text">退出登录</text>
+				</view> -->
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { useAppStore } from "@/stores/app";
+
+const appStore = useAppStore();
+
+// 返回上一页
+function goBack() {
+	uni.navigateBack();
+}
+
+// 跳转到用户协议
+function toUserAgreement() {
+	uni.navigateTo({
+		url: '/pages/webView/webView?title=用户协议&url=' + encodeURIComponent('https://rjsd.mychery.com/user_agreement.html')
+	});
+}
+
+// 跳转到隐私政策
+function toPrivacyPolicy() {
+	uni.navigateTo({
+		url: '/pages/webView/webView?title=隐私条款&url=' + encodeURIComponent('https://rjsd.mychery.com/privacy_policy.html')
+	});
+}
+
+// 跳转到个人信息收集清单
+function toUserInfoList() {
+	uni.navigateTo({
+		url: '/pages/webView/webView?title=个人信息已收集清单&url=' + encodeURIComponent('https://rjsd.mychery.com/userinfo.html')
+	});
+}
+
+// 跳转到第三方信息共享清单
+function toThirdPartyList() {
+	uni.navigateTo({
+		url: '/pages/webView/webView?title=第三方信息共享清单&url=' + encodeURIComponent('https://rjsd.mychery.com/third_userinfo.html')
+	});
+}
+
+// 注销账号(需单独处理,此处先弹窗提示)
+function cancelAccount() {
+	uni.showModal({
+		title: '提示',
+		content: '确认要注销账号吗?注销后所有数据将被清除且无法恢复。',
+		success: (res) => {
+			if (res.confirm) {
+				// 这里可以调用注销接口
+				uni.showToast({ title: '注销功能暂未开放', icon: 'none' });
+			}
+		}
+	});
+}
+
+// 退出登录
+function userLogoutFn() {
+	uni.showModal({
+		title: '提示',
+		content: '确认要退出登录吗?',
+		success: function(res) {
+			if (res.confirm) {
+				appStore.LOGOUT();
+				uni.navigateBack(); // 退出后返回上一页
+			}
+		}
+	});
+}
+</script>
+
+<style lang="less" scoped>
+.setting-container {
+	height: 100vh;
+	background: #F5F7FA;
+	padding: 0 20rpx;
+
+	.nav-header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 28rpx 16rpx 20rpx;
+		background: transparent;
+
+		.back-btn {
+			width: 60rpx;
+			height: 60rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			.back-icon {
+				width: 48rpx;
+				height: 48rpx;
+			}
+		}
+
+		.nav-title {
+			font-size: 36rpx;
+			font-weight: 600;
+			color: #333333;
+		}
+
+		.placeholder {
+			width: 60rpx;
+			height: 60rpx;
+		}
+	}
+
+	.menu-section {
+		padding-top: 20rpx;
+
+		.menu-card {
+			overflow: hidden;
+		}
+
+		.menu-item {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 32rpx;
+			background: #FFFFFF;
+			border-radius: 24rpx;
+			margin-bottom: 20rpx;
+			min-height: 112rpx;
+
+			.menu-item-left {
+				display: flex;
+				align-items: center;
+				gap: 24rpx;
+				flex: 1;
+			}
+
+			.menu-icon {
+				width: 48rpx;
+				height: 48rpx;
+				flex-shrink: 0;
+			}
+
+			.menu-text {
+				font-size: 32rpx;
+				font-weight: 500;
+				color: #333333;
+				line-height: 45rpx;
+			}
+
+			.arrow-icon {
+				width: 48rpx;
+				height: 48rpx;
+				opacity: 0.5;
+				flex-shrink: 0;
+			}
+
+			&.logout-item {
+				height: 88rpx;
+				background: #FEEAEA;
+				border-radius: 32rpx;
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				.logout-text {
+					font-weight: 400;
+					font-size: 32rpx;
+					color: #F52929;
+					text-align: center;
+				}
+			}
+		}
+	}
+}
+</style>

+ 152 - 0
jd_logistics-app/pages/order/components/OrderAddedService.vue

@@ -0,0 +1,152 @@
+<template>
+  <view class="info-card">
+    <view class="card-body">
+      <!-- 包装服务(仅京东) -->
+      <view class="info-row" v-if="orderType === 1">
+        <text class="info-label">包装服务:</text>
+        <text class="info-value" :class="{ 'status-off': !addedService.isPack }">
+          {{ addedService.isPack ? '已开启' : '未开启' }}
+        </text>
+      </view>
+
+      <!-- 保价(两者共有) -->
+      <view class="info-row">
+        <text class="info-label">保价:</text>
+        <text class="info-value" :class="{ 'status-off': !addedService.guaranteeMoney }">
+          <template v-if="addedService.guaranteeMoney">
+            ¥{{ addedService.guaranteeMoney }}
+          </template>
+          <template v-else>未保价</template>
+        </text>
+      </view>
+
+      <!-- 签单返还(两者共有,但顺丰需显示具体类型) -->
+      <view class="info-row">
+        <text class="info-label">签单返还:</text>
+        <text class="info-value" :class="{ 'status-off': !addedService.isReceiptCollect }">
+          {{ signReturnDisplay }}
+        </text>
+      </view>
+
+      <!-- 超长超重(两者共有?) -->
+<!--      <view class="info-row">
+        <text class="info-label">超长超重:</text>
+        <text class="info-value" :class="{ 'status-off': !addedService.isOverLongWeight }">
+          {{ addedService.isOverLongWeight ? '已开启' : '未开启' }}
+        </text>
+      </view> -->
+
+      <!-- 打木架(仅顺丰) -->
+      <view class="info-row" v-if="orderType === 2">
+        <text class="info-label">打木架:</text>
+        <text class="info-value" :class="{ 'status-off': !addedService.isWoodenCrate }">
+          {{ addedService.isWoodenCrate ? '已开启' : '未开启' }}
+        </text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { computed } from 'vue'
+
+const props = defineProps({
+  orderDetail: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+// 解析 addedService 字段(可能为字符串或对象)
+const addedService = computed(() => {
+  if (!props.orderDetail.addedService) return {}
+  if (typeof props.orderDetail.addedService === 'string') {
+    try {
+      return JSON.parse(props.orderDetail.addedService) || {}
+    } catch (e) {
+      console.error('解析增值服务失败', e)
+      return {}
+    }
+  }
+  return props.orderDetail.addedService
+})
+
+const orderType = computed(() => props.orderDetail.orderType) // 1:京东, 2:顺丰
+
+// 签单返还显示文本
+const signReturnDisplay = computed(() => {
+  const value = addedService.value.isReceiptCollect
+  if (!value) return '未开启'
+
+  if (orderType.value === 2) {
+    // 顺丰凭证类型映射
+    const map = { 1: '运单原件', 2: '复印件', 3: '照片' }
+    return map[value] || '已开启'
+  }
+  return '已开启'
+})
+</script>
+
+<style scoped lang="scss">
+.info-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  padding: 20rpx;
+  margin-bottom: 20rpx;
+
+  .card-header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 16rpx;
+
+    image {
+      width: 35rpx;
+      height: 35rpx;
+    }
+
+    .card-title {
+      height: 48rpx;
+      line-height: 48rpx;
+      font-weight: bold;
+      font-size: 32rpx;
+      color: #333;
+      margin-left: 10rpx;
+    }
+  }
+
+  .card-body {
+    .info-row {
+      min-height: 44rpx;
+      line-height: 44rpx;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-top: 16rpx;
+
+      &:first-child {
+        margin-top: 0;
+      }
+
+      .info-label {
+        font-size: 28rpx;
+        color: #666666;
+      }
+
+      .info-value {
+        font-size: 28rpx;
+        color: #333;
+
+        &.price {
+          color: #FD5F3C;
+          font-weight: bold;
+        }
+
+        &.status-off {
+          color: #999; // 未开启状态颜色变淡
+        }
+      }
+    }
+  }
+}
+</style>

BIN
jd_logistics-app/static/img/mine/icon-mint-setting.png