Bläddra i källkod

feat:小程序所有页面UI

颜琼丽 1 dag sedan
förälder
incheckning
b257ca1dd1
43 ändrade filer med 11669 tillägg och 0 borttagningar
  1. 26 0
      jd_logistics-app/App.vue
  2. 20 0
      jd_logistics-app/index.html
  3. 45 0
      jd_logistics-app/main.js
  4. 72 0
      jd_logistics-app/manifest.json
  5. 18 0
      jd_logistics-app/package-lock.json
  6. 5 0
      jd_logistics-app/package.json
  7. 232 0
      jd_logistics-app/pages.json
  8. 192 0
      jd_logistics-app/pages/address/address_list.vue
  9. 434 0
      jd_logistics-app/pages/address/edit.vue
  10. 716 0
      jd_logistics-app/pages/ai/ai.vue
  11. 60 0
      jd_logistics-app/pages/ai/components/agentCheck.vue
  12. 145 0
      jd_logistics-app/pages/ai/components/messagesInfo.vue
  13. 72 0
      jd_logistics-app/pages/ai/components/messagesInfoDefault.vue
  14. 37 0
      jd_logistics-app/pages/ai/components/tools.vue
  15. 156 0
      jd_logistics-app/pages/historyList/historyList.vue
  16. 165 0
      jd_logistics-app/pages/index/components/PersonalExpressDialog.vue
  17. 273 0
      jd_logistics-app/pages/index/index.vue
  18. 146 0
      jd_logistics-app/pages/index111/index.vue
  19. 369 0
      jd_logistics-app/pages/logistics/index.vue
  20. 602 0
      jd_logistics-app/pages/mine/mine.vue
  21. 252 0
      jd_logistics-app/pages/mine/mine1.vue
  22. 370 0
      jd_logistics-app/pages/order/components/OrderItem.vue
  23. 152 0
      jd_logistics-app/pages/order/components/TrackPopup.vue
  24. 385 0
      jd_logistics-app/pages/order/index.vue
  25. 634 0
      jd_logistics-app/pages/order/order_detail.vue
  26. 71 0
      jd_logistics-app/pages/policy/ad_detail.vue
  27. 23 0
      jd_logistics-app/pages/policy/recharge_policy.vue
  28. 23 0
      jd_logistics-app/pages/policy/vip_policy.vue
  29. 144 0
      jd_logistics-app/pages/recharge/recharge.vue
  30. 98 0
      jd_logistics-app/pages/recharge/success_pay.vue
  31. 295 0
      jd_logistics-app/pages/recharge/vip.vue
  32. 100 0
      jd_logistics-app/pages/recharge/wallet.vue
  33. 209 0
      jd_logistics-app/pages/search/search.vue
  34. 110 0
      jd_logistics-app/pages/user/user.vue
  35. 60 0
      jd_logistics-app/pages/webView/webView.vue
  36. 13 0
      jd_logistics-app/uni.promisify.adaptor.js
  37. 77 0
      jd_logistics-app/uni.scss
  38. 3766 0
      jd_logistics-app/utils/area-data.js
  39. 227 0
      jd_logistics-app/utils/cache.js
  40. 78 0
      jd_logistics-app/utils/request.js
  41. 577 0
      jd_logistics-app/utils/util.js
  42. 175 0
      jd_logistics-app/utils/wsUtil.js
  43. 45 0
      jd_logistics-app/vite.config.js

+ 26 - 0
jd_logistics-app/App.vue

@@ -0,0 +1,26 @@
+<script setup>
+import { onShow, onLaunch, onHide } from "@dcloudio/uni-app"
+import { getLocation, getNavbarHeight, uniLogin, getUserInfo } from "@/utils/util.js";
+onLaunch((e)=>{
+	console.log('App1 Launch',e)
+	// 获取系统信息
+ 	getNavbarHeight();
+	
+	// getLocation(false);
+	// uniLogin();
+	getUserInfo();
+});
+onShow(()=>{
+	console.log('App1 onShow')
+});
+onHide(()=>{
+	console.log('App1 onHide')
+});
+
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import "@/uni_modules/uview-plus/index.scss";
+	@import "static/css/base.scss";
+</style>

+ 20 - 0
jd_logistics-app/index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 45 - 0
jd_logistics-app/main.js

@@ -0,0 +1,45 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+import './uni.promisify.adaptor'
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+  ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import * as Pinia from 'pinia';
+import uviewPlus, { setConfig  } from '@/uni_modules/uview-plus'
+
+
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  
+  app.use(Pinia.createPinia())
+
+  app.use(uviewPlus, () => {
+		return {
+			options: {
+				// 修改$u.config对象的属性
+				config: {
+					// 修改默认单位为rpx,相当于执行 uni.$u.config.unit = 'rpx'
+					unit: 'rpx',
+          // customIcon: {
+            // family: 'iconfont',
+            // url: '//at.alicdn.com/t/c/font_4946742_e8oa3t01rkk.css'
+          // }
+				}
+			}
+		}
+  })
+  return {
+    app
+  }
+}
+
+// #endif

+ 72 - 0
jd_logistics-app/manifest.json

@@ -0,0 +1,72 @@
+{
+    "name" : "lightning-app-cs",
+    "appid" : "__UNI__516F309",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wxdc3282b1876a4286",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "3"
+}

+ 18 - 0
jd_logistics-app/package-lock.json

@@ -0,0 +1,18 @@
+{
+  "name": "lightning-app-cs",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "dayjs": "^1.11.13"
+      }
+    },
+    "node_modules/dayjs": {
+      "version": "1.11.18",
+      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
+      "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
+      "license": "MIT"
+    }
+  }
+}

+ 5 - 0
jd_logistics-app/package.json

@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "dayjs": "^1.11.13"
+  }
+}

+ 232 - 0
jd_logistics-app/pages.json

@@ -0,0 +1,232 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "瑞鲸物流",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path" : "pages/mine/mine",
+			"style" : 
+			{
+				"navigationBarTitleText" : "我的",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path" : "pages/logistics/index",
+			"style" : 
+			{
+				"navigationBarTitleText" : "物流轨迹"
+				
+			}
+		},
+		{
+			"path" : "pages/search/search",
+			"style" : 
+			{
+				"navigationBarTitleText" : "",
+				"transparentTitle": "always",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path" : "pages/ai/ai",
+			"style" : 
+			{
+				"navigationBarTitleText" : "",
+				"transparentTitle": "always",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path" : "pages/mine/settlementCode",
+			"style" : 
+			{
+				"navigationBarTitleText" : "到付月结码"
+			}
+		}
+	],
+	"subPackages": [
+		{
+			"root": "pages/user",
+      "name": "user",
+			"pages": [
+				{
+					"path" : "user",
+					"style" : 
+					{
+						"navigationBarTitleText" : "个人资料"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/webView",
+      "name": "webView",
+			"pages": [
+				{
+					"path" : "webView",
+					"style" : 
+					{
+						"navigationBarTitleText" : "文章详情"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/historyList",
+      "name": "historyList",
+			"pages": [
+				{
+					"path" : "historyList",
+					"style" : 
+					{
+						"navigationBarTitleText" : "历史对话记录"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/recharge",
+      "name": "recharge",
+			"pages": [
+				{
+					"path" : "recharge",
+					"style" : 
+					{
+						"navigationBarTitleText" : "充值"
+					}
+				},
+				{
+					"path" : "vip",
+					"style" : 
+					{
+						"navigationBarTitleText" : "会员中心",
+						"navigationStyle": "custom",
+						"transparentTitle": "always"
+					}
+				},
+				{
+					"path" : "wallet",
+					"style" : 
+					{
+						"navigationBarTitleText" : "我的钱包"
+					}
+				},
+				{
+					"path" : "success_pay",
+					"style" : 
+					{
+						"navigationBarTitleText" : "支付结果"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/policy",
+      "name": "policy",
+			"pages": [
+				{
+					"path" : "recharge_policy",
+					"style" : 
+					{
+						"navigationBarTitleText" : "充值协议"
+					}
+				},
+				{
+					"path" : "vip_policy",
+					"style" : 
+					{
+						"navigationBarTitleText" : "VIP会员开通协议"
+					}
+				},
+				{
+					"path" : "ad_detail",
+					"style" : 
+					{
+						"navigationBarTitleText" : "详情"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/order",
+      "name": "order",
+			"pages": [
+				{
+					"path" : "index",
+					"style" : 
+					{
+						"navigationBarTitleText" : "订单列表"
+					}
+				},
+				{
+					"path" : "order_detail",
+					"style" : 
+					{
+						"navigationBarTitleText" : "订单详情"
+					}
+				},
+				{
+					"path" : "create_order",
+					"style" : 
+					{
+						"navigationBarTitleText" : "创建订单"
+					}
+				}
+			]
+		},
+		{
+			"root": "pages/address",
+      "name": "address",
+			"pages": [
+				{
+					"path" : "address_list",
+					"style" : 
+					{
+						"navigationBarTitleText" : "地址薄"
+					}
+				},
+				{
+					"path" : "edit",
+					"style" : 
+					{
+						"navigationBarTitleText" : "新增地址"
+					}
+				}
+			]
+		}
+	],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#fff",
+		"backgroundColor": "#F5F7FA"
+	},
+		"tabBar": {
+		"color": "#333333",
+		"selectedColor": "#1B64F0",
+		"borderStyle": "black",
+		"backgroundColor": "#ffffff",
+		"list": [{
+			"pagePath": "pages/index/index",
+			"selectedIconPath": "static/img/tabs/home_active.png",
+			"iconPath": "static/img/tabs/home.png",
+			"text": "首页"
+		},{
+			"pagePath": "pages/search/search",
+			"selectedIconPath": "static/img/tabs/search_active.png",
+			"iconPath": "static/img/tabs/search.png",
+			"text": "查快递"
+		},{
+			"pagePath": "pages/mine/mine",
+			"selectedIconPath": "static/img/tabs/user_active.png",
+			"iconPath": "static/img/tabs/user.png",
+			"text": "我的"
+		}]
+	},
+	"uniIdRouter": {}
+}

+ 192 - 0
jd_logistics-app/pages/address/address_list.vue

@@ -0,0 +1,192 @@
+<template>
+	<view class="address-page">
+		<!-- 地址列表 -->
+		<scroll-view class="address-list" scroll-y>
+			<!-- 地址项组件 -->
+			<address-item v-for="(item, index) in addressList" :key="item.id" :address="item"
+				:is-default="item.isDefault" @set-default="handleSetDefault(index)" @edit="handleEdit(index)"
+				@delete="handleDelete(index)" />
+		</scroll-view>
+
+		<!-- 添加新地址按钮 -->
+		<view class="add-btn-container">
+			<button class="add-btn" @click="handleAddAddress">
+				<text class="add-btn-text">添加新地址</text>
+			</button>
+		</view>
+
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref
+	} from 'vue'
+	import AddressItem from '@/components/AddressItem.vue'
+
+	// 模拟地址数据
+	const addressList = ref([{
+			id: 1,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路97号',
+			isDefault: true
+		},
+		{
+			id: 2,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		},
+		{
+			id: 3,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路97号',
+			isDefault: false
+		}
+	])
+
+	// 当前要操作的地址索引
+	const currentIndex = ref(-1)
+
+	// 设置默认地址
+	const handleSetDefault = (index) => {
+		addressList.value.forEach((item, i) => {
+			item.isDefault = i === index
+		})
+	}
+
+	// 编辑地址
+	const handleEdit = (index) => {
+		console.log('编辑地址:', index)
+		// 这里可以跳转到编辑页面
+		// uni.navigateTo({ url: '/pages/address/edit?id=' + addressList.value[index].id })
+	}
+
+	// 删除地址
+	const handleDelete = (index) => {
+		currentIndex.value = index
+		uni.showModal({
+			title: '确认删除',
+			content: '是否确认删除这个地址',
+			// showCancel:false,
+			success: function(res) {
+				if (res.confirm) {
+					confirmDelete()
+				} else if (res.cancel) {
+					console.log('用户点击取消');
+				}
+			}
+		});
+	}
+
+	// 确认删除
+	const confirmDelete = () => {
+		if (currentIndex.value >= 0) {
+			addressList.value.splice(currentIndex.value, 1)
+			currentIndex.value = -1
+		}
+	}
+
+	// 添加新地址
+	const handleAddAddress = () => {
+		console.log('添加新地址')
+		uni.navigateTo({
+			url: '/pages/address/edit'
+		})
+	}
+</script>
+
+<style scoped>
+	.address-page {
+		display: flex;
+		flex-direction: column;
+		height: 100vh;
+		background-color: #F5F7FA;
+	}
+
+	.address-list {
+		flex: 1;
+		padding: 20rpx 30rpx;
+		box-sizing: border-box;
+	}
+
+	.add-btn-container {
+		width: 100%;
+		position: fixed;
+		bottom: 0rpx;
+		padding: 32rpx;
+		background-color: #fff;
+		border-top: 1rpx solid #eee;
+	}
+
+	.add-btn {
+		height: 88rpx;
+		background: #1B64F0;
+		border-radius: 44rpx;
+	}
+
+	.add-btn-text {
+		color: #fff;
+		font-size: 32rpx;
+		font-weight: 500;
+		height: 88rpx;
+		line-height: 88rpx;
+	}
+
+	/* 按钮激活效果 */
+	.add-btn:active {
+		opacity: 0.8;
+	}
+</style>

+ 434 - 0
jd_logistics-app/pages/address/edit.vue

@@ -0,0 +1,434 @@
+<template>
+	<view class="container">
+
+		<!-- 物品信息 -->
+		<view class="form-card">
+
+			<view class="form-item">
+				<view class="item-label">
+					<input class="input-field" placeholder="姓名" placeholder-class="placeholder" v-model="formInfo" />
+				</view>
+				<view class="item-control">
+					<input class="input-field" placeholder="电话" placeholder-class="placeholder" v-model="formInfo" />
+				</view>
+			</view>
+			<view class="form-item" @click="showTimePicker = true">
+				<text class="time-value placeholder">省市区</text>
+				<text class="time-value value">{{ selectedTime }}</text>
+				<u-icon class="arrow" name='arrow-right' size="18"></u-icon>
+			</view>
+
+			<view class="form-item">
+				<view class="item-control">
+					<input class="input-field" placeholder="详细地址" placeholder-class="placeholder" v-model="formInfo" />
+				</view>
+			</view>
+
+
+			<view class="form-item">
+				<!-- 设置默认和清空 -->
+				<view class="form-actions">
+					<view class="action-left">
+						<switch color="#007AFF" @change="onDefaultChange" />
+						<text class="action-text">设为默认寄件地址</text>
+					</view>
+					<view class="action-right" @click="clearForm">
+						<text class="clear-text">清空</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+
+		<view class="section-title">最近使用地址</view>
+
+		<!-- 最近使用地址 -->
+		<view class="recent-address">
+			<!-- 地址项1 -->
+			<view class="address-item" @tap="selectAddress(0)">
+				
+					<AddressInfo v-if="addressSend.id" :address="addressSend" />
+				<view class="address-info">
+					<view class="address-name">袁添昊 13344642161</view>
+					<view class="address-detail">湖北省荆州市新石南路747号</view>
+				</view>
+			<!-- 	<view class="address-select">
+					
+				</view> -->
+			</view>
+
+			<!-- 地址项2 -->
+			<view class="address-item" @tap="selectAddress(1)">
+				<view class="address-info">
+					<view class="address-name">袁添昊 13344642161</view>
+					<view class="address-detail">湖北省荆州市新石南路747号</view>
+				</view>
+				<!-- <view class="address-select">
+					
+				</view> -->
+			</view>
+		</view>
+		<!-- 下单按钮 -->
+		<view class="submit-btn" @click="onConfirm">
+			确定
+		</view>
+
+		<!-- 安全区域占位 -->
+		<view class="safe-area"></view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		reactive
+	} from 'vue'
+	import {onLoad} from '@dcloudio/uni-app'
+	import AddressInfo from '@/components/AddressInfo.vue'
+	
+	const addType = ref('1') //1 表示从创建订单过来  2 表示编辑  3表示创建
+	
+	// 表单数据
+	const formData = reactive({
+		name: '',
+		phone: '',
+		region: [],
+		address: '',
+		isDefault: false
+	})
+	
+	// 最近地址列表
+	const recentAddresses = ref([{
+			name: '袁添昊',
+			phone: '13344642161',
+			address: '湖北省荆州市新石南路747号'
+		},
+		{
+			name: '袁添昊',
+			phone: '13344642161',
+			address: '湖北省荆州市新石南路747号'
+		}
+	])
+	
+	onLoad((option)=>{
+		
+	})
+
+
+	// 省市区选择
+	const onRegionChange = (e) => {
+		formData.region = e.detail.value
+	}
+
+	// 默认地址切换
+	const onDefaultChange = (e) => {
+		formData.isDefault = e.detail.value
+	}
+
+	// 清空表单
+	const clearForm = () => {
+		formData.name = ''
+		formData.phone = ''
+		formData.region = []
+		formData.address = ''
+		formData.isDefault = false
+	}
+
+	// 选择地址
+	const selectAddress = (index) => {
+		const address = recentAddresses.value[index]
+		formData.name = address.name
+		formData.phone = address.phone
+		formData.address = address.address
+	}
+
+	// 确定提交
+	const onConfirm = () => {
+		// 这里可以添加表单验证
+		if (!formData.name || !formData.phone || !formData.address) {
+			uni.showToast({
+				title: '请填写完整信息',
+				icon: 'none'
+			})
+			return
+		}
+
+		// 提交逻辑
+		console.log('提交数据:', formData)
+		uni.showToast({
+			title: '提交成功',
+			icon: 'success'
+		})
+
+		// 返回上一页或执行其他操作
+		// uni.navigateBack()
+	}
+</script>
+
+<style scoped lang="less">
+	.container {
+		background-color: #F5F7FA;
+		min-height: 100vh;
+		padding: 20rpx 20rpx 30rpx 20rpx;
+	}
+
+	.form-card {
+		background-color: #fff;
+		border-radius: 32rpx;
+		padding: 0rpx 20rpx;
+	}
+
+	.form-title {
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #333;
+		margin-bottom: 32rpx;
+	}
+
+
+	.form-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		height: 100rpx;
+		border-bottom: 1rpx solid #F1F3F8;
+
+		&:last-child {
+			border-bottom: none;
+			margin-bottom: 0;
+		}
+
+		.item-label {
+			font-size: 28rpx;
+			color: #666;
+			font-weight: 400;
+
+			&.required::before {
+				content: '*';
+				color: #ff4444;
+				margin-right: 8rpx;
+			}
+		}
+
+		.item-control {
+			display: flex;
+			align-items: center;
+
+			&.btn {
+				background: #fff;
+				border: 1rpx solid #dcdfe6;
+				border-radius: 5rpx;
+				height: 59rpx;
+				width: 266rpx;
+				box-sizing: border-box;
+			}
+
+		}
+
+		.input-field {
+			width: 100%;
+			height: 100rpx;
+			line-height: 100rpx;
+			padding: 0 24rpx;
+			font-size: 28rpx;
+			color: #333;
+			text-align: left;
+
+
+			&::placeholder {
+				color: #999;
+			}
+		}
+
+		.time-value {
+			display: flex;
+			align-items: center;
+			font-size: 28rpx;
+			color: #333;
+			line-height: 88rpx;
+			height: 88rpx;
+			padding: 0 24rpx;
+
+			&.placeholder {
+				color: #999;
+			}
+
+			.value {
+				margin-right: 16rpx;
+			}
+
+			.arrow {
+				margin-left: 32rpx;
+				color: #999;
+				font-size: 28rpx;
+				font-weight: bold;
+			}
+		}
+	}
+
+	/* 寄件标识 */
+	.send-tag {
+		padding: 40rpx 30rpx 20rpx;
+		background-color: #fff;
+	}
+
+	.send-circle {
+		width: 60rpx;
+		height: 60rpx;
+		border-radius: 50%;
+		background-color: #007AFF;
+		color: #fff;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 28rpx;
+		font-weight: bold;
+	}
+
+	/* 表单区域 */
+	.form-container {
+		background-color: #fff;
+		padding: 0 30rpx;
+		margin-bottom: 20rpx;
+	}
+
+	.form-title {
+		font-size: 18px;
+		font-weight: 500;
+		color: #333;
+		padding: 30rpx 0 20rpx;
+		border-bottom: 1rpx solid #eee;
+	}
+
+	.form-item {
+		width: 100%;
+		padding: 30rpx 0;
+		border-bottom: 1rpx solid #eee;
+	}
+
+	.form-input {
+		font-size: 16px;
+		height: 40rpx;
+		line-height: 40rpx;
+		color: #333;
+	}
+
+	.form-input::placeholder {
+		color: #999;
+	}
+
+	.picker-placeholder {
+		font-size: 16px;
+		height: 40rpx;
+		line-height: 40rpx;
+		color: #999;
+	}
+
+	.picker-placeholder.has-value {
+		color: #333;
+	}
+
+	/* 表单操作 */
+	.form-actions {
+		width: 100%;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 30rpx 0;
+	}
+
+	.action-left {
+		display: flex;
+		align-items: center;
+	}
+
+	.action-text {
+		font-size: 28rpx;
+		color: #333;
+		margin-left: 10rpx;
+	}
+
+	.action-right {
+		padding: 10rpx 20rpx;
+	}
+
+	.clear-text {
+		height: 44rpx;
+		font-weight: 400;
+		font-size: 28rpx;
+		color: #1B64F0;
+		line-height: 44rpx;
+	}
+
+	/* 最近地址 */
+	.recent-address {
+		background-color: #fff;
+		padding: 0 30rpx;
+	}
+
+	.section-title {
+		height: 48rpx;
+		font-size: 32rpx;
+		color: #333333;
+		line-height: 48rpx;
+		margin: 20rpx 0rpx;
+		font-weight: bold;
+	}
+
+	.address-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 30rpx 0;
+		border-bottom: 1rpx solid #eee;
+	}
+
+	.address-info {
+		flex: 1;
+	}
+
+	.address-name {
+		font-size: 16px;
+		color: #333;
+		margin-bottom: 10rpx;
+	}
+
+	.address-detail {
+		font-size: 14px;
+		color: #666;
+		line-height: 1.4;
+	}
+
+	.address-select {
+		width: 40rpx;
+		height: 40rpx;
+		border: 1rpx solid #007AFF;
+		border-radius: 50%;
+		margin-left: 20rpx;
+	}
+
+
+	.safe-area {
+		height: 140rpx;
+	}
+
+	.submit-btn {
+		position: fixed;
+		bottom: 40rpx;
+		left: 30rpx;
+		right: 30rpx;
+		width: 686rpx;
+		height: 88rpx;
+		background: #1B64F0;
+		border-radius: 44rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+
+		font-size: 32rpx;
+		color: #FFFFFF;
+		line-height: 88rpx;
+		text-align: center;
+		z-index: 10;
+	}
+</style>

+ 716 - 0
jd_logistics-app/pages/ai/ai.vue

@@ -0,0 +1,716 @@
+<template>
+  <view class="chat-container"  :style="{ 
+    paddingTop: appStore.navbarHeight + 'px',
+  }">
+		<view class="mine_ybt_title flex-center bg_color_fff" 
+		:style="{ height: appStore.navbarHeight + 'px',
+    paddingTop: appStore.statusBarHeight + 'px'}"
+		>
+			<!-- <text class="font_size35 bold">AI客服</text> -->
+     <agentCheck ref="agentCheckRef"></agentCheck>
+		</view>
+    <!-- 聊天消息区域 -->
+    <view class="chat-messages flex-column">
+			<!-- 工具栏 -->
+			<tools @addHuiHuaFn="addHuiHuaFn"></tools>
+			<!-- 消息列表 -->
+      <scroll-view
+        class="scrollViewRef flex_1"
+        scroll-y="true" 
+        :scroll-top="scrollTop"
+        ref="scrollViewRef"
+        :scroll-with-animation="true"
+        @refresherrefresh="handlePullDownRefresh"
+        :refresher-enabled="true"
+        :refresher-triggered="triggered"
+      >
+        <messagesInfoDefault v-if="messages.length==0" 
+        @sendMessage="sendMessage"
+        ref="messagesInfoDefaultRef" key="messagesInfoDefaultRef">
+        </messagesInfoDefault>
+        <messagesInfo 
+        :messages="messages" 
+        :isLoading="isLoading" 
+        @imageLoaded="scrollToBottom"
+        :id="`msg-${messagesKey}`">
+        </messagesInfo>
+      </scroll-view>
+    </view>
+    <!-- 输入区域 -->
+    <view class="input-area">
+      <button class="image-btn" :disabled="isLoading" @click.stop.prevent="uploadImage">
+        <image class="image-icon" src="/static/img/service/tupian.png" model="aspectFit"></image>
+      </button>
+      <button v-if="isvoice"
+        class="record-btn"
+        :class="{ recording: isRecording }"
+        @touchstart="startRecord"
+        @touchend="stopRecord"
+        @touchmove="handleTouchMove"
+        @touchcancel="cancelRecord"
+        :disabled="isRecording && isCancel"
+      >
+        <view v-if="!isRecording">按住说话</view>
+        <view v-if="isRecording && !isCancel">松手发送,上移取消</view>
+        <view v-if="isRecording && isCancel">松手取消</view>
+        <text v-if="recordDuration>0"></text>
+      </button>
+      <input v-else
+        class="input-box"
+        :placeholder="isLoading?'努力回答中...':'发消息或按住说话'"
+        placeholder-style="color: #999"
+        v-model.trim="inputText"
+        :disabled="isLoading"
+        @confirm="sendMessage({chatType:0,msgContent:inputText})"
+      />
+      <button class="isvoice-btn" @click.stop.prevent="isvoice=!isvoice;authorizeRecord()" :disabled="isLoading">
+        <image class="mic-icon" src="/static/img/service/xiaoxi.png" v-if="isvoice" model="aspectFit"></image>
+        <image class="mic-icon" src="/static/img/service/maikefengyuyin.png" v-else model="aspectFit"></image>
+      </button>
+      <button class="send-btn" @click.stop.prevent="sendMessage({chatType:0,msgContent:inputText})" :disabled="isLoading" v-if="!isvoice">
+        <image class="send-icon" src="/static/img/service/send-icon.png"></image>
+      </button>
+      <view class="ai-tip">内容由AI生成,仅供参考</view>
+    </view>
+     <!-- 录音动画/提示 -->
+      <view 
+        class="record-toast" 
+        v-if="isRecording"
+        :class="{ cancel: isCancel }"
+      >
+        <image 
+          v-if="!isCancel"
+          src="/static/img/voice/recording.gif" 
+          class="toast-icon"
+          alt="录音中动画"
+        ></image>
+        <image 
+          v-if="isCancel"
+          src="/static/img/voice/cancel.png" 
+          class="toast-icon"
+          alt="取消录音图标"
+        ></image>
+        <text v-if="!isCancel">正在录音...({{60-recordDuration}})</text>
+        <text v-if="isCancel">松手取消发送</text>
+      </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, nextTick, watch } from 'vue';
+import { onShow,onHide,onLoad } from "@dcloudio/uni-app"
+import { chatHistoryDetails } from '@/api/ai.js';
+import messagesInfo from "./components/messagesInfo.vue";
+import messagesInfoDefault from "./components/messagesInfoDefault.vue";
+import tools from "./components/tools.vue";
+import agentCheck from "./components/agentCheck.vue";
+import {
+	HTTP_REQUEST_URL,
+	HTTP_REQUEST_URL_WS,
+  TOKENNAME
+} from '@/config/app';
+import { chooseImageOne,checkLoginShowModal,checkAiQuotaDailyModal,getUserInfo } from "@/utils/util.js";
+// 封装的websocket
+import WSClient from '@/utils/wsUtil.js';
+import { useAppStore } from '@/stores/app'
+import { useToast } from '@/hooks/useToast'
+import dayjs from "dayjs";
+const appStore = useAppStore();
+const { Toast } = useToast();
+const agentCheckRef = ref(null);
+const messagesInfoDefaultRef = ref(null);
+const newSession = ref(false);//是否新会话
+const historySession = ref('');//是否新会话
+
+// 添加滚动相关变量
+const scrollTimeout = ref(null);
+const scrollViewRef = ref(null);
+const scrollTop = ref(0);
+const messagesKey = ref(0);
+const triggered = ref(false);
+
+// 底部发送功能
+const inputText = ref('');
+const isLoading = ref(false);//思考中
+const isvoice = ref(false);//是否语音输入
+const wsClient = ref(null);
+
+// 模拟聊天消息数据
+const messages = ref([]);
+
+watch(() => appStore.agentId, (state) => {
+  messages.value = [];
+  getAdSearchFn()
+});
+
+onLoad((e)=>{
+  console.log('ai onLoad',e);
+})
+onShow(async()=>{
+  if(!await checkLoginShowModal())return;
+  nextTick(async ()=>{
+    // 初始化选择智能体
+    await agentCheckRef.value.initAgentId();
+     // 初始化完成后获取 agentId
+    console.log('agentId:', agentCheckRef.value.agentId);
+    if(appStore.msgContent){
+      sendMessage({chatType:0,msgContent:appStore.msgContent});
+      appStore.UPDATE_msgContent('');
+    }else if(appStore.sessionId){
+      handlePullDownRefresh();
+    }else{
+      // getAdSearchFn()
+    }
+    
+  })
+
+	aiStartChatFn();
+});
+onHide(()=>{
+  isLoading.value = false;
+  cleanupResources();
+});
+// 新增:添加回话功能
+function addHuiHuaFn(){
+  newSession.value = true;
+  messages.value = [];
+  getAdSearchFn();
+}
+function getAdSearchFn(){
+  nextTick(()=>{
+    setTimeout(()=>{
+      messagesInfoDefaultRef.value.getAdSearchFn();
+    },50)
+  })
+}
+
+function authorizeRecord() { 
+    uni.authorize({
+    scope: 'scope.record',
+    success() {
+      // uni.getRecorderManager();
+    }
+  })
+}
+function aiStartChatFn(){ 
+  cleanupResources();
+  wsClient.value = new WSClient({
+    url: `${HTTP_REQUEST_URL_WS}/api/websocket`,
+    method: 'POST',
+    headers: {
+      "Authorization": 'Bearer '+appStore.token
+    }
+  });
+  // 注册SSE事件
+  registerEvents();
+  wsClient.value.open({type:0,message:inputText.value})
+}
+
+// 
+async function sendMessage({chatType,msgContent=""}){
+  console.log('sendMessage',wsClient.value);
+  // 登录检测
+  if(!await checkLoginShowModal())return;
+  // 今日免费提问次数检测
+  if(!await checkAiQuotaDailyModal())return;
+  if (!msgContent) {
+    Toast({title:'请输入内容'});
+    return;
+  }
+  isLoading.value = true
+  messages.value.push({
+    msgContent,
+    chatType,// 0 (文本),1 (图片)
+    speakerType:0//0用户消息1AI消息
+  });
+  messages.value.push({
+    msgContent: "",
+    event: "start",//sse长链接的返回状态
+    chatType,
+    speakerType:1,
+    messageTimeNow:dayjs().format('HH:mm')
+  });
+  //type  0 (文本),1 (图片)
+  wsClient.value.send({
+    type:chatType,
+    message:msgContent,
+    agentId:agentCheckRef.value.agentId,
+    useBalance:true,
+    newSession:newSession.value,
+    historySession:historySession.value
+  });
+  messagesKey.value++;
+  inputText.value = '';
+  newSession.value = false;
+  // 每次收到新消息时滚动到底部
+  // 如果是图片,子组件里图片加载成功后触发
+  if(chatType!=1)scrollToBottom();
+}
+async function uploadImage(){
+  if(!await checkLoginShowModal())return;
+  const res = await chooseImageOne();
+  console.log('uploadImage',res);
+  if(res){
+    // userInfo.value[key] = res.fileName
+    sendMessage({chatType:1,msgContent:res.data})
+  }
+}
+function cleanupResources(closeSSE=true) {
+  // 关闭SSE连接
+  if (wsClient.value) {
+    // 移除所有事件监听
+    if (wsClient.value.callbacks) {
+      Object.keys(wsClient.value.callbacks).forEach(event => {
+        wsClient.value.callbacks[event] = [];
+      });
+    }
+    wsClient.value.close('clean');
+    wsClient.value = null;
+  }
+}
+function registerEvents(){
+  wsClient.value.on('open', (data) => {
+    console.log('ws连接成功',data);
+  });
+  wsClient.value.on('answer', (data) => {
+    console.log('answer',data.segment,messages.value.length - 1);
+    isLoading.value = false;
+    const lastIndex = messages.value.length - 1;
+    // 保存完整内容(累加片段)
+    // const newFullAnswer = messages.value[lastIndex].msgContent + data.segment;
+    messages.value[lastIndex].msgContent = messages.value[lastIndex].msgContent + data.segment;
+    messagesKey.value++;
+    console.log('answer',messages);
+    // 每次收到新消息时滚动到底部
+    scrollToBottom();
+    nextTick(()=>{
+      // 获取用户信息
+      getUserInfo();
+    })
+  });
+  wsClient.value.on('error', (err) => {
+    isLoading.value = false;
+    console.log('error',err);
+    // aiStartChatFn();
+    Toast({ title: JSON.stringify(err) || "服务器异常" });
+  });
+  wsClient.value.on('close', (data) => {
+    isLoading.value = false;
+    let lastIndex = messages.value.length - 1;
+    console.log('close',data,lastIndex);
+    // 判断关闭原因,取消,删除当前聊天内容,超时提示请求超时
+    switch (data.reason) {
+      case 'timeout': {
+        // 提示超时
+        // uni.showToast({
+        //   title: '请求超时,请重试',
+        //   icon: 'none'
+        // });
+        if(lastIndex>-1)messages.value[lastIndex].msgContent = messages.value[lastIndex].msgContent || "请求超时,请重试";
+        aiStartChatFn();
+        break;
+      }
+      default:
+        break;
+    }
+  });
+}
+
+// 新增:滚动到底部的方法
+function scrollToBottom() {
+  nextTick(() => {
+    // if (scrollTimeout.value) return;
+    // scrollTimeout.value = setTimeout(() => {
+      uni.createSelectorQuery()
+      .select(`#msg-${messagesKey.value}`)
+      .boundingClientRect(rect => {
+        if (rect) {
+          scrollTop.value = rect.height;
+          // 使用更平滑的滚动方式
+          uni.pageScrollTo({
+            scrollTop: rect.bottom,
+            duration: 100
+          })
+        }
+      })
+      .exec();
+      // scrollTimeout.value = null;
+    // }, 80); // 延长节流间隔至80ms,减少冲突
+  });
+}
+
+/*******************************************历史数据********************************************************/ 
+
+const hitstoryParams = ref({
+  pageNum: 0,
+  pageSize: 10,
+  sessionId:appStore.sessionId,
+});
+// 重命名方法,避免与小程序生命周期冲突
+function handlePullDownRefresh() {
+  console.log('下拉刷新触发');
+  triggered.value = true;
+  hitstoryParams.value.pageNum++;
+  chatHistoryDetailsFn();
+}
+function chatHistoryDetailsFn(){
+  hitstoryParams.value.sessionId = appStore.sessionId;
+  chatHistoryDetails(hitstoryParams.value).then(res=>{ 
+    triggered.value = false;
+    console.log('chatHistoryFn',res);
+    const rows = res?.rows || [];
+    if(rows.length==0 && hitstoryParams.value.pageNum>0){
+      hitstoryParams.value.pageNum--;
+      // Toast({title:'没有更多历史记录了'});
+      return;
+    }
+    if(messages.value.length==res.total){
+      Toast({title:'没有更多历史记录了'});
+      return;
+    }
+    messages.value = [...rows, ...messages.value]
+  })
+}
+
+
+/*******************************************语音功能********************************************************/ 
+
+const isRecording = ref(false);// 是否正在录音
+const isCancel = ref(false);         // 是否取消录音
+const recordDuration = ref(0);       // 录音时长(秒)
+const tempFilePath = ref('');        // 录音临时文件路径
+const recordTimer = ref(null);       // 录音计时定时器
+const recorderManager = uni.getRecorderManager();  // 录音管理
+// 开始录音(触摸开始)
+const startRecord = async (e) => {
+  if(!await checkLoginShowModal())return;
+  // 防止冒泡导致的异常
+  e.stopPropagation();
+  
+  // 初始化录音参数
+  const options = {
+    format: 'mp3',       // 录音格式
+    sampleRate: 44100,   // 采样率
+    numberOfChannels: 1, // 声道数
+    encodeBitRate: 96000 // 编码比特率
+  };
+
+  // 开始录音
+  recorderManager.start(options);
+  isRecording.value = true;
+  isCancel.value = false;
+  recordDuration.value = 0;
+
+  // 计时逻辑
+  recordTimer.value = setInterval(() => {
+    recordDuration.value++;
+    // 限制最大录音时长(如60秒)
+    if (recordDuration.value >= 60) {
+      stopRecord();
+    }
+  }, 1000);
+
+  // 监听录音错误
+  recorderManager.onError((err) => {
+    console.error('录音错误:', err);
+    cancelRecord();
+    uni.showToast({ title: '录音失败', icon: 'none' });
+  });
+};
+
+// 停止录音(触摸结束)
+const stopRecord = () => {
+  if (!isRecording.value) return;
+
+  // 清除计时
+  clearInterval(recordTimer.value);
+  
+  // 停止录音
+  recorderManager.stop();
+  recorderManager.onStop((res) => {
+    tempFilePath.value = res.tempFilePath;
+    console.log('recorderManager',res.tempFilePath);
+    
+    // 判断是否取消或录音过短
+    if (isCancel.value) {
+      uni.showToast({ title: '已取消发送', icon: 'none' });
+    } else if (recordDuration.value < 1) {
+      uni.showToast({ title: '录音时间太短', icon: 'none' });
+    } else {
+      // 上传录音并添加到列表
+      uploadVoice(res.tempFilePath);
+    }
+    // 重置状态
+    resetRecordState();
+  });
+
+ 
+};
+
+// 处理触摸移动(用于判断是否取消)
+const handleTouchMove = (e) => {
+  if (!isRecording.value) return;
+
+  // 获取按钮位置和触摸位置
+  const buttonRect = uni.createSelectorQuery().select('.record-btn').boundingClientRect();
+  buttonRect.exec((rects) => {
+    const rect = rects[0];
+    if (!rect) return;
+
+    // 计算触摸点与按钮的垂直距离(向上移动超过50px视为取消)
+    const touchY = e.touches[0].clientY;
+    const buttonTop = rect.top;
+    if (touchY < buttonTop - 50) {
+      isCancel.value = true;
+    } else {
+      isCancel.value = false;
+    }
+  });
+};
+
+// 取消录音(触摸中断)
+const cancelRecord = () => {
+  if (!isRecording.value) return;
+  
+  clearInterval(recordTimer.value);
+  recorderManager.stop();
+  resetRecordState();
+  uni.showToast({ title: '已取消发送', icon: 'none' });
+};
+
+// 重置录音状态
+const resetRecordState = () => {
+  isRecording.value = false;
+  isCancel.value = false;
+  recordDuration.value = 0;
+  tempFilePath.value = '';
+};
+
+// 上传录音到服务器
+const uploadVoice = (filePath) => {
+  if (!filePath) return;
+
+  uni.showLoading({ title: '发送中...' });
+  
+  // 调用上传接口
+  uni.uploadFile({
+    url: `${HTTP_REQUEST_URL}/mini/chat/file/voiceUpload`, // 替换为你的后端接口
+    filePath,
+    name: 'file',        // 后端接收文件的参数名
+    formData: {
+      duration: recordDuration.value // 携带录音时长
+    },
+    method: 'POST',
+    header: {
+      "Authorization": appStore.token
+    },
+    success: (res) => {
+      console.log('上传结果:', res);
+      const result = JSON.parse(res.data);
+      if (result.code === 200) {
+        // 上传成功,添加到消息列表
+        sendMessage({ chatType:0,msgContent:result.data });
+        // messages.value.push({
+        //   id: Date.now(),
+        //   isMine: true,
+        //   avatar: '/static/avatar/user.png',
+        //   duration: recordDuration.value,
+        //   url: result.data.url, // 服务器返回的音频地址
+        //   isPlaying: false
+        // });
+      } else {
+        uni.showToast({ title: '发送失败', icon: 'none' });
+      }
+    },
+    fail: (err) => {
+      console.error('语音上传失败:', err);
+      uni.showToast({ title: '发送失败', icon: 'none' });
+    },
+    complete: () => {
+      uni.hideLoading();
+    }
+  });
+};
+</script>
+
+<style scoped lang="scss">
+.mine_ybt_title{
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  z-index: 1;
+}
+/* 录音区域 */
+.record-btn{
+  flex: 1;
+  height: 70rpx;
+  line-height: 70rpx;
+  font-size: 30rpx;
+  color: #333;
+
+}
+.record-area {
+  padding: 30rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.record-btn {
+  flex: 1;
+  height: 70rpx;
+  line-height: 70rpx;
+  font-size: 30rpx;
+  color: #333;
+  background-color: #f2f2f2;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+}
+
+.record-btn.recording {
+  background-color: #ff4d4f;
+  color: #fff;
+}
+
+.record-btn.cancel {
+  background-color: #999;
+}
+/* 整体容器样式 */
+.chat-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  // padding-bottom: 200rpx;
+  /* background-color: #f5f5f5; */
+}
+
+/* 聊天消息区域样式 */
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  .scrollViewRef{
+    // height: 100%;
+     padding: 16rpx;
+     overflow-y: auto;
+  }
+  // height: calc(100vh - 200rpx);
+}
+
+
+
+/* 输入区域样式 */
+.input-area {
+  // position: fixed;
+  // left: 0;
+  // bottom: 0;
+  // width: 100%;
+  display: flex;
+  align-items: center;
+  background-color: #fff;
+  padding: 30rpx 40rpx;
+  border-top: 1rpx solid #e0e0e0;
+  position: relative;
+}
+.ai-tip{
+  position: absolute;
+  bottom: 5rpx;
+  width: calc(100% - 80rpx);
+  text-align: center;
+  font-size: 20rpx;
+  color: #999;
+}
+
+/* 图片图标样式 */
+.image-btn{
+  margin-right: 30rpx;
+  width: 40rpx;
+  height: 40rpx;
+  &[disabled]{
+    .image-icon{
+      opacity: 0.4;
+    }
+  }
+  .image-icon {
+    width: 100%;
+    height:100%;
+  }
+}
+
+/* 输入框样式 */
+.input-box {
+  flex: 1;
+  height: 70rpx;
+  font-size: 30rpx;
+  color: #333;
+  background-color: transparent;
+}
+
+/* 麦克风图标样式 */
+.isvoice-btn{
+  width: 40rpx;
+  height: 40rpx;
+  margin: 0 40rpx;
+  &[disabled]{
+    .mic-icon{
+      opacity: 0.4;
+    }
+  }
+  .mic-icon {
+    width: 100%;
+    height:100%;
+  }
+}
+
+/* 发送按钮样式 */
+.send-btn {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0;
+  &[disabled]{
+    .send-icon{
+      opacity: 0.4;
+    }
+  }
+}
+
+/* 发送图标样式 */
+.send-icon {
+  width: 100%;
+  height: 100%;
+}
+
+/* 录音提示 */
+.record-toast {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 300rpx;
+  height: 300rpx;
+  border-radius: 20rpx;
+  background-color: rgba(0, 0, 0, 0.7);
+  color: #fff;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+}
+
+.record-toast.cancel {
+  background-color: rgba(255, 77, 79, 0.8);
+}
+
+.toast-icon {
+  width: 200rpx;
+  height: 200rpx;
+  margin-bottom: 20rpx;
+}
+</style>

+ 60 - 0
jd_logistics-app/pages/ai/components/agentCheck.vue

@@ -0,0 +1,60 @@
+<template>
+ 	<view class="agentCheck">
+    <u-dropdown class="up-dropdown">
+      <u-dropdown-item v-model="agentId" @change="changeAgentId" :title="agentName" :options="agentList"></u-dropdown-item>
+    </u-dropdown>
+  </view>
+</template>
+<script setup>
+import { ref, computed } from "vue";
+import { useAppStore } from "@/stores/app";
+import { getAgentList } from "@/api/home.js";
+import { onLoad } from '@dcloudio/uni-app'
+const appStore = useAppStore();
+const agentId = ref('');
+const agentList = ref([]);
+defineExpose({
+  initAgentId,
+  agentId
+});
+
+//计算属性
+const agentName = computed(()=>{
+  return agentList.value.find(item=>item.value == agentId.value)?.label || '';
+})
+onLoad(()=>{
+  // agentListFn();
+});
+async function agentListFn(){
+	await getAgentList().then(res=>{
+		if(res.code == 200){
+      let rows = res.rows || [];
+			agentList.value = rows.map(item=>{
+        return {
+          label: item.agentName,
+          value: item.agentId
+        }
+      });
+		}
+	})
+}
+async function initAgentId(){
+  await agentListFn();
+  console.log('initAgentId',agentList.value,appStore.agentId);
+  agentId.value = appStore.agentId || agentList.value[0].value;
+  appStore.agentId = agentId.value;
+}
+function changeAgentId(){
+  console.log('changeAgentId',agentId.value);
+  appStore.agentId = agentId.value;
+  appStore.sessionId = '';
+}
+
+</script>
+<style lang="scss" scoped>
+.agentCheck{
+  width: 100vw;
+  position: relative;
+  z-index: 20;
+}
+</style>

+ 145 - 0
jd_logistics-app/pages/ai/components/messagesInfo.vue

@@ -0,0 +1,145 @@
+<template>
+<view class="messagesInfo">
+  <view 
+      v-for="(msg, index) in props.messages" 
+      :key="index" 
+      :class="['message-item', msg.speakerType==0? 'is-mine' : '']"
+    >
+      <image v-if="msg.speakerType==1"
+        src="/static/img/service/aijiqiren.png" 
+        class="message-avatar"
+        mode="aspectFill"
+      />
+      <view class="message-content">
+        <view class="typing-indicator" 
+        v-if="props.isLoading && msg.speakerType==1 && index== props.messages.length-1"
+        >
+          <view class="dot"></view>
+          <view class="dot"></view>
+          <view class="dot"></view>
+        </view>
+        <!-- 0用户消息1AI消息 -->
+        <view v-if="msg.speakerType==0">
+          <!-- 0 (文本),1 (图片) -->
+          <image v-if="msg.chatType==1"
+            :src="msg.msgContent" 
+            class="content-image"
+            mode="widthFix"
+            @load="onImageLoad"
+            @error="onImageLoad"
+            
+          />
+          <text v-else>{{ msg.msgContent }}</text>
+        </view>
+        <zero-markdown-view v-else
+          :markdown="msg.msgContent" 
+          themeColor="#000000" 
+          :aiMode='false' 
+        >
+        </zero-markdown-view>
+        <view v-if="msg.messageTime" class="gray font_size20">{{msg.messageTime}}</view>
+        <view v-else-if="msg.speakerType==1 && msg.messageTimeNow" class="gray font_size20 text_align_right">
+          {{props.isLoading && index== props.messages.length-1?'':msg.messageTimeNow}}
+        </view>
+      </view>
+      <image v-if="msg.speakerType==0"
+        :src="appStore?.userInfo?.userAvatar || appStore.logo || '/static/img/service/user-avatar.png'" 
+        class="message-avatar mine-avatar"
+        mode="aspectFill"
+      />
+    </view>
+</view>
+</template>
+<script setup>
+import { ref } from 'vue'
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+const emit = defineEmits(['imageLoaded'])
+const props = defineProps({
+  messages: {
+    type: Array,
+    default: [],
+  },
+  isLoading: {
+    type: Boolean,
+    default: false,
+  }
+});
+function onImageLoad() {
+  emit('imageLoaded')
+}
+</script>
+<style lang="scss" scoped>
+.messagesInfo{
+  .content-image{
+    width: 200rpx;
+  }
+    /* 单个消息样式 */
+  .message-item {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 16rpx;
+  }
+
+  /* 用户自己发送的消息样式 */
+  .is-mine {
+    justify-content: flex-end;
+  }
+
+  /* 头像样式 */
+  .message-avatar {
+    width: 60rpx;
+    height: 60rpx;
+    border-radius: 50%;
+    margin-right: 16rpx;
+    &.mine-avatar{
+      margin-left: 16rpx;
+      margin-right: 0;
+    }
+  }
+
+  /* 消息内容样式 */
+  .message-content {
+    max-width: 70%;
+    min-width: 100rpx;
+    padding: 16rpx;
+    border-radius: 16rpx;
+    background-color: #fff;
+    box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
+    position: relative;
+  }
+
+  /* 用户自己发送的消息内容样式 */
+  .is-mine.message-content {
+    background-color: #00c853;
+    color: #fff;
+  }
+    /* 打字指示器(加载动画) */
+  .typing-indicator {
+    // position: absolute;
+    // top: 20rpx;
+    // left: 20rpx;
+    display: flex;
+    align-items: center;
+    gap: 8rpx;
+    padding: 5rpx 0;
+  }
+  /* 加载动画关键帧 */
+  @keyframes typing {
+    0% { transform: scale(0); }
+    40% { transform: scale(1); }
+    80% { transform: scale(0); }
+    100% { transform: scale(0); }
+  }
+
+  /* 加载动画的点 */
+  .dot {
+    width: 12rpx;
+    height: 12rpx;
+    background-color: #666;
+    border-radius: 50%;
+    animation: typing 1.4s infinite ease-in-out both;
+  }
+
+}
+</style>

+ 72 - 0
jd_logistics-app/pages/ai/components/messagesInfoDefault.vue

@@ -0,0 +1,72 @@
+<template>
+ <view class=" messagesInfoDefault">
+  <view class="flex_1 flex-column-center">
+    <image 
+      src="/static/img/service/aijiqiren.png" 
+      class="service-avatar"
+      mode="widthFix"
+    />
+    <view class="bold font_size40 mt20">Hi~,我是财税小助手!</view>
+  </view>
+  <view class="padding30 border_radius_20 bg_color_fff mt20" v-if="adSearchList.length>0">
+    <view class="font_size30 bold">AI赋能,专业解答财税问题</view>
+    <view class="font_size25 mt20 padding20 bg_color_f5 border_radius_20"
+     @click="sendMessage(item.dictValue)"
+    v-for="(item,index) in adSearchList" :key="index">
+      #{{item.dictValue}}
+    </view>
+    <view class="flex-center mt20">
+      <view class="flex-center-between gray" @click="getAdSearchFn">
+        <img src="/static/img/shuaxin.png" alt="" class="mr20" style="width:40rpx;height:40rpx;"/>
+        <text>换一批</text>
+      </view>
+    </view>
+  </view>
+  <!-- <view class="gray mt20 font_size25 flex-center">( 下拉获取历史数据 )</view> -->
+</view>
+</template>
+<script setup>
+import { ref,watch,nextTick } from "vue";
+import { getAdSearch } from "@/api/home";
+import { useAppStore } from "@/stores/app";
+import { onLoad } from '@dcloudio/uni-app'
+const appStore = useAppStore();
+const adSearchList = ref([]);
+const emit = defineEmits(['sendMessage']);
+
+defineExpose({
+  getAdSearchFn
+});
+
+// watch(() => appStore.agentId, (state) => {
+//   nextTick(()=>{
+//     setTimeout(()=>{ 
+//       getAdSearchFn();
+//     },500)
+//   })
+// });
+
+function getAdSearchFn(){
+  getAdSearch(appStore.agentId).then(res=>{
+    if(res.code == 200){
+      adSearchList.value = res.data || [];
+    }
+  })
+}
+function sendMessage(msgContent){
+  emit('sendMessage',{chatType:0,msgContent});
+}
+
+</script>
+<style lang="scss" scoped>
+.messagesInfoDefault{
+  position: fixed;
+  width: 100%;
+  top: 40rpx;
+  left: 0;
+  padding: 30rpx;
+  .service-avatar{
+    width: 200rpx;
+  }
+}
+</style>

+ 37 - 0
jd_logistics-app/pages/ai/components/tools.vue

@@ -0,0 +1,37 @@
+<template>
+ 	<view class="bg_color_fff tools flex-center-between">
+    <view class="font_size25 nowrap">今日次数 <text class="color_price">{{appStore.userInfo?.aiQuotaDaily}}</text> 次</view>
+    <view class="font_size25 nowrap">余额 <text class="color_price">{{appStore.userInfo?.rechargeBalance}}</text> {{appStore.moneyUnit}}</view>
+    <view>
+      <image src="/static/img/wenhua.png" mode="" class="mr20 tool-icon" @click="tohistoryList"/>
+      <image src="/static/img/addHuiHua.png" mode="" class="mr20 ml20 tool-icon" @click="addHuiHuaFn"/>
+    </view>
+  </view>
+</template>
+<script setup>
+import { ref } from "vue";
+import { useAppStore } from "@/stores/app";
+import { onShow} from '@dcloudio/uni-app'
+const appStore = useAppStore();
+const emit = defineEmits(['addHuiHuaFn'])
+// 新增:添加回话功能
+function addHuiHuaFn(){
+  appStore.sessionId = '';
+  emit('addHuiHuaFn');
+}
+function tohistoryList(){
+  uni.navigateTo({
+    url: '/pages/historyList/historyList',
+  })
+}
+
+</script>
+<style lang="scss" scoped>
+.tools{
+  padding: 0 20rpx 20rpx;
+  .tool-icon{
+    width: 50rpx;
+    height: 50rpx;
+  }
+}
+</style>

+ 156 - 0
jd_logistics-app/pages/historyList/historyList.vue

@@ -0,0 +1,156 @@
+<template>
+<view class="historyList padding30">
+  <z-paging
+    ref="pagingRef"
+    class="paginng-contaner"
+    :default-page-size="20"
+    :use-refresher-status-bar-placeholder="true"
+    v-model="list"
+    @query="handleQuery"
+  >
+		<view class="bg_color_fff padding20 border_radius_20">
+			<u-search placeholder="查找对话内容" v-model="keyword" 
+      @search="reloadList" @custom="reloadList" @clear="reloadList"
+      :showAction="false" borderColor="#ffffff"></u-search>
+		</view>
+    <view class="padding20" v-for="group in groupedList" :key="group.date">
+      <view>{{group.date}}</view>
+      <view class="flex-between padding20 bg_color_fff border_radius_20 mt10" 
+      v-for="message in group.messages" :key="message.sessionId">
+        <view class="payment-radio  mr20" @click="checkboxChange(message.sessionId)">
+            <view
+						class="radio-circle"
+						:class="{ checked: selected.includes(message.sessionId) }"
+					></view>
+				</view>
+        <image src="/static/img/wenhua.png" class="mr20" mode="" style="width: 40rpx;height: 40rpx;"/>
+        <view class="flex_1 mr20" @click="add(message.sessionId)">{{message.firstMessageContent}}</view>
+        <text class="gray font_size20">{{message.HS}}</text>
+      </view>
+    </view>
+    <view class="footplaceholder"></view>
+     <!-- foot -->
+    <view class="foot bg_color_fff flex-center-between">
+			<view class="order_btn paddingTB20 flex_1 text_align_center mr20 plain" @click="del">删除</view>
+			<view class="order_btn paddingTB20 flex_1 text_align_center" @click="add">新增会话</view>
+		</view>
+  </z-paging>
+</view>
+</template>
+<script setup> 
+import { ref, computed } from 'vue'
+import dayjs from 'dayjs'
+import { chatHistoryList, delChatHistoryList } from '@/api/ai'
+import { useAppStore } from '@/stores/app'
+const appStore = useAppStore();
+
+const pagingRef = ref();
+const keyword = ref('');
+const list = ref([]);
+const selected = ref([]);
+
+function checkboxChange(sessionId){
+  if(selected.value.includes(sessionId)){
+    selected.value = selected.value.filter(item => item !== sessionId);
+  }else{
+    selected.value.push(sessionId);
+  }
+}
+function del(){
+  if(selected.value.length==0){
+    uni.showToast({
+      title: '请选择要删除的会话',
+      icon: 'none'
+    });
+    return;
+  }
+  uni.showModal({
+    title: '提示',
+    content: '确定要删除吗?',
+    success: function (res) {
+      if (res.confirm) {
+        delChatHistoryList(selected.value).then(res => {
+          if(res.code == 200){
+            uni.showToast({
+              title: '删除成功',
+              icon: 'success'
+            });
+            selected.value = [];
+            reloadList();
+          }
+        })
+      } else if (res.cancel) {
+        console.log('用户点击取消');
+      }
+    }
+  });
+}
+//跳转ai页面
+function add(sessionId){
+  if(sessionId) appStore.sessionId = sessionId;
+  uni.switchTab({
+    url: '/pages/ai/ai'
+  });
+}
+function reloadList(item) {
+  pagingRef.value.reload();
+}
+// 下拉刷新和滚动底部会自动触发此方法
+async function handleQuery(page, pageSize, from) {
+  try {
+    const params = {
+      pageNum: page,
+      pageSize,
+			agentId:appStore.agentId,
+    };
+    const { rows } = await chatHistoryList(params);
+    pagingRef.value.complete(rows);
+  } catch (e) {
+		console.log('msg-comment',e)
+    pagingRef.value.complete(false);
+  }
+}
+const groupedList = computed(() => {
+  const groups = {};
+  
+  list.value.forEach(message => {
+    const date = dayjs(message.firstMessageTime);
+    const today = dayjs().startOf('day');
+    const yesterday = today.subtract(1, 'day');
+    message.HS=date.format('HH:mm');
+    
+    let groupKey;
+    if (date.isSame(today, 'day')) {
+      groupKey = '今天';
+    } else if (date.isSame(yesterday, 'day')) {
+      groupKey = '昨天';
+    } else {
+      groupKey = date.format('YYYY-MM-DD');
+    }
+    
+    if (!groups[groupKey]) {
+      groups[groupKey] = [];
+    }
+    groups[groupKey].push(message);
+  });
+  
+  // 转换并排序
+  return Object.keys(groups)
+    .map(key => ({
+      date: key,
+      messages: groups[key]
+    }))
+    .sort((a, b) => {
+      // 按日期倒序排列
+      if (a.date === '今天') return -1;
+      if (b.date === '今天') return 1;
+      if (a.date === '昨天') return -1;
+      if (b.date === '昨天') return 1;
+      return new Date(b.date) - new Date(a.date);
+    });
+});
+</script>
+<style lang="less" scoped>
+.historyList{
+}
+</style>

+ 165 - 0
jd_logistics-app/pages/index/components/PersonalExpressDialog.vue

@@ -0,0 +1,165 @@
+<template>
+	<!-- 使用 u-popup 组件 -->
+	<u-popup :show="visible" mode="bottom" :round="30" :closeable="false" :closeOnClickOverlay="true"
+		@close="handleClose">
+		<view class="popup-content">
+			<!-- 标题 -->
+			<view class="popup-header">
+				<text class="popup-title">个人寄件</text>
+			</view>
+
+			<!-- 物流选项 -->
+			<view class="express-options">
+				<!-- 顺丰选项 -->
+				<view class="express-option sf-option" :class="{ 'selected': selectedExpress === '顺丰' }"
+					@click="selectExpress('顺丰')">
+					<image class="option-icon" src="/static/img/icon-logo-sf.png"
+						mode="aspectFit" />
+					<text class="option-text">顺丰物流</text>
+					
+				</view>
+
+				<!-- 京东选项 -->
+				<view class="express-option jd-option" :class="{ 'selected': selectedExpress === '京东' }"
+					@click="selectExpress('京东')">
+					<image class="option-icon" src="/static/img/icon-logo-jd.png"
+						mode="aspectFit" />
+					<text class="option-text">京东物流</text>
+				</view>
+			</view>
+
+			<!-- 取消按钮 -->
+			<view class="popup-footer">
+				<button class="cancel-btn" @click="handleClose">取消</button>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script setup>
+	import {
+		ref
+	} from 'vue'
+
+	const props = defineProps({
+		visible: {
+			type: Boolean,
+			default: false
+		}
+	})
+
+	const emit = defineEmits(['update:visible', 'select'])
+
+	const selectedExpress = ref('')
+
+	const selectExpress = (company) => {
+		selectedExpress.value = company
+
+		// 延时关闭并传递选择结果
+		setTimeout(() => {
+			emit('select', company)
+			emit('update:visible', false)
+			selectedExpress.value = ''
+		}, 300)
+	}
+
+	const handleClose = () => {
+		selectedExpress.value = ''
+		emit('update:visible', false)
+	}
+</script>
+
+<style lang="scss" scoped>
+	.popup-content {
+		padding: 32rpx 16rpx 0rpx 16rpx;
+		background-color: #fff;
+		border-radius: 40rpx 40rpx 0rpx 0rpx;
+	}
+
+	.popup-header {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin-bottom: 46rpx;
+	}
+
+	.popup-title {
+		height: 52rpx;
+		line-height: 52rpx;
+		font-size: 36rpx;
+		font-weight: bold;
+		color: #333;
+	}
+
+
+	.express-options {
+		height: 204rpx;
+		display: flex;
+		justify-content: space-around;
+	}
+
+	.express-option {
+		width: 200rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+
+		&.selected {
+			width: 200rpx;
+			height: 204rpx;
+			background: #E9F0FF;
+			border-radius: 32rpx;
+			border: 2rpx solid #C1D5FF;
+		}
+		
+		
+		.option-icon {
+			width: 88rpx;
+			height: 88rpx;
+			margin-bottom: 8rpx;
+		}
+		
+		.option-text {
+			font-size: 28rpx;
+			font-weight: 500;
+			color: #333;
+		}
+	}
+
+
+	.popup-footer {
+		margin-top: 44rpx;
+		
+		
+		.cancel-btn {
+			width: 100%;
+			height: 88rpx;
+			background: #E9F0FF;
+			border-radius: 44rpx;
+			font-weight: 400;
+			font-size: 32rpx;
+			color: #2361FF;
+			line-height: 88rpx;
+			text-align: center;
+			font-style: normal;
+			text-transform: none;
+		
+		}
+	}
+
+
+	@keyframes popIn {
+		0% {
+			transform: scale(0);
+		}
+
+		70% {
+			transform: scale(1.2);
+		}
+
+		100% {
+			transform: scale(1);
+		}
+	}
+</style>

+ 273 - 0
jd_logistics-app/pages/index/index.vue

@@ -0,0 +1,273 @@
+<template>
+	<view class="container">
+		<!-- 上半部分:轮播图区域 -->
+		<view class="top-section">
+			<swiper class="swiper" 
+				:indicator-dots="true" 
+				:autoplay="true" 
+				:interval="3000" 
+				:duration="1000" 
+				:circular="true"
+				indicator-active-color="#1B64F0"
+				indicator-color="rgba(255, 255, 255, 0.6)"
+				indicator-class="swiper-indicator"
+				active-class="swiper-indicator-active">
+				<swiper-item v-for="(item, index) in swiperList" :key="index">
+					<view class="swiper-item">
+						<image class="swiper-img" :src="item.imageUrl" mode="aspectFill" />
+					</view>
+				</swiper-item>
+			</swiper>
+		</view>
+		
+		<view class="btn-container">
+			<view class="btn-item" @click="handleExpress('2')">
+				<image class="button-icon" src="/static/img/index-time.png" />
+				<view class="button-right">
+					<view class="button-title">瑞鲸速达(顺丰)</view>
+					<view class="button-desc">一小时下单取件</view>
+				</view>
+				
+			</view>
+			<view class="btn-item" @click="handleExpress('1')">
+				<image class="button-icon" src="/static/img/index-un-time.png" />
+				<view class="button-right">
+					<view class="button-title">瑞鲸速达(京东)</view>
+					<view class="button-desc">下单当日取件</view>
+				</view>
+				
+			</view>
+			<view class="btn-item" @click="showExpressDialog">
+				<image class="button-icon" src="/static/img/index-personal.png" />
+				<view class="button-right">
+					<view class="button-title">个人寄件</view>
+					<view class="button-desc">支持顺丰和京东</view>
+				</view>
+				
+			</view>
+			
+		</view>
+
+		<!-- 使用 u-popup 弹框组件 -->
+		<PersonalExpressDialog :visible="showDialog" @update:visible="showDialog = $event"
+			@select="handleExpressSelect" />
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,onMounted
+	} from 'vue'
+	// 导入 u-popup 弹框组件
+	import PersonalExpressDialog from './components/PersonalExpressDialog.vue'
+	import { bannerList } from '../../api/user'
+
+	const showDialog = ref(false)
+
+	// 轮播图数据
+	const swiperList = ref([])
+	
+	onMounted(()=>{
+		getBannerList()
+	})
+	
+	const getBannerList = ()=>{
+		bannerList().then(res=>{
+			if(res.code == 200){
+				swiperList.value = res.data
+			}
+		},{})
+	}
+
+	const showExpressDialog = () => {
+		console.log('显示个人寄件弹框')
+		showDialog.value = true
+	}
+
+	const handleExpressSelect = (company) => {
+		uni.showToast({
+			title: `已选择${company}物流`,
+			icon: 'success',
+			duration: 1500
+		})
+
+		// 模拟跳转到下单页面
+		setTimeout(() => {
+			uni.showLoading({
+				title: '正在跳转...'
+			})
+
+			setTimeout(() => {
+				uni.hideLoading()
+				// 实际跳转代码
+				// uni.navigateTo({ 
+				//   url: `/pages/order/create?company=${company}` 
+				// })
+			}, 800)
+		}, 500)
+	}
+
+	const handleExpress = (company) => {
+	  uni.navigateTo({ 
+		    url: `/pages/order/create_order?product=${company}` 
+		  })
+		
+		// 实际跳转代码
+		// setTimeout(() => {
+		//   uni.navigateTo({ 
+		//     url: `/pages/order/create?company=${company}` 
+		//   })
+		// }, 1500)
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		display: flex;
+		flex-direction: column;
+		height: 100vh;
+		background-color: #F5F7FA;
+	}
+
+	.top-section {
+		height: 563rpx;
+		position: relative;
+	}
+
+	.swiper {
+		width: 100%;
+		height: 100%;
+		position: relative;
+	}
+
+	.swiper-item {
+		width: 100%;
+		height: 100%;
+		position: relative;
+	}
+
+	.swiper-img {
+		width: 100%;
+		height: 100%;
+		object-fit: cover;
+	}
+
+	/* 自定义指示点样式 - 通过定位调整位置 */
+	/* 方法一:调整整个指示器容器的位置 */
+	/* 注意:在不同平台可能需要使用不同的选择器 */
+	/* 微信小程序 */
+	::v-deep .wx-swiper-dots {
+		bottom: 80rpx !important; /* 调整指示器容器的位置 */
+		display: flex !important;
+		justify-content: center !important;
+		align-items: center !important;
+	}
+	
+	/* H5平台 */
+	::v-deep .uni-swiper-dots {
+		bottom: 80rpx !important; /* 调整指示器容器的位置 */
+		display: flex !important;
+		justify-content: center !important;
+		align-items: center !important;
+	}
+
+	/* 方法二:直接修改指示点样式,通过调整父容器样式 */
+	.swiper-indicator {
+		width: 8rpx !important;
+		height: 8rpx !important;
+		border-radius: 50% !important;
+		background: rgba(255, 255, 255, 0.6) !important;
+		margin: 0 4rpx !important;
+	}
+	
+	.swiper-indicator-active {
+		width: 32rpx !important;
+		height: 8rpx !important;
+		background: #1B64F0 !important;
+		border-radius: 4rpx !important;
+	}
+
+	/* 方法三:如果上述方法都不生效,可以使用绝对定位自定义指示器 */
+	.custom-indicator {
+		position: absolute;
+		left: 0;
+		right: 0;
+		bottom: 80rpx; /* 调整指示器的垂直位置 */
+		z-index: 10;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+	
+	.custom-indicator-dot {
+		width: 8rpx;
+		height: 8rpx;
+		border-radius: 50%;
+		background: rgba(255, 255, 255, 0.6);
+		margin: 0 4rpx;
+		transition: all 0.3s ease;
+	}
+	
+	.custom-indicator-dot.active {
+		width: 32rpx;
+		height: 8rpx;
+		background: #1B64F0;
+		border-radius: 4rpx;
+	}
+	
+	.btn-container{
+			display: flex;
+			flex-wrap: wrap;
+			justify-content: space-between;
+			position: relative;
+			top: -40rpx;
+			padding: 0rpx 20rpx;
+		
+		.btn-item{
+			width: 339rpx;
+			height: 172rpx;
+			background: #FFFFFF;
+			box-shadow: 0rpx 4rpx 16rpx 0rpx rgba(27,100,240,0.08), 0rpx 0rpx 8rpx 0rpx rgba(27,100,240,0.08);
+			border-radius: 32rpx;
+			border: 2rpx solid #FFFFFF;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			padding: 20rpx 0rpx 20rpx 20rpx;
+			margin-bottom: 32rpx;
+			
+			.button-icon{
+				width: 88rpx;
+				height: 88rpx;
+			}
+			
+			.button-right{
+				display: flex;
+				flex-direction: column;
+				flex: 1;
+				margin-left: 16rpx;
+				
+				.button-title {
+					height: 48rpx;
+					line-height: 48rpx;
+					font-size: 28rpx;
+					font-weight: bold;
+					color: #333;
+					flex-shrink: 0;
+				}
+				
+				.button-desc {
+					height: 44rpx;
+					line-height: 44rpx;
+					font-size: 24rpx;
+					color: #666;
+					margin-top: 8rpx;
+				}
+			}
+		}
+		
+		
+	}
+
+	
+</style>

+ 146 - 0
jd_logistics-app/pages/index111/index.vue

@@ -0,0 +1,146 @@
+<template>
+	<view class="content padding30">
+		<view class="bg_color_fff padding20 border_radius_20" @click="to_service_search">
+			<u-search placeholder="输入你想知道的事" v-model="keyword" 
+			@search="toAi({msgContent:keyword})" 
+			@custom="toAi({msgContent:keyword})"
+			:showAction="false" borderColor="#ffffff"></u-search>
+		</view>
+		<swiper class="swiper_list bg_color_fff border_radius_20 mt20" circular :indicator-dots="true" :autoplay="true" indicator-active-color="#ffffff">
+			<swiper-item v-for="(item, index) in swiperList" :key="index" class="swiper_item border_radius_20"
+			@click="toWebView(item)"
+			>
+				<image :src="HTTP_ADMIN_URL+item.dictValue" mode="" class="swiper_img border_radius_20"></image>
+			</swiper-item>
+		</swiper>
+		<!-- 财智工具箱 -->
+		<view class="mt20 bg_color_fff border_radius_20">
+			<view class="bold font_size30 padding20">财智工具箱</view>
+			<view class="grid-container padding20">
+				<view class="border_radius_20" 
+				@click="toAi({agentId:item.agentId})"
+				v-for="(item, index) in agentList" :key="index">
+					<image :src="HTTP_ADMIN_URL+item.imageUrl" class="item-img"></image>
+					<view class="item-content">
+						<view class="font_size30 bold title text-ellipsis">{{item.agentName}}</view>
+						<view class="font_size20 gray mt10 desc text-ellipsis">{{item.agentDesc}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- 常见问题 -->
+		<view class="mt20 bg_color_fff border_radius_20">
+			<view class="bold font_size30 padding20">常见问题</view>
+			<view class="padding20 flex-center-between border-bottom" 
+			@click="toAi({agentId:item.cssClass*1,msgContent:item.dictValue})"
+			v-for="(item, index) in adSearchList" :key="index">
+				<text class="flex_1 mr20">#{{item.dictValue}}</text>
+				<image src="/static/img/arrow-right.png" mode="widthFix" class="menu_img"></image>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { adList, getAgentList, getAdSearch } from "@/api/home.js";
+import { HTTP_ADMIN_URL } from "@/config/app.js";
+import { onLoad, onShareAppMessage} from '@dcloudio/uni-app'
+onShareAppMessage((res) => {
+  return appStore.onShareAppMessageObj
+})
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+const keyword = ref('');
+const swiperList = ref([]);
+const agentList = ref([]);
+const adSearchList = ref([]);
+
+onLoad(()=>{
+	console.log('index onLoad');
+	adListFn();
+	agentListFn();
+	getAdSearchFn();
+});
+
+function toWebView({targetPath,dictCode}){
+	if(targetPath){
+		uni.navigateTo({
+			url: '/pages/webView/webView?url='+encodeURIComponent(url)
+		});
+	}else{
+		uni.navigateTo({
+			url: '/pages/policy/ad_detail?dictCode='+dictCode
+		});
+	}
+}
+function toAi({agentId='', msgContent=''}){
+	if(agentId) appStore.UPDATE_agentId(agentId);
+	if(msgContent)appStore.UPDATE_msgContent(msgContent);
+	uni.switchTab({
+		url: '/pages/ai/ai'
+	});
+}
+//获取轮播图
+function adListFn(){
+	adList().then(res=>{
+		if(res.code == 200){
+			swiperList.value = res.data || [];
+		}
+	})
+}
+//获取智能体列表
+function agentListFn(){
+	getAgentList().then(res=>{
+		if(res.code == 200){
+			agentList.value = res.rows || [];
+		}
+	})
+}
+// 搜索常用问题
+function getAdSearchFn(){
+  getAdSearch().then(res=>{
+    if(res.code == 200){
+      adSearchList.value = res.data || [];
+    }
+  })
+}
+
+</script>
+
+<style scoped lang="scss">
+	.content{
+		/* 轮播 */
+		.swiper_list{
+			height: 350rpx;
+			overflow: visible;
+			.swiper_item{
+				height: fit-content !important;
+				.swiper_img{
+					width: 100%;
+					height: 350rpx;
+				}
+			}
+		}
+		.grid-container{
+			.item-img{
+				width: 100%;
+				height: 200rpx;
+				object-fit: cover;
+				object-position: center;
+				border-radius: 20rpx 20rpx 0 0;
+			}
+			.title, .desc {
+				display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 ,适用于webkit内核浏览器 */
+				-webkit-box-orient: vertical; /* 设置或检索伸缩盒对象的子元素的排列方式 为垂直排列 */
+				-webkit-line-clamp: 1; /* 显示的行数,这里设置为2行 */
+				overflow: hidden; /* 隐藏超出的内容 */
+			}
+		}
+		.menu_img{
+			width: 50rpx;
+			height: 50rpx;
+		}
+	}
+
+</style>

+ 369 - 0
jd_logistics-app/pages/logistics/index.vue

@@ -0,0 +1,369 @@
+<template>
+	<view class="logistics-container">
+		<!-- 头部快递信息 -->
+		<view class="logistics-header">
+			<image class="logo" src="/static/img/icon-logo-jd.png"></image>
+			<view class="company-info">
+				<text class="company-name">京东物流</text>
+				<view class="tracking-number">
+					<text class="number">JDVA40141037033</text>
+					<text class="copy-btn" @click="copyTrackingNumber">复制</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 物流轨迹时间轴 -->
+		<view class="timeline-container">
+			<TimelineItem v-for="(item, index) in trackList" :key="index" :title="item.title" :desc="item.desc"
+				:time="item.time" :is-active="item.isActive" :show-line="index < trackList.length - 1"
+				:show-dot-bg="index === 0" />
+		</view>
+
+	
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		onMounted
+	} from 'vue'
+	import TimelineItem from '@/components/TimelineItem.vue'
+
+	const scrollHeight = ref(400)
+
+	// 模拟物流轨迹数据
+	const trackList = ref([{
+			title: '已代收',
+			desc: '您的快件已由[门卫]代收,快递员:马新宽、联系...',
+			time: '2026-01-11 17:32:32',
+			isActive: true
+		},
+		{
+			title: '派送中',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '运输中',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '运输中',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '运输中',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿站】...',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		}
+	])
+
+	onMounted(() => {
+		// 计算滚动区域高度
+		uni.getSystemInfo({
+			success: (res) => {
+				scrollHeight.value = res.windowHeight - 420 // 减去头部、当前状态、底部等固定高度
+			}
+		})
+	})
+
+	const copyTrackingNumber = () => {
+		uni.setClipboardData({
+			data: 'JDVA40141037033',
+			success: () => {
+				uni.showToast({
+					title: '已复制运单号',
+					icon: 'success'
+				})
+			}
+		})
+	}
+
+	const contactCourier = () => {
+		uni.showModal({
+			title: '联系快递员',
+			content: '拨打快递员电话?',
+			success: (res) => {
+				if (res.confirm) {
+					uni.makePhoneCall({
+						phoneNumber: '请填写快递员电话'
+					})
+				}
+			}
+		})
+	}
+
+	const viewDetails = () => {
+		uni.showToast({
+			title: '查看详情',
+			icon: 'none'
+		})
+	}
+
+	const copyAllInfo = () => {
+		const allInfo =
+			`京东物流\n运单号:JDVA40141037033\n状态:已代收\n快递员:马新宽\n\n物流轨迹:\n2026-01-11 17:32:32 已代收\n2026-01-11 17:32:32 派送中\n2026-01-11 17:32:32 运输中\n2026-01-11 17:32:32 已揽件`
+
+		uni.setClipboardData({
+			data: allInfo,
+			success: () => {
+				uni.showToast({
+					title: '已复制全部信息',
+					icon: 'success'
+				})
+			}
+		})
+	}
+</script>
+
+<style lang="scss" scoped>
+	.logistics-container {
+		padding: 20rpx;
+		background-color: #f5f5f5;
+		min-height: 100vh;
+	}
+
+	// 头部样式
+	.logistics-header {
+		height: 140rpx;
+		background: #FFFFFF;
+		border-radius: 32rpx;
+		padding: 20rpx;
+		display: flex;
+
+		.logo {
+			width: 100rpx;
+			height: 100rpx;
+		}
+	}
+
+	.company-info {
+		margin-left: 20rpx;
+
+		.company-name {
+			font-size: 32rpx;
+			height: 48rpx;
+			line-height: 48rpx;
+			font-weight: bold;
+			display: block;
+			margin-bottom: 8rpx;
+		}
+
+		.tracking-number {
+			height: 44rpx;
+			display: flex;
+			align-items: center;
+
+			.number {
+				height: 44rpx;
+				font-weight: 400;
+				line-height: 44rpx;
+				font-size: 28rpx;
+				color: #666;
+			}
+
+			.copy-btn {
+				height: 44rpx;
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #1B64F0;
+				margin-left: 8rpx;
+				line-height: 44rpx;
+				text-align: center;
+			}
+		}
+	}
+
+
+	// 当前状态样式
+	.current-status {
+		background-color: #fff;
+		border-radius: 16rpx;
+		padding: 30rpx;
+		margin-bottom: 20rpx;
+		display: flex;
+		align-items: flex-start;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+	}
+
+	.status-icon {
+		width: 80rpx;
+		height: 80rpx;
+		background: #1aad19;
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 20rpx;
+
+		.icon {
+			color: #fff;
+			font-size: 40rpx;
+			font-weight: bold;
+		}
+	}
+
+	.status-info {
+		flex: 1;
+
+		.status-text {
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #333;
+			display: block;
+			margin-bottom: 10rpx;
+		}
+
+		.status-desc {
+			font-size: 26rpx;
+			color: #666;
+			display: block;
+			margin-bottom: 15rpx;
+			line-height: 1.4;
+		}
+
+		.courier-info {
+			background: #f8f8f8;
+			border-radius: 12rpx;
+			padding: 15rpx;
+			margin-bottom: 15rpx;
+
+			.courier-text {
+				font-size: 26rpx;
+				color: #333;
+				display: block;
+				margin-bottom: 5rpx;
+			}
+
+			.contact-text {
+				font-size: 24rpx;
+				color: #1aad19;
+			}
+		}
+
+		.status-time {
+			font-size: 24rpx;
+			color: #999;
+			display: block;
+		}
+	}
+
+	// 时间轴容器
+	.timeline-container {
+		margin-top: 20rpx;
+		background-color: #fff;
+		border-radius: 32rpx;
+		margin-bottom: 20rpx;
+		padding: 20rpx;
+	}
+
+</style>

+ 602 - 0
jd_logistics-app/pages/mine/mine.vue

@@ -0,0 +1,602 @@
+<!-- pages/mine/mine.vue -->
+<template>
+	<view class="mine-container">
+		<!-- 顶部用户信息区域 -->
+		<view class="user-header ">
+			<headerInfo title="个人中心"></headerInfo>
+			<view class="user-info-section">
+				<view class="avatar-section">
+					<u-avatar 
+						@click="toUser"
+						size="144rpx"
+						shape="circle"
+						:src="userAvatar"
+						mode="aspectFill"
+						:show-level="false"
+					></u-avatar>
+					<view class="user-text-info">
+						<view class="user-name-section">
+							<view v-if="isLoggedIn">
+								<view class="name-section">
+									<view class="user-name" @click="toUser">
+										{{ displayName }}
+									</view>
+									<view class="department-badge">
+										储运部
+									</view>
+								</view>
+								
+								<view class="user-account">
+									账号:{{ userAccount }}
+								</view>
+							</view>
+							<button v-else
+								class="login-button"
+								open-type="getPhoneNumber"
+								@getphonenumber="handleGetPhoneNumber"
+								:loading="isLogging"
+								:disabled="isLogging || isProcessing"
+							>
+								{{ loginButtonText }}
+							</button>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 菜单功能区域 -->
+		<view class="menu-section">
+			<view class="menu-card">
+				<!-- 订单列表 -->
+				<view class="menu-item" @click="toOrderList">
+					<view class="menu-item-left">
+						<image src="/static/img/mine/icon-mine-order.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="toAddressBook">
+					<view class="menu-item-left">
+						<image src="/static/img/mine/icon-mine-address.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="toSettlementCode">
+					<view class="menu-item-left">
+						<image src="/static/img/mine/icon-mine-qr.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="makePhoneCall">
+					<view class="menu-item-left">
+						<image src="/static/img/mine/icon-mine-server.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="toAboutUs">
+					<view class="menu-item-left">
+						<image src="/static/img/mine/icon-mine-about.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 logout-item" 
+					v-if="isLoggedIn"
+					@click="userLogoutFn">
+					<text class="menu-text logout-text">退出登录</text>
+				</view>
+			</view>
+		</view>
+		
+		<!-- 加载提示 -->
+		<u-loading-page :loading="pageLoading" bg-color="transparent"></u-loading-page>
+	</view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import headerInfo from "@/components/headerInfo.vue";
+import { userLogout } from "@/api/user.js";
+import { getAdServicePhone } from "@/api/home.js";
+import { checkLoginShowModal, quickLogin } from "@/utils/util.js";
+import { onLoad, onShow, onShareAppMessage } from '@dcloudio/uni-app'
+import { useAppStore } from "@/stores/app";
+
+const appStore = useAppStore();
+
+// 响应式数据
+const isLogging = ref(false);
+const isProcessing = ref(false);
+const pageLoading = ref(false);
+const phoneNumber = ref('');
+
+// 计算属性
+const isLoggedIn = computed(() => {
+	return !!(appStore.userInfo?.phonenumber && appStore.token);
+});
+
+const userAvatar = computed(() => {
+	return appStore.userInfo?.avatar || '/static/img/default-avatar.png';
+});
+
+const displayName = computed(() => {
+	return appStore.userInfo?.nickName || appStore.userInfo?.userName;
+});
+
+const userAccount = computed(() => {
+	return appStore.userInfo?.phonenumber || '';
+});
+
+const loginButtonText = computed(() => {
+	if (isLogging.value) return '登录中...';
+	if (isProcessing.value) return '处理中...';
+	return '请点击登录';
+});
+
+onShareAppMessage((res) => {
+	return appStore.onShareAppMessageObj;
+});
+
+onLoad(() => {
+	getAdServicePhoneFn();
+	// 检查用户登录状态
+	checkUserStatus();
+});
+
+onShow(() => {
+	// 页面显示时检查是否需要重新获取用户信息
+	if (appStore.token && !appStore.userInfo?.userPhone) {
+		appStore.USERINFO();
+	}
+});
+
+// 检查用户状态
+async function checkUserStatus() {
+	if (appStore.token && !appStore.userInfo) {
+		pageLoading.value = true;
+		try {
+			await appStore.USERINFO();
+		} catch (error) {
+			console.error('获取用户信息失败:', error);
+		} finally {
+			pageLoading.value = false;
+		}
+	}
+}
+
+// 获取客服电话
+function getAdServicePhoneFn() {
+	getAdServicePhone({}).then(res => {
+		phoneNumber.value = res.data?.servicePhone || '';
+	}).catch(err => {
+		console.log(err);
+	});
+}
+
+// 跳转到用户信息页
+async function toUser() {
+	if (!await checkLoginShowModal()) return;
+	uni.navigateTo({
+		url: '/pages/user/user'
+	});
+}
+
+// 跳转到订单列表
+async function toOrderList() {
+	if (!await checkLoginShowModal()) return;
+	uni.navigateTo({
+		url: '/pages/order/index'
+	});
+}
+
+// 跳转到地址簿
+async function toAddressBook() {
+	if (!await checkLoginShowModal()) return;
+	uni.navigateTo({
+		url: '/pages/address/address_list'
+	});
+}
+
+// 跳转到月结码
+async function toSettlementCode() {
+	if (!await checkLoginShowModal()) return;
+	uni.navigateTo({
+		url: '/pages/mine/settlementCode'
+	});
+}
+
+// 跳转到隐私政策
+async function toPrivacyPolicy() {
+	uni.navigateTo({
+		url: '/pages/webview/webview?url=' + encodeURIComponent('https://your-domain.com/privacy')
+	});
+}
+
+// 跳转到关于我们
+async function toAboutUs() {
+	uni.navigateTo({
+		url: '/pages/about/index'
+	});
+}
+
+// 处理获取手机号登录 - 使用优化后的quickLogin
+async function handleGetPhoneNumber(e) {
+	if (isLogging.value || isProcessing.value) return;
+	
+	isLogging.value = true;
+	isProcessing.value = true;
+	
+	// 先隐藏loading(如果有)
+	uni.hideLoading();
+	
+	try {
+		// 显示加载提示
+		uni.showLoading({
+			title: '登录中...',
+			mask: true
+		});
+		
+		// 调用快速登录,并传入自定义回调
+		await quickLogin(e, {
+			onSuccess: (result) => {
+				console.log('页面登录成功回调:', result);
+				// 页面特定的成功逻辑
+				uni.showToast({
+					title: result.message || `欢迎回来,${result.userInfo?.userName || '用户'}`,
+					icon: 'success',
+					duration: 2000
+				});
+				
+				// 由于appStore.USERINFO()已经在工具函数中调用,这里不需要再调用
+				// 但我们可以触发页面更新
+				uni.$emit('pageRefresh');
+			},
+			onFail: (error) => {
+				console.log('页面登录失败回调:', error);
+				// 页面特定的失败逻辑
+				let errorMessage = error.message || '登录失败,请重试';
+				
+				if (error.type === 'auth_denied') {
+					errorMessage = '您拒绝了授权,无法登录';
+					// 如果是授权拒绝,可以显示引导重新授权的提示
+					uni.showModal({
+						title: '提示',
+						content: '需要手机号授权才能正常使用,请点击右上角菜单,选择「重新进入小程序」后重新授权',
+						showCancel: false,
+						confirmText: '知道了'
+					});
+				} else if (error.type === 'auth_code_missing') {
+					errorMessage = '授权信息不完整';
+				} else if (error.type === 'login_failed') {
+					errorMessage = error.originalError || error.message;
+				}
+				
+				// 只在需要时显示Toast(非授权拒绝的情况)
+				if (error.type !== 'auth_denied') {
+					uni.showToast({
+						title: errorMessage,
+						icon: 'none',
+						duration: 3000
+					});
+				}
+			},
+			redirectUrl: '/pages/mine/mine'
+		});
+		
+	} catch (error) {
+		console.error('登录处理异常:', error);
+		// 这里是未在onFail中捕获的错误(应该是很少出现的)
+		uni.showToast({
+			title: error,
+			icon: 'none'
+		});
+	} finally {
+		isLogging.value = false;
+		isProcessing.value = false;
+		// 确保loading被隐藏
+		setTimeout(() => {
+			uni.hideLoading();
+		}, 300);
+	}
+}
+
+// 拨打电话
+const makePhoneCall = () => {
+	if (!phoneNumber.value) {
+		uni.showToast({
+			title: '客服电话暂时无法接通',
+			icon: 'none'
+		});
+		return;
+	}
+	
+	uni.makePhoneCall({
+		phoneNumber: phoneNumber.value,
+		success: () => {
+			console.log('成功唤起拨号界面');
+		},
+		fail: (err) => {
+			console.error('唤起拨号界面失败', err);
+			uni.showToast({
+				title: '拨号失败',
+				icon: 'none'
+			});
+		}
+	});
+};
+
+// 退出登录
+function userLogoutFn() {
+	uni.showModal({
+		title: '提示',
+		content: '确认要退出登录吗?',
+		success: function(res) {
+			if (res.confirm) {
+				uni.showLoading({
+					title: '正在退出...'
+				});
+				
+				userLogout({}).then(res => {
+					appStore.LOGOUT();
+					uni.hideLoading();
+					uni.showToast({
+						title: '已退出登录',
+						icon: 'success'
+					});
+					
+					// 延迟刷新页面状态
+					setTimeout(() => {
+						uni.$emit('userLogoutSuccess');
+					}, 500);
+				}).catch(err => {
+					uni.hideLoading();
+					uni.showToast({
+						title: '退出失败',
+						icon: 'none'
+					});
+				});
+			}
+		}
+	});
+}
+</script>
+
+<style lang="less" scoped>
+.mine-container {
+	height: 100vh;
+	background: linear-gradient(135deg, #CFE9FF 0%, #F5F7FA 50.86%);
+	position: relative;
+
+	// 用户信息区域
+	.user-header {
+		padding: 0rpx 24rpx;
+
+		.user-info-section {
+			margin-top: 32rpx;
+			display: flex;
+			flex-direction: column;
+			gap: 20rpx;
+
+			.avatar-section {
+				display: flex;
+				align-items: center;
+				min-height: 144rpx; // 添加最小高度防止闪烁
+
+				.user-text-info {
+					flex: 1;
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					min-height: 144rpx; // 添加最小高度防止闪烁
+				}
+
+				.user-name-section {
+					margin-left: 16rpx;
+					min-height: 100rpx; // 固定高度防止闪烁
+					display: flex;
+					flex-direction: column;
+					justify-content: center;
+					
+					.name-section {
+						height: 52rpx;
+						display: flex;
+						align-items: center;
+						margin-bottom: 8rpx;
+					}
+					
+					.user-name {
+						font-weight: bold;
+						font-size: 36rpx;
+						color: #333333;
+						text-align: center;
+						line-height: 1;
+					}
+
+					.login-button {
+						background: transparent;
+						font-size: 40rpx;
+						font-weight: 600;
+						color: #333;
+						line-height: 56rpx;
+						padding: 0;
+						margin: 0;
+						text-align: left;
+						height: auto;
+						min-height: 56rpx;
+						display: flex;
+						align-items: center;
+						justify-content: flex-start;
+
+						&::after {
+							border: none;
+						}
+						
+						&[disabled] {
+							opacity: 0.7;
+						}
+					}
+				}
+
+				.user-account {
+					height: 44rpx;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #333333;
+					line-height: 44rpx;
+					margin-top: 4rpx;
+				}
+			}
+
+			.department-badge {
+				width: 104rpx;
+				background: #1B64F0;
+				border-radius: 8rpx;
+				font-size: 24rpx;
+				color: #FFFFFF;
+				text-align: center;
+				margin-left: 8rpx;
+				padding: 7rpx 16rpx;
+				line-height: 1;
+			}
+		}
+	}
+
+	// 菜单区域
+	.menu-section {
+		padding: 0 20rpx;
+		margin-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; // 固定高度防止闪烁
+
+			&:last-child {
+				border-bottom: none;
+				margin-bottom: 0;
+			}
+
+			.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: #F52929;
+				border-radius: 32rpx;
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				.logout-text {
+					font-weight: 400;
+					font-size: 32rpx;
+					color: #FFFFFF;
+					text-align: center;
+				}
+			}
+		}
+	}
+}
+
+// 动画效果
+.menu-item {
+	transition: all 0.3s ease;
+	opacity: 1;
+
+	&:active {
+		background-color: rgba(66, 133, 244, 0.05);
+		transform: translateX(8rpx);
+	}
+	
+	&[disabled] {
+		opacity: 0.6;
+		pointer-events: none;
+	}
+}
+
+.department-badge {
+	transition: all 0.3s ease;
+
+	&:active {
+		transform: scale(0.95);
+	}
+}
+
+// 响应式适配
+@media (max-width: 375px) {
+	.menu-section {
+		padding: 0 20rpx !important;
+	}
+
+	.menu-card {
+		padding: 0 20rpx !important;
+	}
+
+	.menu-text {
+		font-size: 30rpx !important;
+	}
+}
+
+// 防止闪烁的全局样式
+view, text, image {
+	will-change: auto;
+	backface-visibility: hidden;
+	-webkit-backface-visibility: hidden;
+}
+</style>

+ 252 - 0
jd_logistics-app/pages/mine/mine1.vue

@@ -0,0 +1,252 @@
+<template>
+	<view class="mine_ybt bg_color_primary">
+		<view class="mine_ybt_top pad30">
+			<headerInfo title="个人中心"></headerInfo>
+			 <view class="flex-center-between color_fff mg20">
+				<u-avatar  @click="toUser"
+					size="100rpx"
+					shape="circle"
+					:src="appStore?.userInfo?.avatar"
+					mode="aspectFill"
+				></u-avatar>
+				<view class="flex_1 flex-center-between ml20">
+					<view class="">
+						<view class="font_size35">
+							<text v-if="appStore.userInfo?.userPhone" class="mr20"  @click="toUser">{{appStore.userInfo?.nickName}}</text>
+							<!-- <text v-else @click="toLogin()">请点击登录</text> -->
+							<button v-else
+								class="login-btn" 
+								open-type="getPhoneNumber"
+								@getphonenumber="getPhoneNumberFn"
+							>
+								请点击登录
+							</button>
+							<text v-if="appStore.userInfo?.memberStatus==1" class="memberPlanName">{{appStore.userInfo?.memberPlanName}}</text>
+						</view>
+						<view class="font_size25 mt20" v-if="appStore.userInfo?.memberExpire">
+							<!-- <text>{{appStore.userInfo?.memberPlanName}}</text> -->
+							<text class="vipTime">到期时间:{{appStore.userInfo?.memberExpire}}</text>
+						</view>
+					</view>
+					<view class="order_btn flex-center share_btn" @click="toUser">
+						<view class="flex-center border_radius_20">
+							<text>编辑资料</text>
+						</view>
+					</view>
+				</view>
+			 </view>
+			<view class="border_radius_20 padding20 vipCar flex-center-between">
+				<view class="flex_1 flex-center-flex-start mr20">
+					<image src="/static/img/mine/vipcard.png" mode="" class="vipcard-img mr10"></image>
+					<text style="color: rgb(246, 223, 185);">开通会员解锁更多权益</text>
+				</view>
+				<view class="order_btns jiesuo border_radius_20 font_size25" @click="toVip">
+					{{appStore.userInfo?.memberStatus==1?'查看权益' : '立即解锁'}}
+				</view>
+			</view>
+		</view>
+		<view class="mine_content padding30 mt20">
+			<!-- 我的订单 -->
+			<view class="font_size25 padding20 border_radius_20 bg_color_fff mb20">
+				<view class="flex-center-between paddingTB20 border-bottom menuList"
+				@click="toPage(item)"
+				 v-for="(item, index) in menuList" :key="index">
+					<view class="flex-center-flex-start mr20 flex_1">
+						<image :src="item.img" mode="" class="order-img mr20"></image>
+						<text>{{item.name}}</text>
+					</view>
+					<image src="/static/img/arrow-right.png" mode="widthFix" class="menu_img"></image>
+				</view>
+				<view class="flex-center-between paddingTB20 border-bottom menuList" @click="makePhoneCall">
+					<view class="flex-center-flex-start mr20 flex_1">
+						<image src="/static/img/mine/kefu.png" mode="" class="order-img mr20"></image>
+						<text>服务热线</text>
+					</view>
+					<image src="/static/img/arrow-right.png" mode="widthFix" class="menu_img"></image>
+				</view>
+				<view class="flex-center-between paddingTB20 border-bottom menuList" v-if="appStore.userInfo?.userPhone"
+				@click="userLogoutFn(item)">
+					<view class="flex-center-flex-start mr20 flex_1">
+						<image src="/static/img/mine/Logout.png" mode="" class="order-img mr20"></image>
+						<text>退出登录</text>
+					</view>
+					<image src="/static/img/arrow-right.png" mode="widthFix" class="menu_img"></image>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref } from "vue";
+import { userLogout } from "@/api/user.js";
+import { getAdServicePhone } from "@/api/home.js";
+import headerInfo from "@/components/headerInfo.vue";
+import { telEncrypt,checkLoginShowModal, getPhoneNumber } from "@/utils/util.js";
+import { onLoad, onShareAppMessage} from '@dcloudio/uni-app'
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+
+onShareAppMessage((res) => {
+  return appStore.onShareAppMessageObj
+})
+
+	const menuList = ref([
+		{
+			name: '我的钱包',
+			img: "/static/img/mine/money.png",
+			url: '/pages/recharge/wallet'
+		},
+		{
+			name: '去充值',
+			img: "/static/img/mine/vip.png",
+			url: '/pages/recharge/recharge'
+		},
+		// {
+		// 	name: '会员充值记录',
+		// 	img: "/static/img/mine/chongzhijulu.png",
+		// 	url: ''
+		// },
+		{
+			name: '我的订单',
+			img: "/static/img/mine/order.png",
+			url: '/pages/order/order_list'
+		}
+	]);
+
+onLoad(() => {
+	getAdServicePhoneFn();
+});
+async function toUser(){
+	if(!await checkLoginShowModal())return;
+	uni.navigateTo({
+		url: '/pages/user/user'
+	});
+}
+async function toVip(){
+	if(!await checkLoginShowModal())return;
+	uni.navigateTo({
+		url: '/pages/recharge/vip'
+	});
+}
+async function toPage(item){
+	if(!await checkLoginShowModal())return;
+	if(!item.url) return;
+	uni.navigateTo({
+		url: item.url
+	});
+}
+function getPhoneNumberFn(e){
+  getPhoneNumber(e)
+};
+// 退出登录
+function userLogoutFn(){
+	//确认退出登录吗
+	uni.showModal({
+		title: '提示',
+		content: '确认要退出登录吗?',
+		success: function (res) {
+			if (res.confirm) {
+				userLogout({}).then(res=>{ 
+					appStore.LOGOUT();
+				})
+			}
+		}
+	})
+}
+// 获取客服电话
+const phoneNumber = ref('');
+function getAdServicePhoneFn() {
+  getAdServicePhone({}).then(res => {
+    phoneNumber.value = res.data?.servicePhone || '';
+  }).catch(err => {
+    console.log(err);
+  });
+}
+// 拨打电话方法
+const makePhoneCall = () => {
+  if(!phoneNumber.value){
+    Toast({title:'请先配置客服电话'})
+    return;
+  }
+  uni.makePhoneCall({
+    phoneNumber:phoneNumber.value,
+    success: () => {
+      console.log('成功唤起拨号界面');
+    },
+    fail: (err) => {
+      console.error('唤起拨号界面失败', err);
+    }
+  });
+};
+</script>
+
+<style lang="less" scoped>
+.mine_ybt{
+	height: 100vh;
+	.vipCar{
+		margin-top: 30rpx;
+    background: linear-gradient(97.4139deg, rgb(137, 153, 191) 4.58393%, rgb(89, 88, 103) 97.8314%);
+		.jiesuo{
+			background: linear-gradient(94.5323deg, rgb(251, 239, 216) 10.1217%, rgb(241, 210, 164) 94.0504%);
+		}
+		.vipcard-img{
+			width: 80rpx;
+			height: 80rpx;
+		}
+	}
+	// background-size: 100% 100%;
+	.order_btns{
+		padding: 8rpx 20rpx;
+		display: inline-block;
+		&.active{
+			background: #fc4f1d;
+		}
+	}
+	.share_btn{
+		border:1px solid #ffffff;
+		padding: 10rpx 20rpx;
+		background: transparent;
+		.share_img{
+			width: 30rpx;
+		}
+	}
+	.mine_content{
+		border-radius: 30rpx 30rpx 0 0;
+		background: #f5f5f5;
+		min-height: calc(100vh - 200rpx);
+	}
+	.order-img{
+		width: 40rpx;
+		height: 40rpx;
+	}
+	.menu_img{
+		width: 50rpx;
+		height: 50rpx;
+	}
+	.menuList{
+		&:last-child{
+			border-bottom: none;
+		}
+	}
+	.vipTime{
+		opacity: .7;
+	}
+	/* 登录按钮 */
+	.login-btn {
+		background-color: transparent;
+		color: #fff;
+		font-size: 35rpx;
+		font-weight: bold;
+		text-align: left;
+	}
+  .memberPlanName{
+		background: linear-gradient(90deg, #FDE492    0%, #FDB85D  100%);
+		color: #874605 ;
+    padding: 5rpx 10rpx;
+    border-radius: 20rpx;
+    font-size: 24rpx;
+  }
+	
+}
+</style>

+ 370 - 0
jd_logistics-app/pages/order/components/OrderItem.vue

@@ -0,0 +1,370 @@
+<template>
+	<view class="card">
+		<view class="card-header">
+			<text class="waybill-number">运单号:JDVA401410370033</text>
+			<image src="/static/img/copy.png" @click="copyWaybillNumber"></image>
+		</view>
+
+		<view class="content">
+			<!-- 左侧寄件人信息 -->
+			<view class="sender-info">
+				<view class="city-tag">洛阳市</view>
+				<view class="name">下海龙</view>
+			</view>
+
+			<view class="translate">
+				<view class="status">待取件</view>
+				<image src="/static/img/translte-1.png"></image>
+			</view>
+
+			<!-- 右侧收件人信息 -->
+			<view class="receiver-info">
+				<view class="city-tag">北京市</view>
+				<view class="name">周欣源</view>
+			</view>
+		</view>
+
+		<!-- 状态信息 -->
+		<view class="section-continer">
+			<view class="timeline">
+				<view class="dot">
+					<view class="timeline-dot dot-active"></view>
+				</view>
+				<view class="timeline-line line-active"></view>
+			</view>
+			<view class="status-section">
+				<view class="status-badge status-collected">已代收</view>
+				<view class="status-detail">
+					快件已投递至【北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿】
+				</view>
+				<view class="status-time">2026-01-11 17:32:32</view>
+			</view>
+		</view>
+
+		<!-- 操作按钮 -->
+		<view class="action-buttons">
+			<view class="action-btn" @click="showExpressTrack">
+				<text class="btn-text">物流轨迹</text>
+			</view>
+			<view class="action-btn" @click="showOrderInfo">
+				<text class="btn-text">运单详情</text>
+			</view>
+			<view class="action-btn btn-cancel">
+				<text class="btn-text">运单取消</text>
+			</view>
+		</view>
+		
+		<!-- 物流轨迹弹框 -->
+		<TrackPopup 
+			v-model:showPopup="showTrackPopup" 
+			:expressData="expressData"
+		/>
+	</view>
+</template>
+
+<script setup>
+	import { ref, defineProps, defineEmits } from 'vue'
+	import TrackPopup from './TrackPopup.vue'
+
+	const emit = defineEmits(['success'])
+	const props = defineProps({
+		isGrab: {
+			type: Boolean,
+			default: false
+		},
+		orderDetail: {
+			type: Object,
+			default: () => ({
+				goodsMainImage: null,
+				goodsName: null,
+				id: undefined,
+				orderCreateTime: '',
+				shopConfirmStatus: "",
+				shopEvaluateStatus: '',
+				shopDispatchVerifyStatus: "",
+				shopGoodsId: undefined,
+				shopOrderAmount: undefined,
+				shopOrderId: "",
+				shopOrderNum: 1,
+				shopOrderStatus: "1",
+				shopPrice: 102,
+				shopRefundReviewStatus: null,
+				shopServiceStatus: null,
+				shopUserTime: null,
+				userId: "1989219383070896130",
+				userNickName: "bonjour",
+				userPhone: "17639845061"
+			})
+		}
+	})
+	
+	// 控制弹框显示
+	const showTrackPopup = ref(false)
+	const expressData = ref({
+		company: '顺丰速运',
+		number: 'JDVA401410370033',
+		status: '待取件'
+	})
+	
+	// 显示物流轨迹弹框
+	const showExpressTrack = () => {
+		// 这里可以调用API获取物流信息
+		// 暂时使用模拟数据
+		showTrackPopup.value = true
+	}
+	
+	const showOrderInfo = () => {
+		uni.navigateTo({
+			url:'/pages/order/order_detail'
+		})
+	}
+	
+	// 复制运单号
+	const copyWaybillNumber = () => {
+		uni.setClipboardData({
+			data: 'JDVA401410370033',
+			success: () => {
+				uni.showToast({
+					title: '运单号已复制',
+					icon: 'success'
+				})
+			}
+		})
+	}
+</script>
+
+<style scoped lang="less">
+	.card {
+		background-color: #fff;
+		border-radius: 32rpx;
+		margin-bottom: 20rpx;
+		padding: 20rpx;
+
+		.card-header {
+			height: 45rpx;
+			margin-bottom: 20rpx;
+			display: flex;
+			align-items: center;
+
+			.waybill-number {
+				height: 100%;
+				line-height: 45rpx;
+				font-size: 28rpx;
+				color: #333;
+				font-weight: 500;
+			}
+
+			image {
+				width: 50rpx;
+				height: 50rpx;
+				padding: 10rpx;
+				margin-left: 8rpx;
+				flex-shrink: 0;
+			}
+		}
+
+		.content {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			margin-bottom: 20rpx;
+
+			.sender-info,
+			.receiver-info {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				min-width: 0; /* 防止flex子元素溢出 */
+
+				.city-tag {
+					max-width: 250rpx;
+					height: 52rpx;
+					font-size: 36rpx;
+					color: #333333;
+					line-height: 52rpx;
+					font-weight: bold;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+
+				.name {
+					max-width: 250rpx;
+					height: 44rpx;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #333333;
+					line-height: 44rpx;
+					margin-top: 8rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+			}
+
+			.translate {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				width: 160rpx;
+				flex-shrink: 0;
+
+				.status {
+					height: 44rpx;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #333333;
+					line-height: 44rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+					max-width: 100%;
+				}
+
+				image {
+					height: 28rpx;
+					width: 160rpx;
+					margin-top: 8rpx;
+					flex-shrink: 0;
+				}
+			}
+		}
+
+		.section-continer {
+			display: flex;
+			background-color: #F5F7FA;
+			border-radius: 16rpx;
+			padding: 20rpx;
+			margin-bottom: 20rpx;
+			
+			.timeline {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				width: 34rpx;
+				margin-right: 20rpx;
+				flex-shrink: 0;
+				
+				.dot {
+					width: 34rpx;
+					height: 34rpx;
+					border-radius: 50%;
+					background-color: #C1D5FF;
+					display: flex;
+					justify-content: center;
+					align-items: center;
+					flex-shrink: 0;
+				}
+			
+				.timeline-dot {
+					width: 18rpx;
+					height: 18rpx;
+					border-radius: 50%;
+					
+					&.dot-active {
+						background-color: #1B64F0;
+					}
+				}
+			
+				.timeline-line {
+					flex: 1;
+					width: 4rpx;
+					box-sizing: border-box;
+					margin-top: 10rpx;
+					
+					&.line-active {
+						border-left: 4rpx dashed #1B64F0;
+					}
+				}
+			}
+			
+			.status-section {
+				flex: 1;
+				min-width: 0;
+			
+				.status-badge {
+					margin-bottom: 8rpx;
+					height: 48rpx;
+					font-weight: 400;
+					font-size: 32rpx;
+					color: #333333;
+					line-height: 48rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+			
+				.status-detail {
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #333333;
+					line-height: 44rpx;
+					margin-top: 8rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+			
+				.status-time {
+					margin-top: 8rpx;
+					height: 40rpx;
+					font-weight: 400;
+					font-size: 24rpx;
+					color: #999999;
+					line-height: 40rpx;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+			}
+		}
+		
+		.action-buttons {
+			margin-top: 20rpx;
+			display: flex;
+			justify-content: flex-end;
+
+			.action-btn {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				width: 160rpx;
+				height: 60rpx;
+				border-radius: 80rpx;
+				border: 2rpx solid #F1F3F8;
+				margin-left: 20rpx;
+		
+				&:first-child {
+					margin-left: 0;
+				}
+
+				&:last-child {
+					margin-right: 0;
+				}
+
+				.btn-text {
+					font-size: 28rpx;
+					color: #333;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+
+				&.btn-cancel {
+					background-color: #fff5f5;
+
+					.btn-text {
+						color: #ff6b6b;
+					}
+				}
+				
+				/* 添加点击效果 */
+				&:active {
+					opacity: 0.7;
+				}
+			}
+		}
+	}
+</style>

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

@@ -0,0 +1,152 @@
+<template>
+	<u-popup :show="showPopup" mode="center" round="20" @close="closePopup" :closeable="true"
+		:safeAreaInsetBottom="false" :closeOnClickOverlay="true">
+		<view class="express-popup">
+			<!-- 弹框头部 -->
+			<view class="popup-header">
+				<text class="title">物流轨迹</text>
+
+			</view>
+
+			<!-- 右上角关闭按钮 -->
+			<view class="close-btn" @tap="closePopup">
+				<u-icon name="close" size="22" color="#333"></u-icon>
+			</view>
+			<!-- 物流轨迹时间线 -->
+			<scroll-view class="timeline-container" scroll-y="true">
+				<TimelineItem v-for="(item, index) in trackList" :key="index" :title="item.title" :desc="item.desc"
+					:time="item.time" :is-active="item.isActive" :show-line="index < trackList.length - 1"
+					:show-dot-bg="index == 0" />
+			</scroll-view>
+		</view>
+	</u-popup>
+</template>
+
+<script setup>
+	import {
+		ref,
+		defineProps,
+		defineEmits
+	} from 'vue'
+	import TimelineItem from '@/components/TimelineItem.vue'
+
+	const props = defineProps({
+		showPopup: {
+			type: Boolean,
+			default: false
+		},
+		expressData: {
+			type: Object,
+			default: () => ({
+				company: '顺丰速运',
+				number: 'SF1234567890123',
+				status: '已签收'
+			})
+		}
+	})
+
+	const emit = defineEmits(['update:showPopup', 'close'])
+
+	// 模拟物流轨迹数据,实际应用中应该通过API获取
+	const trackList = ref([{
+			title: '已代收',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿...】',
+			time: '2026-01-11 17:32:32',
+			isActive: true
+		},
+		{
+			title: '派送中',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿...】',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '运输中',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿.北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿..】',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿.北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿..】',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿.北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿..】',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿.北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿北京海淀区北京大学菜鸟驿..】',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		},
+		{
+			title: '已揽件',
+			desc: '快件已投递至【北京海淀区北京大学菜鸟驿..北京海淀区北京大学菜鸟驿.】',
+			time: '2026-01-11 17:32:32',
+			isActive: false
+		}
+	])
+
+	const closePopup = () => {
+		emit('update:showPopup', false)
+		emit('close')
+	}
+</script>
+
+<style scoped lang="less">
+	.express-popup {
+		width: 670rpx;
+		background-color: #ffffff;
+		border-radius: 32rpx;
+		padding: 32rpx;
+		max-height: 88vh;
+		display: flex;
+		flex-direction: column;
+	}
+
+	/* 头部样式 */
+	.popup-header {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		padding-bottom: 20rpx;
+
+
+		.title {
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #333333;
+		}
+	}
+
+	.close-btn {
+		position: absolute;
+		right: 32rpx;
+		top: 40rpx;
+	}
+
+	/* 时间线容器 */
+	.timeline-container {
+		overflow-y: auto;
+		max-height: calc(88vh - 120rpx);
+	}
+
+	/* 弹框样式调整 */
+	:deep(.u-popup) {
+		display: flex;
+		justify-content: center;
+		align-items: flex-end;
+	}
+
+	:deep(.u-popup__content) {
+		width: 90% !important;
+		max-width: 670rpx !important;
+		/* 限制最大宽度,避免在宽屏上过宽 */
+		margin: 0 auto;
+	}
+</style>

+ 385 - 0
jd_logistics-app/pages/order/index.vue

@@ -0,0 +1,385 @@
+<template>
+	<view class="container">
+		<!-- 搜索框 -->
+		<view class="search-section">
+			<u-search v-model="searchKeyword" placeholder="请输入订单编号或用户名称" :show-action="false" shape="square"
+				bg-color="#F5F7FA" @search="handleSearch" @clear="handleSearch"></u-search>
+		</view>
+
+		<!-- Tab切换 -->
+		<!-- <view class="tabs-section"> -->
+		<!-- 	<view class="custom-tabs">
+				<view class="tab-item" :class="{ active: currentTab == index }" v-for="(tab, index) in tabList"
+					:key="index" @click="switchTab(index)">
+					{{ tab.name }}
+				</view>
+			</view> -->
+		<!-- </view> -->
+
+		<!-- 订单列表 -->
+		<scroll-view class="order-list" scroll-y="true" @scrolltolower="loadMore">
+			<view v-if="ordersList.length > 0" v-for="order in ordersList" :key="order.id">
+				<OrderItem :order-detail="order" @success="getOrderList(false)"></OrderItem>
+			</view>
+			<view class="empty-state" v-else>
+				<u-icon class="empty-icon" name="list" size="60" color="#ccc"></u-icon>
+				<view class="empty-text">暂无相关订单</view>
+			</view>
+
+			<view class="load-more-status" v-if="loadFinished && ordersList.length !== 0">
+				<text>没有更多数据了</text>
+			</view>
+
+			<!-- 加载状态提示 -->
+			<view class="load-more-status" v-if="loadState">
+				<text>加载中...</text>
+			</view>
+
+		</scroll-view>
+
+
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		computed
+	} from 'vue'
+	import {
+		onShow,
+		onLoad
+	} from '@dcloudio/uni-app' // 导入 UniApp 的生命周期
+	import OrderItem from './components/OrderItem.vue'
+	import { getOrderListApi } from '../../api/order'
+
+	// 搜索关键词
+	const searchKeyword = ref('')
+	const pageNum = ref(1)
+	const pageSize = ref(10)
+	const recordTotal = ref(0)
+	const loadState = ref(false)
+	const loadFinished = ref(false)
+
+	// Tab数据
+	const tabList = ref([{
+			name: '全部订单'
+		},
+		{
+			name: '待确认'
+		},
+		{
+			name: '已完成'
+		},
+		{
+			name: '申请退款'
+		}
+	])
+	// 1-全部订单, 2-待确认,  4-已完成, 5-申请退款
+	//orderType: 订单类型(1-全部订单, 2-待确认, 3-待核销, 4-已完成, 5-申请退款, 6-服务中)
+	const orderTypeMap = {
+		0: 1, // 全部订单
+		1: 2, // 待确认 
+		2: 4, // 已完成  
+		3: 5, // 申请退款  
+	}
+
+	// 当前选中的Tab
+	const currentTab = ref(0)
+
+	// 订单数据
+	const ordersList = ref([{},{},{},{},{}])
+
+
+	// 获取状态类名
+	const getStatusClass = (status) => {
+		switch (status) {
+			case 0:
+				return 'status-pending' // 待派单
+			case 1:
+				return 'status-assigned' // 已派单
+			case 2:
+				return 'status-completed' // 已完成
+			case 3:
+				return 'status-market' // 抢单市场
+			default:
+				return 'status-pending'
+		}
+	}
+
+	onLoad((option) => {
+		if (option.orderStatusType) {
+			currentTab.value = option.orderStatusType
+		}
+	})
+
+	onShow(() => {
+		// 可以在这里初始化数据
+		getOrderList(false)
+	})
+
+	// Tab切换事件
+	const switchTab = (index) => {
+		currentTab.value = index
+		getOrderList(false)
+	}
+	const loadMore = () => {
+		pageNum.value++
+		getOrderList(true)
+	}
+
+	const handleSearch = () => {
+		getOrderList(false)
+	}
+
+	// 获取收益明细列表
+	const getOrderList = async (isLoadMore = false) => {
+		return
+		if (loadState.value) return
+
+		if (!isLoadMore) {
+			pageNum.value = 1
+			loadFinished.value = false
+			ordersList.value = []
+		}
+		loadState.value = true
+		//orderType: 订单类型(1-全部订单, 2-待确认,  4-已完成, 5-申请退款)
+		try {
+			uni.showLoading({
+				mask: true
+			})
+			const orderType = orderTypeMap[currentTab.value]
+			const params = {
+				pageNum: pageNum.value,
+				pageSize: pageSize.value,
+				orderType: orderType,
+				keyword: searchKeyword.value
+			}
+			var res = await getOrderListApi(params)
+			if (res.code === 200) {
+				const list = res.rows || []
+				const total = res.total || 0
+
+
+				if (isLoadMore) {
+					ordersList.value = [...ordersList.value, ...list]
+				} else {
+					ordersList.value = list
+				}
+				recordTotal.value = total
+				// 判断是否还有更多数据
+				if (list.length < pageSize.value) {
+					loadFinished.value = true
+				}
+			}
+		} catch (error) {
+
+		} finally {
+			loadState.value = false
+			uni.hideLoading()
+		}
+	}
+
+
+	// 查看订单详情
+	const viewOrderDetail = (order) => {
+		uni.showToast({
+			title: `查看订单详情 - ${order.orderNumber}`,
+			icon: 'none'
+		})
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		display: flex;
+		flex-direction: column;
+		height: 100vh;
+		background-color: #F5F7FA;
+	}
+
+
+	.search-section {
+		background-color: #fff;
+		padding: 16rpx;
+		margin-left: 14rpx;
+
+		:deep .u-search__content {
+			border-radius: 36rpx !important;
+		}
+
+	}
+
+	.page-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		text-align: center;
+		margin-bottom: 30rpx;
+		color: #333333;
+	}
+
+	.tabs-section {
+		height: 88rpx;
+		background-color: #ffffff;
+	}
+
+	.custom-tabs {
+		display: flex;
+		width: 100%;
+	}
+
+	.tab-item {
+		flex: 1;
+		text-align: center;
+		padding: 30rpx 0;
+		font-size: 28rpx;
+		color: #333333;
+		position: relative;
+		transition: all 0.3s;
+	}
+
+	.tab-item.active {
+		color: #0089FF;
+	}
+
+	.tab-item.active::after {
+		content: '';
+		position: absolute;
+		bottom: 0;
+		left: 50%;
+		transform: translateX(-50%);
+		width: 40rpx;
+		height: 6rpx;
+		background-color: #2979ff;
+		border-radius: 4rpx;
+	}
+
+	.order-list {
+		flex: 1;
+		padding: 20rpx;
+		min-height: 500rpx;
+		background-color: #F5F7FA;
+		box-sizing: border-box;
+	}
+
+	.order-card {
+		background-color: #ffffff;
+		border-radius: 16rpx;
+		padding: 16rpx;
+		margin-bottom: 16rpx;
+		position: relative;
+	}
+
+	.order-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 20rpx;
+	}
+
+	.order-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333333;
+	}
+
+	.order-status {
+		width: 136rpx;
+		height: 54rpx;
+		line-height: 54rpx;
+		color: #fff;
+		font-size: 24rpx;
+		border-radius: 0rpx 16rpx 0rpx 16rpx;
+		position: absolute;
+		right: 0px;
+		top: 0px;
+		text-align: center;
+	}
+
+	.status-pending {
+		background: linear-gradient(270deg, #49ABFF 0%, #0089FF 100%);
+	}
+
+	.status-assigned {
+		background: linear-gradient(134deg, #5ED89B 0%, #36B475 99%);
+	}
+
+	.status-completed {
+		background: linear-gradient(134deg, #5ED89B 0%, #36B475 99%);
+	}
+
+	.status-market {
+		background: linear-gradient(90deg, #FF9F23 0%, #FD5F3C 100%);
+	}
+
+	.price-section {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 16rpx;
+	}
+
+	.order-price {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #FD5F3C;
+	}
+
+	.countdown {
+		font-size: 24rpx;
+		color: #F52929;
+	}
+
+	.order-details {
+		margin-bottom: 16rpx;
+		padding: 16rpx;
+		background: #F5F7FA;
+		border-radius: 16rpx;
+
+
+		.customer-info {
+			display: flex;
+			align-items: center;
+			margin-bottom: 16rpx;
+		}
+
+		.customer-photo {
+			width: 48rpx;
+			height: 48rpx;
+			border-radius: 48rpx;
+			margin-right: 16rpx;
+		}
+
+		.customer-name {
+			font-size: 24rpx;
+			color: #333333;
+			font-weight: bold;
+		}
+
+	}
+
+	.load-more-status {
+		text-align: center;
+		padding: 20rpx;
+		font-size: 24rpx;
+		color: #999;
+		position: fixed;
+		width: 100%;
+		height: 100%;
+	}
+
+
+	.empty-state {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		padding: 120rpx 40rpx;
+		color: #999999;
+	}
+
+	.empty-text {
+		margin-top: 20rpx;
+		font-size: 28rpx;
+	}
+</style>

+ 634 - 0
jd_logistics-app/pages/order/order_detail.vue

@@ -0,0 +1,634 @@
+<template>
+	<view class="verification-container">
+		<!-- 内容区域 -->
+		<view class="content">
+
+
+			<!-- 用户信息卡片 -->
+			<view class="info-card1">
+				<view class="address-item">
+					<view class="img-status-text">
+						<image src="/static/img/icon-send.png" mode="" class="img"></image>
+						<!-- <view class="status-text">{{getStatusText}}</view> -->
+					</view>
+					<view class="user-info">
+						<AddressInfo :address="addressSend"/>
+					</view>
+				</view>
+				<view class="address-item">
+					<view class="img-status-text">
+						<image src="/static/img/icon-receive.png" mode="" class="img"></image>
+						<!-- <view class="status-text">{{getStatusText}}</view> -->
+					</view>
+					<view class="user-info">
+						<AddressInfo :address="addressReceive"/>
+					</view>
+				</view>
+			</view>
+
+			<view class="action-title">运单信息</view>
+
+
+			<OrderInfo :order-detail="orderInfo"></OrderInfo>
+
+			<view class="action-title">费用信息</view>
+
+			<OrderFeesInfo :order-detail="orderInfo"/>
+
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		computed
+	} from 'vue'
+	import {
+		onLoad,
+		onShow
+	} from '@dcloudio/uni-app'
+	import OrderInfo from './components/OrderInfo.vue'
+	import OrderFeesInfo from './components/OrderFeesInfo.vue'
+	import AddressInfo from '@/components/AddressInfo.vue'
+
+
+
+	import {
+		useAppStore
+	} from '@/stores/app'
+	const appStore = useAppStore()
+
+	const shopOrderId = ref('')
+	
+	const addressSend = ref({
+			id: 1,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路97号',
+			isDefault: true
+		})
+	const addressReceive = ref({
+			id: 1,
+			name: '张三',
+			phone: '132****5678',
+			address: '广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路广东省深圳市罗湖区贝丽北路97号',
+			isDefault: true
+		})
+
+	const orderInfo = ref({
+		createTime: null,
+		goodsInfo: null,
+		goodsMainImage: null,
+		goodsName: "",
+		serviceAvatar: null,
+		serviceBelongShop: "",
+		serviceCommission: "",
+		serviceName: "",
+		servicePhone: "",
+		shopOrderAmount: "",
+		shopOrderId: "",
+		shopOrderNum: 1,
+		userName: "",
+		userPhone: "",
+		dispatchInfo: {},
+		goodsInfo: {},
+		grabInfo: {},
+		userInfo: {}
+	})
+
+	const showOrderCofirmBtn = ref(false)
+	const showOrderDispatchBtn = ref(false)
+	const showOrderRefundBtn = ref(false)
+	const showCallUserBtn = ref(true)
+
+
+	onLoad((option) => {
+		shopOrderId.value = option.id
+	})
+
+	onShow(() => {
+		// getOrderInfo()
+	})
+
+	const getOrderInfo = () => {
+		const param = {
+			shopOrderId: shopOrderId.value
+		}
+		getOrderInfoApi(param).then(res => {
+			if (res.code == 200) {
+				console.log('getOrderInfoApi=========', res)
+				orderInfo.value = res.data
+			}
+		})
+	}
+
+	// 返回按钮点击事件
+	const handleBack = () => {
+		uni.navigateBack()
+	}
+
+	// 确认核销按钮点击事件
+	const handleVerification = () => {
+		uni.showModal({
+			title: '确认核销',
+			content: '确定要核销此订单吗?',
+			success: (res) => {
+				if (res.confirm) {
+					uni.showToast({
+						title: '核销成功',
+						icon: 'success'
+					})
+					// 这里可以添加核销成功的后续逻辑
+				}
+			}
+		})
+	}
+
+
+	const makePhoneCall = (phoneNum) => {
+		// 这里以拨打10086为例,实际使用时可以替换为动态获取的电话号码
+
+		uni.makePhoneCall({
+			phoneNumber: phoneNum, // 电话号码
+			success: () => {
+				console.log('拨打电话成功')
+			},
+			fail: (err) => {
+				console.log('拨打电话失败:', err)
+			}
+		})
+	}
+
+
+
+	//确认订单是去派单状态
+	const updateOrderDispatch = () => {
+		if (!appStore.userInfo.orderDispatch) {
+			uni.showToast({
+				title: "当前会员不能使用此功,请升级为SVIP会员",
+				icon: "none"
+			})
+			return
+		}
+		uni.showModal({
+			title: '去派单',
+			content: '是否确认去派单?',
+			success: (res) => {
+				if (res.confirm) {
+					const params = {
+						shopOrderId: orderInfo.value.shopOrderId,
+						dispatchVerifyStatus: "0"
+					}
+					updateOrderDispatchApi(params).then(res => {
+						if (res.code == 200) {
+							uni.showToast({
+								title: '操作成功',
+								icon: 'success'
+							})
+							uni.navigateBack()
+						}
+					})
+				}
+			}
+		})
+
+	}
+
+
+	//直接核销订单
+	const updateOrderVerify = () => {
+		const params = {
+			shopOrderId: orderInfo.value.shopOrderId
+		}
+		updateOrderVerifyApi(params).then(res => {
+			if (res.code == 200) {
+				uni.showToast({
+					title: '操作成功',
+					icon: 'success'
+				})
+				uni.navigateBack()
+			}
+		})
+	}
+
+	//0 待服务 1 服务中 2 服务完成 3 待接单
+	const startServe = () => {
+
+		// 在实际UniApp项目中,使用以下代码:
+		uni.scanCode({
+			success: (res) => {
+				console.log(res, '=============scanCode==startServe=')
+				if (res.result == orderInfo.value.shopOrderId) {
+					//这里判断 grabId 是否相同 相同的话直接更新
+					updateOrderVerify()
+				} else {
+					uni.showModal({
+						content: "服务订单与扫码订单不一致",
+						confirmText: "确认",
+						showCancel: false
+					})
+				}
+			},
+			fail: (err) => {
+				uni.showToast({
+					title: '扫码失败',
+					icon: 'none'
+				})
+			}
+		})
+
+	}
+
+
+	const handleOrderRefund = (status) => {
+		if (status == '2') {
+			uni.showModal({
+				title: '拒绝退款',
+				content: '是否确认拒绝退款?',
+				success: (res) => {
+					if (res.confirm) {
+						updateOrderRefund(status)
+					}
+				}
+			})
+			return
+		}
+		uni.showModal({
+			title: '确认退款',
+			content: '是否确认退款?',
+			success: (res) => {
+				if (res.confirm) {
+					updateOrderRefundSure(status)
+				}
+			}
+		})
+	}
+
+
+	//处理退款订单  0 待审核 1 审核通过 2 审核拒绝
+	const updateOrderRefundSure = (status) => {
+		const params = {
+			shopOrderId: orderInfo.value.shopOrderId
+		}
+		updateOrderRefundSureApi(params).then(res => {
+			if (res.code == 200) {
+				uni.showToast({
+					title: '操作成功',
+					icon: 'success'
+				})
+				uni.navigateBack()
+				// emit('success');
+			}
+		})
+	}
+
+	//处理退款订单  0 待审核 1 审核通过 2 审核拒绝
+	const updateOrderRefund = (status) => {
+		const params = {
+			shopOrderId: orderInfo.value.shopOrderId,
+			reviewResult: status
+		}
+		updateOrderRefundApi(params).then(res => {
+			if (res.code == 200) {
+				uni.showToast({
+					title: '操作成功',
+					icon: 'success'
+				})
+				uni.navigateBack()
+			}
+		})
+	}
+
+	// 发布抢单
+	const publishOrder = () => {
+		uni.navigateTo({
+			url: '/pages/dispatchOrders/publishOrder?id=' + orderInfo.value.shopOrderId
+		})
+	}
+
+	// 直接派单
+	const assignOrder = () => {
+		uni.navigateTo({
+			url: '/pages/dispatchOrders/dispatch?id=' + orderInfo.value.shopOrderId
+		})
+	}
+
+
+	const reSetBtnStatus = () => {
+		showOrderCofirmBtn.value = false
+		showOrderDispatchBtn.value = false
+		showOrderRefundBtn.value = false
+		showCallUserBtn.value = false
+	}
+
+
+	//orderType: 订单类型(1-全部订单, 2-待确认, 3-待核销, 4-已完成, 5-申请退款, 6-服务中)
+	const getStatusText = computed(() => {
+		// shop_order_status	 商品订单状态(0 待支付  1 待使用 2 退款中  3 退款成功  4 退款失败   5  服务完成)	
+		// shop_evaluate_status  评价状态(0 待评价 1 已评价)	
+		// shop_refund_review_status  订单退款审核状态(0 待审核 1 审核通过 2 审核拒绝)			
+		// shop_service_status	服务状态:0=待服务,1=正在服务中,2=服务完成	
+		// shop_confirm_status	确认状态:0=待确认,1=已确认
+		// shop_dispatch_verify_status	派单核销状态:0=待派单,1=待核销,2=已派单,3=已核销
+
+		//dispatchInfo.shopOrderServiceStatus   0 待派单 1 已派单 2 待抢单  3 已抢单
+		reSetBtnStatus()
+
+
+		if (orderInfo.value.shopOrderStatus == '2') {
+			showOrderRefundBtn.value = true
+			return '申请退款'
+		}
+		if (orderInfo.value.shopOrderStatus == '3') {
+			showCallUserBtn.value = true
+			return '退款成功'
+		}
+
+		if (orderInfo.value.shopOrderStatus == '4') {
+			showCallUserBtn.value = true
+			return '退款失败'
+		}
+
+
+		if (orderInfo.value.shopOrderStatus == '0') {
+			showCallUserBtn.value = true
+			return '待支付'
+		}
+
+		if (orderInfo.value.shopConfirmStatus == '0') {
+			showOrderCofirmBtn.value = true
+			return '待确认'
+		}
+
+
+		if (orderInfo.value.shopOrderStatus == '5') {
+			if (orderInfo.value.shopEvaluateStatus == '0') {
+				showCallUserBtn.value = true
+				return '待评价'
+			}
+			if (orderInfo.value.shopEvaluateStatus == '1') {
+				showCallUserBtn.value = true
+				return '已评价'
+			}
+			return '服务完成'
+		}
+
+		//orderDetail.shopConfirmStatus == '1' 的时候才会有下边的状态值
+		if (orderInfo.value.shopOrderStatus == '1' && orderInfo.value.shopConfirmStatus == '1') {
+
+			if (orderInfo.value.dispatchInfo && orderInfo.value.dispatchInfo.shopOrderServiceStatus == '2') {
+				showCallUserBtn.value = true
+				return '待抢单'
+			}
+			if (orderInfo.value.dispatchInfo && orderInfo.value.dispatchInfo.shopOrderServiceStatus == '3') {
+				showCallUserBtn.value = true
+				return '已抢单'
+			}
+
+			switch (orderInfo.value.shopDispatchVerifyStatus) {
+				case '0':
+					//这里需要看下是否是有会员 派单权限
+					showOrderDispatchBtn.value = true
+					return '待派单' // 待派单
+				case '1':
+					showCallUserBtn.value = true
+					return '待核销' // 待核销
+				case '2':
+					showCallUserBtn.value = true
+					return '已派单' // 已派单
+				case '3':
+					showCallUserBtn.value = true
+					return '已核销' // 已核销
+			}
+			return '待使用'
+		}
+	})
+</script>
+
+<style lang="scss" scoped>
+	.verification-container {
+		min-height: 100vh;
+		background-color: #F5F7FA;
+		position: relative;
+		padding-bottom: 140rpx;
+		/* 为底部固定按钮预留空间 */
+
+		.content {
+			padding: 20rpx;
+		}
+
+		.verification-status {
+			height: 72rpx;
+			background: #FFF7EC;
+			border-radius: 16rpx 16rpx 16rpx 16rpx;
+			align-items: center;
+			display: flex;
+			padding-left: 16rpx;
+
+			image {
+				width: 28rpx;
+				height: 28rpx;
+			}
+
+			.status-text {
+				margin-left: 8rpx;
+				height: 40rpx;
+				font-weight: 400;
+				font-size: 24rpx;
+				color: #FFAF41;
+				line-height: 40rpx;
+			}
+		}
+
+		.action-title {
+			height: 48rpx;
+			font-weight: bold;
+			font-size: 32rpx;
+			color: #333333;
+			line-height: 48rpx;
+			margin: 20rpx 0rpx;
+		}
+
+		.info-card1 {
+			background: #fff;
+			border-radius: 32rpx;
+			padding: 20rpx;
+			margin-bottom: 16rpx;
+			// F1F3F8
+			.address-item{
+				display: flex;
+												
+				&:first-child {
+				    border-bottom: 2rpx solid #F1F3F8;
+					padding-bottom: 20rpx;
+				}
+				&:last-child {
+					padding-top: 20rpx;
+				}
+			}
+
+			.user-info {
+				flex: 1;
+				margin-left: 26rpx;
+			}
+
+			.img-status-text {
+				align-self: center;
+				text-align: center;
+				flex-shrink: 0;
+
+				.img {
+					width: 56rpx;
+					height: 56rpx;
+				}
+
+				.status-text {
+					font-size: 24rpx;
+					color: #0089FF;
+					font-size: 500;
+				}
+			}
+		}
+
+
+		.info-card {
+			background-color: #ffffff;
+			border-radius: 16rpx;
+			overflow: hidden;
+			padding: 16rpx;
+			margin-top: 16rpx;
+
+			.card-header {
+				display: flex;
+				align-items: center;
+
+				image {
+					width: 35rpx;
+					height: 35rpx;
+				}
+
+				.card-title {
+					height: 48rpx;
+					line-height: 48rpx;
+					font-weight: 400;
+					font-size: 32rpx;
+					font-weight: bold;
+					color: #333;
+					margin-left: 10rpx;
+				}
+			}
+
+		}
+
+		.verification-card-body {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+
+			image {
+				margin-top: 16rpx;
+				width: 320rpx;
+				height: 320rpx;
+			}
+		}
+
+		.verification-code {
+			padding: 20rpx 0;
+			display: flex;
+			align-items: center;
+
+			.code-label {
+				font-size: 28rpx;
+				color: #666;
+			}
+
+			.code-value {
+				font-size: 32rpx;
+				color: #333;
+				font-weight: bold;
+			}
+
+		}
+
+		.fixed-footer {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			background-color: #ffffff;
+			box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
+
+			.footer-buttons {
+				display: flex;
+				justify-content: center;
+				padding: 22rpx;
+
+				.back {
+					width: 238rpx;
+					height: 88rpx;
+					background: rgba(0, 137, 255, 0.1);
+					border-radius: 8rpx 8rpx 8rpx 8rpx;
+					font-family: PingFang SC, PingFang SC;
+					font-weight: 400;
+					font-size: 32rpx;
+					color: #0089FF;
+					line-height: 88rpx;
+					text-align: center;
+				}
+
+				.confirm {
+					width: 416rpx;
+					height: 88rpx;
+					margin-left: 32rpx;
+					align-items: center;
+					background: #0089FF;
+					border-radius: 16rpx 16rpx 16rpx 16rpx;
+					font-family: PingFang SC Bold, PingFang SC Bold;
+					font-weight: 400;
+					font-size: 32rpx;
+					color: #FFFFFF;
+					line-height: 88rpx;
+					text-align: center;
+				}
+
+				.call {
+					width: 327rpx;
+					height: 88rpx;
+					border-radius: 16rpx 16rpx 16rpx 16rpx;
+					font-family: PingFang SC, PingFang SC;
+					font-weight: 400;
+					font-size: 32rpx;
+					line-height: 88rpx;
+					text-align: center;
+				}
+
+				.call-user {
+					width: 100%;
+					height: 88rpx;
+					border-radius: 16rpx 16rpx 16rpx 16rpx;
+					font-family: PingFang SC, PingFang SC;
+					font-weight: 400;
+					font-size: 32rpx;
+					line-height: 88rpx;
+					text-align: center;
+					background: #0089FF;
+					color: #FFFFFF;
+					margin: 0rpx 32rpx;
+				}
+
+				.shop {
+					background: rgba(0, 137, 255, 0.1);
+					color: #0089FF;
+				}
+
+				.user {
+					margin-left: 32rpx;
+					background: #0089FF;
+					color: #FFFFFF;
+				}
+			}
+		}
+	}
+</style>

+ 71 - 0
jd_logistics-app/pages/policy/ad_detail.vue

@@ -0,0 +1,71 @@
+<template> 
+<view class="padding20 ad_detail">
+  <image :src="HTTP_ADMIN_URL+imageUrl" mode="" class="swiper_img border_radius_20"></image>
+  <view v-html="remark" class="padding20 border_radius_20 bg_color_fff mt20"></view>
+  <view class="mt20">
+    <view v-for="(item, index) in aiAgents" :key="index" 
+    @click="toAi({agentId:item.agentId})"
+    class="flex-center-between padding20 border_radius_20 bg_color_fff mt20">
+      <image :src="HTTP_ADMIN_URL+item.imageUrl" mode="" class="aiAgents_img border_radius_20 mr20"></image>
+      <view class="ml20 flex_1 flex-center-between">
+        <view class="flex_1 mr20">
+          <view class="bold font_size35 line2">{{item.agentName}}</view>
+          <view class="gray font_size25 line2">{{item.agentDesc}}</view>
+        </view>
+        <image src="/static/img/arrow-right.png" mode="widthFix" class="menu_img"></image>
+      </view>
+    </view>
+  </view>
+</view>
+</template>
+<script setup> 
+import { ref } from "vue";
+import { HTTP_ADMIN_URL } from "@/config/app.js";
+import { getAdDetails } from "@/api/home";
+import { onLoad } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+
+const imageUrl = ref('');
+const remark = ref('');
+const aiAgents = ref([]);
+
+onLoad(({dictCode}) => { 
+  getAdDetailsFn(dictCode);
+})
+function getAdDetailsFn(dictCode) { 
+  getAdDetails({dictCode}).then(res => {
+    if (res.code == 200) {
+      imageUrl.value = res.data?.sysDictData?.dictValue ||  '';
+      remark.value = res.data?.sysDictData?.remark || '';
+      aiAgents.value = res.data?.aiAgents || [];
+    }
+  })
+}
+function toAi({agentId='', msgContent=''}){
+	if(agentId) appStore.UPDATE_agentId(agentId);
+	if(msgContent)appStore.UPDATE_msgContent(msgContent);
+	uni.switchTab({
+		url: '/pages/ai/ai'
+	});
+}
+
+</script>
+<style lang="scss" scoped>
+  .ad_detail{
+    height: 100%;
+    .swiper_img{
+      width: 100%;
+      height: 350rpx;
+      background-color: #ffffff;
+    }
+    .aiAgents_img{
+      width: 100rpx;
+      height: 100rpx;
+    }
+    .menu_img{
+      width: 40rpx;
+      height: 40rpx;
+    } 
+  }
+</style>

+ 23 - 0
jd_logistics-app/pages/policy/recharge_policy.vue

@@ -0,0 +1,23 @@
+<template> 
+<view class="padding30">
+  <view v-html="info.content" class="padding30 border_radius_20 bg_color_fff"></view>
+</view>
+</template>
+<script setup> 
+import { ref } from "vue";
+import { agreementRecharge } from "@/api/agreement";
+import { onLoad } from "@dcloudio/uni-app";
+const info = ref({});
+
+onLoad(() => { 
+  agreementRechargeFn();
+})
+function agreementRechargeFn() { 
+  agreementRecharge().then(res => {
+    if (res.code == 200) {
+      info.value = res.data;
+    }
+  })
+}
+
+</script>

+ 23 - 0
jd_logistics-app/pages/policy/vip_policy.vue

@@ -0,0 +1,23 @@
+<template> 
+<view class="padding30">
+  <view v-html="info.content" class="padding30 border_radius_20 bg_color_fff"></view>
+</view>
+</template>
+<script setup> 
+import { ref } from "vue";
+import { agreementMember } from "@/api/agreement";
+import { onLoad } from "@dcloudio/uni-app";
+const info = ref({});
+
+onLoad(() => { 
+  agreementMemberFn();
+})
+function agreementMemberFn() { 
+  agreementMember().then(res => {
+    if (res.code == 200) {
+      info.value = res.data;
+    }
+  })
+}
+
+</script>

+ 144 - 0
jd_logistics-app/pages/recharge/recharge.vue

@@ -0,0 +1,144 @@
+<template>
+	<view class="recharge padding30">
+		<!-- 余额 -->
+		<view class="blance color_fff border_radius_20">
+			<view class="flex-center-between">
+				<view class="font_size30 mr20">余额 ( {{appStore.moneyUnit}} )</view>
+				<view class="font_size30" @click="toWallet">账户明细</view>
+			</view>
+			<view class="font_size60 bold mt30">{{appStore.userInfo?.rechargeBalance}}</view>
+		</view>
+		<!-- 充值金额 -->
+    <view class="padding30 border_radius_20 bg_color_fff mt20">
+      <view class="font_size30 bold">充值金额</view>
+      <view class="grid-container mt30">
+        <view class="flex-column-center bg_color_f5 border_radius_20 paddingTB20 rePrice"
+				:class="{active: ruleId==item.ruleId }"
+				@click="ruleId = item.ruleId;orderAmt = item.rechargeAmount"
+				 v-for="(item, index) in ruleList" :key="index">
+          <view class="bold font_size40 color_price">{{item.rechargeAmount}}</view>
+          <view class="font_size20 mt10 gray">{{item.offerTag}}</view>
+					<image src="/static/img/check.png" mode="widthFix" class="checked_img" v-if="ruleId==item.ruleId"></image>
+        </view>
+      </view>
+			<view class="mt30">
+				<view class="bold font_size25">{{rechargeRuleInfo.agreementName}}</view>
+				<view class="mt10 gary font_size25">
+					1.活动有效期为 {{ruleList[0]?.startTime}} 至 {{ruleList[0]?.endTime}}。
+				</view>
+				<view class="mt20 gary font_size25" v-html="rechargeRuleInfo.content"></view>
+			</view>
+    </view>
+		<agreeRecharge ref="agreeRecharge" @setAloneChecked="setAloneChecked"></agreeRecharge>
+		<!-- foot -->
+    <view class="foot bg_color_fff">
+			<view class="order_btn paddingTB20 flex_1 text_align_center" @click="submitForm">充值</view>
+		</view>
+	</view>
+</template>
+<script setup>
+import { ref } from "vue";
+import { getRuleList } from "@/api/user";
+import { createOrder } from "@/api/order";
+import { wxPay,getUserInfo } from "@/utils/util.js";
+import { agreementRechargeRule } from "@/api/agreement";
+import { agreeRecharge } from "@/components/agreeRecharge.vue"
+import { useToast } from "@/hooks/useToast";
+const { Toast } = useToast();
+import { onLoad } from "@dcloudio/uni-app";
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+const ruleList = ref([]);
+const orderId = ref('');
+const rechargeRuleInfo = ref('');
+const ruleId = ref('');
+const orderAmt = ref('');
+const aloneChecked = ref(false);
+
+onLoad(() => {
+	getRuleListFn();
+	agreementRechargeRuleFn();
+});
+async function submitForm(){
+	if(!aloneChecked.value){
+		Toast({ title: "请先阅读并同意充值协议", icon: 'none' });
+		return;
+	}
+	const res = await createOrder({
+		payMethod:0,//支付方式 0微信 1余额
+		productType:1,//商品类型 0购买会员 1充值
+		productId:ruleId.value,
+		orderNum:1,//订单数量
+		orderAmt:orderAmt.value//订单金额
+	});
+	//payData
+	orderId.value = res.data.orderId;
+	const payInfo = res.data.payData.prepayWithRequestPaymentResponse
+	wxPay({
+		timeStamp:payInfo.timeStamp,
+		nonceStr:payInfo.nonceStr,
+		packageVal:payInfo.packageVal,
+		signType:payInfo.signType,
+		paySign:payInfo.paySign,
+	},to_success_pay);
+}
+function to_success_pay({isSuccess}){
+	console.log('to_success_pay',isSuccess);
+	uni.navigateTo({
+    url: `/pages/recharge/success_pay?orderId=${orderId.value}&isSuccess=${isSuccess}&payMethod=0`
+  })
+}
+function toWallet(){
+	uni.navigateTo({
+		url: '/pages/recharge/wallet'
+	});
+}
+function getRuleListFn(){ 
+	getRuleList().then(res => {
+		if(res.code == 200){
+			ruleList.value = res.data || [];
+			if(ruleList.value.length > 0){
+				ruleId.value = ruleList.value[0].ruleId || '';
+				orderAmt.value = ruleList.value[0].rechargeAmount || '';
+			}
+		}
+	})
+}
+function agreementRechargeRuleFn(){ 
+	agreementRechargeRule().then(res => {
+		if(res.code == 200){
+			rechargeRuleInfo.value = res.data || {};
+		}
+	})
+}
+function setAloneChecked(value){
+  aloneChecked.value = value;
+}
+</script>
+<style lang="less" scoped>
+.recharge{
+  background: #f5f5f5;
+	.blance{
+		background-color: #fb7145;
+		padding: 30rpx 30rpx 60rpx;
+	}
+	.rePrice{
+		position: relative;
+		border: 1rpx solid transparent;
+		&.active{
+			border: 1rpx solid #fb7145;
+		}
+		.checked_img{
+			width: 40rpx;
+			height: 40rpx;
+			position: absolute;
+			top: 50%;
+			right: 20rpx;
+			transform: translateY(-50%);
+		}
+	}
+	.order_btn{
+		background-color: #fb7145;
+	}
+}
+</style>

+ 98 - 0
jd_logistics-app/pages/recharge/success_pay.vue

@@ -0,0 +1,98 @@
+<template>
+  <view class="pay-success-container">
+    <!-- 支付成功图标及状态 -->
+    <view class="icon-circle">
+      <image class="success-icon" src="/static/success_card.png"></image> 
+    </view>
+    <text class="title">{{title}}</text>
+    <text class="desc gray">{{msg}}</text>
+    <!-- 查看订单按钮 -->
+    <button class="check-order-btn bg_color_primary" @click="goBack">返回</button>
+  </view>
+</template>
+
+<script setup>
+import { ref,computed } from 'vue';
+import { payResult } from "@/api/order.js";
+import { getUserInfo } from "@/utils/util.js";
+import { onLoad } from "@dcloudio/uni-app";
+const orderId = ref('');
+const isSuccess = ref(1);//1成功 0失败;
+const payMethod = ref('');//支付方式 0微信 1余额
+
+const title = computed(()=>{
+	return isSuccess.value==1 ? '支付成功' : '支付失败'
+})
+const msg = computed(()=>{
+	return isSuccess.value==1 ? '非常感谢您购买我们的服务~' : '抱歉,支付失败,请稍后再试~'
+})
+
+onLoad((e)=>{
+	orderId.value = e.orderId || 0;
+	isSuccess.value = e.isSuccess;
+	payMethod.value = e.payMethod;
+  console.log("pay-success-onload",e);
+  getUserInfo();
+  if(payMethod.value == 0)payResultFn();
+})
+// 导航到订单详情页面
+function goBack() {
+  uni.navigateBack({
+    delta: 1
+  });
+}
+function payResultFn() {
+  payResult({
+    orderId: orderId.value,
+  }).then(res => {
+    console.log("payResult", res);
+  });
+}
+</script>
+
+<style scoped>
+.pay-success-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: start;
+  min-height: 100vh;
+  background-color: #f5f5f5;
+  padding: 0 20rpx;
+}
+.icon-circle {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 50%;
+  background-color: #ffd9d1;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 120rpx 0 40rpx;
+}
+.success-icon {
+  width: 80rpx;
+  height: 80rpx;
+}
+.title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+.desc {
+  font-size: 28rpx;
+  text-align: center;
+  margin-bottom: 60rpx;
+  line-height: 44rpx;
+}
+.check-order-btn {
+  /* width: 320rpx; */
+	width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  color: #fff;
+  font-size: 32rpx;
+  border-radius: 44rpx;
+}
+</style>

+ 295 - 0
jd_logistics-app/pages/recharge/vip.vue

@@ -0,0 +1,295 @@
+<template>
+	<view class="vip">
+    <view class="vip_top">
+      <headerInfo title="会员中心" :isBack="true"></headerInfo>
+      <view class="flex-center-between color_fff mg20">
+        <u-avatar  @click="toUser"
+					size="100rpx"
+					shape="circle"
+					:src="appStore?.userInfo?.avatar"
+					mode="aspectFill"
+				></u-avatar>
+        <view class="flex_1 flex-center-between ml20">
+          <view class="flex-column flex_1">
+            <view>
+              <text class="font_size32 nickName bold mr20">{{appStore.userInfo?.nickName}}</text>
+              <text class="memberPlanName" v-if="appStore.userInfo?.memberStatus==1">{{appStore.userInfo?.memberPlanName}}</text>
+            </view>
+            <text class="font_size32 nickName mt10" v-if="appStore.userInfo?.memberStatus==1">
+							<text class="vipTime">到期时间: {{appStore.userInfo?.memberExpire}}</text>
+            </text>
+            <text class="font_size32 nickName mt10" v-else>未开通VIP,无法享受会员</text>
+          </view>
+          <image src="/static/img/vip/vip.png" mode="" style="width:204rpx;height:204rpx;"/>
+        </view>
+      </view>
+    </view>
+    <view class="padding20 vip_content">
+      <!-- 会员充值 -->
+      <view class="flex-center mt10">
+        <view class="flex-center-between">
+          <view class="line_vip"></view>
+            <text class="title_vip font_size32 bold">会员充值</text>
+          <view class="line_vip reverse"></view>
+        </view>
+      </view>
+      <view class="mt20 vip_pay">
+        <view class="grid-container3 mt30">
+          <view class="flex-column-center border_radius_20 paddingTB20 plan" 
+          :class="{'active':planId==item.planId}"
+          @click="planId=item.planId;orderAmt=item.price;"
+          v-for="(item, index) in planList" :key="index">
+            <view class="bold font_size28 planName">{{item.planName}}</view>
+            <view class="font_size35 mt20 color_price"><text class="font_size20">¥</text>{{item.price}}</view>
+            <view class="font_size20 mt20 gray line_through">¥{{item.originalPrice}}</view>
+            <view class="bold font_size28 planName mt20">{{item.durationUnit=='lifetime'?'':item.duration}}{{item.durationUnitName}}</view>
+            <view class="hot" v-if="item.durationUnit=='lifetime'">
+              <image src="/static/img/vip/fire.png" mode="" style="width:20rpx;height:20rpx"/>
+              <text class="font_size20 color_fff">推荐</text>
+            </view>
+          </view>
+        </view>
+      </view>
+      <!-- 会员专享4大权益 -->
+      <view class="flex-center mt30">
+        <view class="flex-center-between">
+          <view class="line_vip"></view>
+            <text class="title_vip font_size32 bold">会员专享4大权益</text>
+          <view class="line_vip reverse"></view>
+        </view>
+      </view>
+      <view class="grid-container mt30">
+        <view class="flex-center-between border_radius_20 bg_color_fff padding20" v-for="(item, index) in quanyilList" :key="index">
+          <image :src="item.icon" class="icon mr20" mode="" />
+          <view class="flex_1">
+            <view class="bold font_size28">{{item.title}}</view>
+            <view class="font_size24 mt10 gray">{{item.content}}</view>
+          </view>
+        </view>
+      </view>
+     
+      <!-- 付款方式 -->
+      <view class="padding30 border_radius_20 bg_color_fff mt20"  v-if="isEdit!=1">
+        <view class="font_size32 bold mb20">付款方式</view>
+        <view class="flex-center-between mt20 paddingTB20" v-for="(item, index) in payments" :key="index" @click="payType=item.value">
+          <view class="flex-center-between">
+            <image :src="item.icon" mode="widthFix" class="weixin_img mr20"></image>
+            <text>{{item.label}}</text>
+            <text class="color_price ml20 font_size24" v-if="index==1"> 余额:{{appStore.userInfo?.rechargeBalance}} ({{appStore.moneyUnit}})</text>
+          </view>
+          <view class="payment-radio">
+            <view
+              class="radio-circle"
+              :class="{ checked: payType === item.value }"
+            ></view>
+          </view>
+        </view>
+      </view>
+      <agreeVip ref="agreeVip" @setAloneChecked="setAloneChecked"></agreeVip>
+    </view>
+    <!-- foot -->
+    <view class="foot bg_color_fff">
+			<view class="order_btn paddingTB20 flex_1 text_align_center" @click="submitForm">
+        <text>{{appStore.userInfo?.memberStatus==1?'续费':'立即解锁'}}</text>
+        <text>¥{{orderAmt}}</text>
+      </view>
+		</view>
+	</view>
+</template>
+<script setup>
+import { ref } from "vue";
+import { wxPay,getUserInfo } from "@/utils/util.js";
+import headerInfo from "@/components/headerInfo.vue";
+import { createOrder } from "@/api/order";
+import { useToast } from "@/hooks/useToast";
+const { Toast } = useToast();
+import { getPlanList } from "@/api/user";
+import { agreeVip } from "@/components/agreeVip"
+import { onLoad } from '@dcloudio/uni-app'
+import { useAppStore } from "@/stores/app"
+const appStore = useAppStore();
+const aloneChecked = ref(false);
+const planId = ref('');
+const orderId = ref('');
+const orderAmt = ref('');
+const planList = ref([]);
+const payType = ref(0);
+const payments = ref([
+  {
+    value: 0,
+    label: "微信支付",
+		icon: "/static/img/weixin.png"
+  },
+  {
+    value: 1,
+    label: "钱包支付",
+		icon: "/static/img/qianbao.png"
+  },
+]);
+const quanyilList = ref([
+  {
+    title: "问答无限制",
+    content: "最新AI聊天模型接口",
+    icon: "/static/img/vip/question.png"
+  },
+  {
+    title: "学习帮手",
+    content: "帮您提高创作效率",
+    icon: "/static/img/vip/xuexi.png"
+  },
+  {
+    title: "疑问解答",
+    content: "互动性聊天体验",
+    icon: "/static/img/vip/yiwen.png"
+  },
+  {
+    title: "私人助理",
+    content: "提供最优解决方案",
+    icon: "/static/img/vip/siren.png"
+  },
+]);
+async function submitForm(){
+  if(!aloneChecked.value){
+		Toast({ title: "请先阅读并同意VIP会员开通协议", icon: 'none' });
+		return;
+	}
+  const res = await createOrder({
+		payMethod:payType.value,//支付方式 0微信 1余额
+		productType:0,//商品类型 0购买会员 1充值
+		productId:planId.value,
+		orderNum:1,//订单数量
+		orderAmt:orderAmt.value//订单金额
+	});
+  orderId.value = res.data.orderId;
+  // 余额支付
+  if(payType.value==1){
+    //1成功 0失败;
+    const paySuccess = res.data.paySuccess;
+    to_success_pay({isSuccess:paySuccess?1:0});
+  }else{ 
+    const payInfo = res.data.payData.prepayWithRequestPaymentResponse
+    wxPay({
+      timeStamp:payInfo.timeStamp,
+      nonceStr:payInfo.nonceStr,
+      packageVal:payInfo.packageVal,
+      signType:payInfo.signType,
+      paySign:payInfo.paySign,
+    },to_success_pay);
+  }
+}
+function to_success_pay({isSuccess}){
+	console.log('to_success_pay',isSuccess);
+	uni.navigateTo({
+    url: `/pages/recharge/success_pay?orderId=${orderId.value}&isSuccess=${isSuccess}&payMethod=${payType.value}`
+  })
+}
+onLoad(() => {
+	getPlanListFn();
+});
+function getPlanListFn(){
+	getPlanList().then(res => {
+		if(res.code == 200){
+			planList.value = res.data || [];
+      //默认单位是durationUnit=='lifetime'
+      if(planList.value.length>0){
+        let plan = planList.value.find(item=>item.durationUnit=='lifetime');
+        planId.value = plan?.planId || planList.value[0].planId;
+        orderAmt.value = plan?.price || planList.value[0].price;
+      }
+		}
+	})
+}
+function setAloneChecked(value){
+  aloneChecked.value = value;
+}
+async function toUser(){
+	uni.navigateTo({
+		url: '/pages/user/user'
+	});
+}
+</script>
+<style lang="less" scoped>
+.vip{
+  height: 100vh;
+  background: #f5f5f5;
+  
+  .vip_top{
+    //#51402E #2B2724 渐变色,从上向下
+    background: linear-gradient(90deg, #51402E 0%, #2B2724 100%);
+    padding: 0 0 50rpx 30rpx;
+    .nickName{
+      color: #DDD3D0;
+    }
+  }
+  .vip_content{
+    position: relative;
+    top: -40rpx;
+    border-radius: 48rpx 48rpx 0 0;
+    background: #f5f5f5;
+    padding-bottom: 100rpx;
+    .title_vip{
+      color: #333333 ;
+      margin: 0 30rpx;
+    }
+    .line_vip{
+      width: 92rpx;
+      height: 6rpx;
+      background: linear-gradient(90deg, #D8D8D8  0%, #333333 100%);
+      &.reverse{
+        transform: rotate(180deg);
+      }
+    }
+  }
+  .gray{
+    color: #999;
+  }
+  .grid-container{
+    gap: 20rpx;
+    .icon{
+      width: 70rpx;
+      height: 70rpx;
+    }
+  }
+  .vip_pay{
+    .plan{
+      padding: 30rpx 20rpx 40rpx;
+      border:1px solid #CCCCCC ;
+      position: relative;
+      background-color: #ffffff;
+      &.active{
+        background-color: #FEF7EA ;
+        border-color: #874605 
+      }
+      .color_price{
+        color: #874605;
+      }
+      .planName{
+        color: #333333;
+      }
+      .hot{
+        position: absolute;
+        top: -20rpx;
+        left: 0rpx;
+        background: linear-gradient(90deg, #2B2724   0%, #503F2E  100%);
+        color: #fff;
+        font-size: 20rpx;
+        padding: 5rpx 10rpx;
+        border-radius: 10rpx 0 10rpx 0;
+      }
+    }
+  }
+  .weixin_img{
+    width: 60rpx;
+    height: 60rpx;
+  }
+  .order_btn ,.memberPlanName{
+		background: linear-gradient(90deg, #FDE492    0%, #FDB85D  100%);
+    color: #874605 ;
+	}
+  .memberPlanName{
+    padding: 5rpx 10rpx;
+    border-radius: 20rpx;
+    font-size: 24rpx;
+  }
+}
+</style>

+ 100 - 0
jd_logistics-app/pages/recharge/wallet.vue

@@ -0,0 +1,100 @@
+<template>
+<view class="wallet">
+	<z-paging
+	ref="pagingRef"
+	class="paginng-contaner"
+	:default-page-size="20"
+	:use-refresher-status-bar-placeholder="true"
+	v-model="list"
+	@query="handleQuery"
+	>
+		<!-- 余额 -->
+		<view class="blance">
+			<view class="flex-center-between">
+				<view class="color_fff">
+					<text class="font_size60 bold mr20">{{appStore.userInfo?.rechargeBalance}}</text>
+					<text class="font_size30">{{appStore.moneyUnit}}</text>
+				</view>
+				<view class="order_btn plain chongzhi font_size30" @click="toRecharge">充值</view>
+			</view>
+			<view class="color_fff font_size25 mt20">账户余额 ( {{appStore.moneyUnit}} )</view>
+		</view>
+		<!-- 账户明细 -->
+		<view class="padding30 accountDetails bg_color_f5">
+			<view class="flex-center-flex-start mb20">
+				<view class="line_vertical"></view>
+				<view class="font_size30 bold">账户明细</view>
+			</view>
+			<view class="bg_color_fff border_radius_20 padding30">
+				<view class="flex-center-between paddingTB20 border_bottom" v-for="item in list" :key="item.recordId">
+					<view class="flex_1">
+						<!-- 交易类型(0=充值,1=消费,2=退款) -->
+						<view class="font_size25 bold">
+							{{item.transactionTypeName}}
+						</view>
+						<view class="font_size20 color_999 mt10">{{item.createTime}}</view>
+					</view>
+					<!-- 交易金额(充值/退款为正,消费为负) -->
+					<view class="flex-column-end">
+						<text class="font_size40 color_price">{{item.amount}}</text>
+						<text class="font_size25 gray">余额{{item.balanceAfter}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</z-paging>
+</view>
+</template>
+<script setup>
+import { ref } from "vue";
+import { getRecordList } from "@/api/user";
+import { useAppStore } from "@/stores/app"
+const appStore = useAppStore();
+const pagingRef = ref();
+const list = ref([]);
+// 下拉刷新和滚动底部会自动触发此方法
+async function handleQuery(page, pageSize, from) {
+  try {
+    const params = {
+      pageNum: page,
+      pageSize,
+			reasonable:false,
+			orderByColumn: 'createTime',
+			isAsc: 'desc'
+    };
+    const { rows } = await getRecordList(params);
+    pagingRef.value.complete(rows);
+  } catch (e) {
+		console.log('msg-comment',e)
+    pagingRef.value.complete(false);
+  }
+}
+function toRecharge(){
+	uni.navigateTo({
+		url: '/pages/recharge/recharge'
+	});
+}
+</script>
+<style lang="less" scoped>
+.wallet{
+	.blance{
+		padding: 80rpx 60rpx;
+		background-color: #fb7145;
+	}
+	.chongzhi{
+		color: #fb7145;
+		padding: 10rpx 40rpx;
+		border-radius: 50rpx;
+	}
+	.line_vertical{
+		background-color: #fb7145;
+		margin-right: 20rpx;
+	}
+	.accountDetails{
+		border-radius: 30rpx 30rpx 0 0;
+		position: relative;
+		top: -30rpx;
+		padding-bottom: 0;
+	}
+}
+</style>

+ 209 - 0
jd_logistics-app/pages/search/search.vue

@@ -0,0 +1,209 @@
+<template>
+	<view class="container">
+
+		<!-- 导航栏 -->
+		<view class="nav-bar">
+			<text class="title">查快递</text>
+		</view>
+
+		<!-- 主体内容 -->
+		<view class="main-content">
+			<!-- Logo 和公司名称 -->
+			<view class="company-section">
+				<view class="logo-placeholder">
+					<!-- 这里可以使用网络图片或本地图标 -->
+					<image class="logo" src="@/static/img/logo.png" mode="aspectFit" />
+				</view>
+				<!-- <text class="company-name">瑞鲸科技</text>
+        <text class="company-en-name">REJOIN</text> -->
+			</view>
+
+			<!-- 搜索区域 -->
+			<view class="search-section">
+				<view class="search-input-wrapper">
+					<image class="search-icon" src="@/static/img/search.png" />
+					<input class="search-input" placeholder="请输入要查询的运单号" placeholder-class="placeholder-style"
+						@focus="onInputFocus" @blur="onInputBlur" v-model="trackingNumber" />
+					<view class="search-btn" @click="handleSearch">
+						搜索
+					</view>
+				</view>
+
+				
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref
+	} from 'vue'
+
+	const trackingNumber = ref('')
+	const isInputFocused = ref(false)
+
+	const onInputFocus = () => {
+		isInputFocused.value = true
+	}
+
+	const onInputBlur = () => {
+		isInputFocused.value = false
+	}
+
+	const handleSearch = () => {
+		uni.navigateTo({
+			url:'/pages/logistics/index'
+		})
+		return
+		if (!trackingNumber.value.trim()) {
+			uni.showToast({
+				title: '请输入运单号',
+				icon: 'none'
+			})
+			return
+		}
+
+		// 这里处理搜索逻辑
+		console.log('搜索运单号:', trackingNumber.value)
+		uni.showLoading({
+			title: '查询中...'
+		})
+
+		// 模拟API调用
+		setTimeout(() => {
+			uni.hideLoading()
+			uni.showToast({
+				title: '查询成功',
+				icon: 'success'
+			})
+		}, 1500)
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		width: 100%;
+		min-height: 100vh;
+		background: linear-gradient( 135deg, #CFE9FF 0%, #F5F7FA 50.86%);
+		display: flex;
+		flex-direction: column;
+	}
+
+
+	/* 导航栏样式 */
+	.nav-bar {
+		margin-top: 100rpx;
+		width: 100%;
+		height: 52rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+
+		.title {
+			height: 52rpx;
+			line-height: 52rpx;
+			font-size: 36rpx;
+			font-weight: 600;
+			color: #333;
+		}
+	}
+
+	/* 主体内容 */
+	.main-content {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding-top: 300rpx;
+	}
+
+	/* 公司信息区域 */
+	.company-section {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		margin-bottom: 72rpx;
+
+		.logo-placeholder {
+			width: 244rpx;
+			height: 70rpx;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			.logo {
+				width: 100%;
+				height: 100%;
+			}
+		}
+
+		.company-name {
+			font-size: 40rpx;
+			font-weight: 600;
+			color: #000000;
+			margin-bottom: 8rpx;
+		}
+
+		.company-en-name {
+			font-size: 28rpx;
+			color: #666666;
+			letter-spacing: 2rpx;
+		}
+	}
+
+	/* 搜索区域 */
+	.search-section {
+		width: 100%;
+		padding: 0 32rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+
+		.search-input-wrapper {
+			width: 100%;
+			height: 88rpx;
+			background: #FFFFFF;
+			border-radius: 44rpx;
+			font-size: 28rpx;
+			color: #333;
+			border: 4rpx solid #1B64F0;
+			padding: 6rpx;
+			display: flex;
+			justify-content: center;
+			justify-items: center;
+			align-items: center;
+			
+			.search-icon{
+				width: 40rpx;
+				height: 40rpx;
+				margin-left: 18rpx;
+			}
+
+			.search-input {
+				flex: 1;
+				margin-left: 8rpx;
+				margin-right: 8rpx;
+			}
+
+			
+			.placeholder-style {
+				color: #999999;
+			}
+			
+			.search-btn {
+				width: 160rpx;
+				height: 76rpx;
+				background: #1B64F0;
+				border-radius: 38rpx;
+				font-weight: 400;
+				font-size: 32rpx;
+				color: #FFFFFF;
+				line-height: 76rpx;
+				text-align: center;
+				font-style: normal;
+			}
+				
+		}
+
+	}
+</style>

+ 110 - 0
jd_logistics-app/pages/user/user.vue

@@ -0,0 +1,110 @@
+<template>
+	<view class="user padding30">
+		
+		<view class="padding30 bg_color_fff border_radius_20">
+			<u-form labelPosition="left" :model="userInfo" :rules="rules" ref="formRef" labelWidth="auto">
+				
+				<u-form-item label="头像" prop="avatar" :borderBottom="true" required>
+					<view class="avatar flex-center-flex-end">
+						<button class="chooseAvatar" open-type="chooseAvatar" @chooseavatar="chooseAvatar">
+									<u-avatar 
+									size="120rpx"
+									shape="circle"
+									:src="userInfo.avatar"
+									mode="aspectFill"
+								></u-avatar>
+						</button>
+					</view>
+						
+				</u-form-item>
+				<u-form-item label="昵称" prop="nickName" :borderBottom="true" required>
+					<u-input v-model="userInfo.nickName" placeholder="请输入昵称" border="none" clearable inputAlign="right" type="nickname"></u-input>
+				</u-form-item>
+				<u-form-item label="手机号" :borderBottom="false" required>
+					<u-input v-model="userInfo.userPhone" placeholder="账号" border="none" clearable inputAlign="right" readonly></u-input>
+				</u-form-item>
+			</u-form>
+		</view>
+		<view class="foot bg_color_fff">
+			<view class="order_btn paddingTB20 flex_1 text_align_center" @click="submitForm">提交</view>
+		</view>
+	</view>
+</template>
+<script setup>
+import { ref,reactive } from 'vue'
+import { userEdit } from "@/api/user.js";
+import { telEncrypt,chooseImageOne,uploadFile } from "@/utils/util.js";
+import { useToast } from "@/hooks/useToast";
+const { Toast } = useToast();
+import { useAppStore } from "@/stores/app";
+const appStore = useAppStore();
+
+const formRef = ref(null);
+const dgtPopupRef = ref(null);
+const userInfo = ref({
+  userPhone: telEncrypt(appStore.userInfo?.userPhone),
+  nickName: appStore.userInfo?.nickName,
+  avatar: appStore.userInfo?.avatar,
+});
+const rules = reactive({
+  "avatar": [
+    { required: true, message: '请上传头像', trigger: ['blur', 'change'] },
+  ],
+  "nickName": [
+    { required: true, message: '请输入昵称', trigger: ['blur', 'change'] },
+  ]
+});
+// 提交表单
+async function submitForm(){
+  formRef.value
+	.validate()
+	.then((valid) => {
+		console.log('submitForm',valid);
+		if (valid) {
+			userEdit(userInfo.value).then(async res=>{ 
+				await appStore.USERINFO();
+				uni.navigateBack();
+			})
+		} else {
+			Toast({ title: "请完善个人资料" });
+		}
+	})
+	.catch((e) => {
+		console.log('error submit!!',e[0].message);
+		Toast({ title: e[0]?.message || "请完善个人资料!" });
+	});
+};
+
+async function uploadImage(){
+  const res = await chooseImageOne('user/userAvatarUpload');
+  console.log('uploadImage',res);
+  if(res){
+  	userInfo.value.avatar= res.data
+  }
+}
+async function chooseAvatar(e){
+	console.log('chooseAvatar',e);
+	if(e.detail?.avatarUrl){
+		const res = await uploadFile(e.detail?.avatarUrl,'user/userAvatarUpload');
+		console.log('uploadImage',res);
+		if(res){
+			userInfo.value.avatar= res.data
+		}
+	}
+	
+
+}
+</script>
+
+<style lang="scss">
+.user{ 
+	.foot{
+		.order_btn{
+			border-radius: 50rpx;
+		}
+	}
+	.avatar{
+		width: 100%;
+	}
+}
+</style>

+ 60 - 0
jd_logistics-app/pages/webView/webView.vue

@@ -0,0 +1,60 @@
+<template>
+  <view class="web-view-container">
+    <web-view :src="webUrl" @message="handleMessage" @error="handleError"></web-view>
+  </view>
+</template>
+
+<script setup>
+import { ref  } from 'vue';
+import { onBackPress,onLoad } from '@dcloudio/uni-app';
+
+// 页面参数
+const webUrl = ref('');
+
+// 页面加载时接收参数
+onLoad((options) => {
+  // 从跳转入参中获取url,如果没有则使用默认地址
+  if (options.url) {
+    // 解码URL,防止参数传递时的编码问题
+    webUrl.value = decodeURIComponent(options.url);
+  } else {
+    // 默认地址,可以根据实际需求修改
+    webUrl.value = 'about:blank';
+  }
+});
+
+// 返回上一页
+function goBack() {
+  uni.navigateBack();
+}
+
+// 处理web-view发送的消息
+function handleMessage(e) {
+  console.log('web-view message:', e);
+  // 可以根据实际需求处理来自web-view的消息
+}
+
+// 处理web-view加载错误
+function handleError(e) {
+  console.error('web-view load error:', e);
+  // 可以在这里添加错误处理逻辑,如显示错误页面等
+}
+
+// 监听返回按钮事件
+onBackPress(() => {
+  goBack();
+  return true;
+});
+</script>
+
+<style scoped lang="scss">
+.web-view-container {
+  width: 100%;
+  height: 100vh;
+  background-color: #f5f5f5;
+}
+web-view {
+  flex: 1;
+  width: 100%;
+}
+</style>

+ 13 - 0
jd_logistics-app/uni.promisify.adaptor.js

@@ -0,0 +1,13 @@
+uni.addInterceptor({
+  returnValue (res) {
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+      return res;
+    }
+    return new Promise((resolve, reject) => {
+      res.then((res) => {
+        if (!res) return resolve(res) 
+        return res[0] ? reject(res[0]) : resolve(res[1])
+      });
+    });
+  },
+});

+ 77 - 0
jd_logistics-app/uni.scss

@@ -0,0 +1,77 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+ @import '@/uni_modules/uview-plus/theme.scss';
+/* 颜色变量 */
+$theme-color:#3a7ffb;
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 3766 - 0
jd_logistics-app/utils/area-data.js


+ 227 - 0
jd_logistics-app/utils/cache.js

@@ -0,0 +1,227 @@
+import { EXPIRE } from "@/config/app";
+
+class Cache {
+  constructor(handler) {
+    this.cacheSetHandler = uni.setStorageSync;
+    this.cacheGetHandler = uni.getStorageSync;
+    this.cacheClearHandler = uni.removeStorageSync;
+    this.cacheExpire = "_expire_2019_12_17_18_44";
+    this.name = "storage";
+  }
+
+  /**
+   * 获取当前时间戳
+   */
+  time() {
+    return Math.round(new Date() / 1000);
+  }
+
+  /**
+   * 日期字符串转时间戳
+   * @param {Object} expiresTime
+   */
+  strTotime(expiresTime) {
+    let expires_time = expiresTime.substring(0, 19);
+    expires_time = expires_time.replace(/-/g, "/");
+    return Math.round(new Date(expires_time).getTime() / 1000);
+  }
+
+  setExpireCaheTag(key, expire) {
+    expire = expire !== undefined ? expire : EXPIRE;
+    if (typeof expire === "number") {
+      let tag = this.cacheGetHandler(this.cacheExpire),
+        newTag = [],
+        newKeys = [];
+      if (typeof tag === "object" && tag.length) {
+        newTag = tag.map((item) => {
+          newKeys.push(item.key);
+          if (item.key === key) {
+            item.expire = expire === 0 ? 0 : this.time() + expire;
+          }
+          return item;
+        });
+      }
+      if (!newKeys.length || newKeys.indexOf(key) === -1) {
+        newTag.push({
+          key: key,
+          expire: expire === 0 ? 0 : this.time() + expire,
+        });
+      }
+      this.cacheSetHandler(this.cacheExpire, newTag);
+    }
+  }
+
+  /**
+   * 设置过期时间缓存
+   * @param {Object} name key
+   * @param {Object} value value
+   * @param {Object} expire 过期时间
+   * @param {Object} startTime 记录何时将值存入缓存,毫秒级
+   */
+  setItem(params) {
+    let obj = {
+      name: "",
+      value: "",
+      expires: "",
+      startTime: new Date().getTime(),
+    };
+    let options = {};
+    //将obj和传进来的params合并
+    Object.assign(options, obj, params);
+    if (options.expires) {
+      //如果options.expires设置了的话
+      //以options.name为key,options为值放进去
+      // localStorage.setItem(options.name,JSON.stringify(options));
+      uni.setStorageSync(options.name, JSON.stringify(options));
+    } else {
+      //如果options.expires没有设置,就判断一下value的类型
+      let type = Object.prototype.toString.call(options.value);
+      //如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去
+      if (Object.prototype.toString.call(options.value) == "[object Object]") {
+        options.value = JSON.stringify(options.value);
+      }
+      if (Object.prototype.toString.call(options.value) == "[object Array]") {
+        options.value = JSON.stringify(options.value);
+      }
+      // localStorage.setItem(options.name,options.value);
+      uni.setStorageSync(options.name, options.value);
+    }
+  }
+
+  /**
+   * 缓存是否过期,过期自动删除
+   * @param {Object} key
+   * @param {Object} $bool true = 删除,false = 不删除
+   */
+  getExpireCahe(key, $bool) {
+    try {
+      let time = this.cacheGetHandler(key + this.cacheExpire);
+      if (time) {
+        let newTime = parseInt(time);
+        if (time && time < this.time() && !Number.isNaN(newTime)) {
+          if ($bool === undefined || $bool === true) {
+            this.cacheClearHandler(key);
+            this.cacheClearHandler(key + this.cacheExpire);
+          }
+          return false;
+        } else return true;
+      } else {
+        return !!this.cacheGetHandler(key);
+      }
+    } catch (e) {
+      return false;
+    }
+  }
+
+  /**
+   * 设置缓存
+   * @param {Object} key
+   * @param {Object} data
+   */
+  set(key, data, expire) {
+    if (typeof data === "object") data = JSON.stringify(data);
+    try {
+      this.setExpireCaheTag(key, expire);
+      return this.cacheSetHandler(key, data);
+    } catch (e) {
+      return false;
+    }
+  }
+
+  /**
+   * 检测缓存是否存在
+   * @param {Object} key
+   */
+  has(key) {
+    return this.getExpireCahe(key);
+  }
+
+  /**
+   * 获取缓存
+   * @param {Object} key
+   * @param {Object} $default
+   * @param {Object} expire
+   */
+  get(key, $default, expire) {
+    try {
+      let isBe = this.getExpireCahe(key);
+      let data = this.cacheGetHandler(key);
+      if (data && isBe) {
+        if (typeof $default === "boolean") return JSON.parse(data);
+        else return data;
+      } else {
+        if (typeof $default === "function") {
+          let value = $default();
+          this.set(key, value, expire);
+          return value;
+        } else {
+          this.set(key, $default, expire);
+          return $default;
+        }
+      }
+    } catch (e) {
+      return null;
+    }
+  }
+
+  /**
+   * 删除缓存
+   * @param {Object} key
+   */
+  clear(key) {
+    try {
+      let cahceValue = this.cacheGetHandler(key + this.cacheExpire);
+      if (cahceValue) this.cacheClearHandler(key + this.cacheExpire);
+      return this.cacheClearHandler(key);
+    } catch (e) {
+      return false;
+    }
+  }
+
+  /**
+   * 清除过期缓存
+   */
+  clearOverdue() {
+    // let cacheList = uni.getStorageInfoSync(),that = this;
+    // if (typeof cacheList.keys === 'object'){
+    // 	cacheList.keys.forEach(item=>{
+    // 		that.getExpireCahe(item);
+    // 	})
+    // }
+  }
+
+  /**
+   * 获取缓存,调用后无需转换数据类型
+   * @param {Object} key
+   */
+  getItem(name) {
+    // let item = localStorage.getItem(name);
+    let item = uni.getStorageSync(name);
+    //先将拿到的试着进行json转为对象的形式
+    try {
+      item = JSON.parse(item);
+    } catch (error) {
+      //如果不行就不是json的字符串,就直接返回
+      item = item;
+    }
+    //如果有startTime的值,说明设置了失效时间
+    if (item.startTime) {
+      let date = new Date().getTime();
+      //何时将值取出减去刚存入的时间,与item.expires比较,如果大于就是过期了,如果小于或等于就还没过期
+      if (date - item.startTime > item.expires) {
+        //缓存过期,清除缓存,返回false
+        // localStorage.removeItem(name);
+        uni.removeStorageSync(name);
+        return false;
+      } else {
+        //缓存未过期,返回值
+        return item.value;
+      }
+    } else {
+      //如果没有设置失效时间,直接返回值
+      return item;
+    }
+  }
+}
+
+export default new Cache();

+ 78 - 0
jd_logistics-app/utils/request.js

@@ -0,0 +1,78 @@
+import {
+	HTTP_REQUEST_URL,
+	HEADER,
+	TOKENNAME,
+	HEADERPARAMS
+} from '@/config/app';
+import {
+	toLogin,
+	checkLogin
+} from '@/libs/login';
+import { useAppStore } from '@/stores/app'
+import { useToast } from '@/hooks/useToast'
+
+/**
+ * 发送请求
+ */
+function baseRequest(url, method, data, {
+	noAuth = false,
+	noVerify = false
+}, params) {
+	const appStore = useAppStore();
+  const { Toast } = useToast();
+	let Url = HTTP_REQUEST_URL,header = HEADER
+	const TOKEN = appStore.token
+	if (params != undefined) {
+		header = HEADERPARAMS;
+	}
+	// if (!noAuth) {
+	// 	//登录过期自动登录
+	// 	if (!TOKEN && !checkLogin()) {
+	// 		toLogin();
+	// 		return Promise.reject({
+	// 			code: 401,
+	// 			message: '未登录'
+	// 		});
+	// 	}
+	// }
+	if (TOKEN) header[TOKENNAME] = TOKEN;
+	return new Promise((reslove, reject) => {
+		Url=HTTP_REQUEST_URL || 'http://api.front.hdq.xbdzz.cn'
+		uni.request({
+			url: Url + '/' + url,
+			method: method || 'GET',
+			header: header,
+			timeout: 30000,
+			data: data || {},
+			success: (res) => {
+
+				if (noVerify)
+					reslove(res.data, res);
+				else if (res.data.code == 200)
+					reslove(res.data, res);
+				else if ([410000, 410001, 410002, 401,403].indexOf(res.data.code) !== -1) {
+					// toLogin();
+					appStore.LOGOUT();
+					Toast({title: res.data.msg || '登录过期,请重新登录'});
+					reject(res.data);
+				} else
+          Toast({title: res.data.msg || '系统错误'})
+					reject(res.data.msg || '系统错误');
+			},
+			fail: (msg) => {
+        Toast({title: '请求失败'})
+				reject('请求失败');
+			}
+		})
+	});
+}
+
+const request = {};
+
+['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
+	request[method] = (api, data, opt, params) => baseRequest(api, method, data, opt || {}, params)
+});
+
+
+
+export default request;

+ 577 - 0
jd_logistics-app/utils/util.js

@@ -0,0 +1,577 @@
+import { useAppStore } from "@/stores/app";
+import { toLogin, checkLogin } from '@/libs/login'
+import { wxLogin, updateOpenId,getUserInfo } from "@/api/user.js";
+import { useToast } from "@/hooks/useToast";
+import Cache from "./cache";
+import {
+  USER_INFO,
+  TOKEN
+} from "@/config/cache";
+const { Toast } = useToast();
+import {
+  HTTP_REQUEST_URL,
+  TOKENNAME
+} from '@/config/app';
+
+// 全局状态管理:防止重复请求
+let isLoggingIn = false;
+
+export function telEncrypt(tel = '') {
+  let str = tel + "";
+  let enStr = str.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2")
+  return enStr
+}
+
+export function uniLogin() {
+  console.log('uniLogin', checkLogin());
+  if (checkLogin()) {
+    const userInfo = Cache.get(USER_INFO) ? JSON.parse(Cache.get(USER_INFO)) : {};
+    if (userInfo.openId) return;
+    uni.getProvider({
+      service: 'oauth',
+      success: function(res) {
+        console.log('getProvider', res.provider)
+        if (~res.provider.indexOf('weixin')) {
+          uni.login({
+            provider: 'weixin',
+            success: async function(loginRes) {
+              updateOpenId({
+                jsCode: loginRes.code
+              }).then(res => {
+                if (res.code == 200) {
+                  const appStore = useAppStore();
+                  appStore.USERINFO();
+                }
+              });
+            }
+          });
+        }
+      }
+    });
+  }
+}
+
+/**
+ * 统一登录方法:同时获取openid和手机号
+ * @param {Object} e - 微信获取手机号事件对象
+ * @param {Object} options - 配置选项
+ * @returns {Promise}
+ */
+export async function getPhoneNumber(e, options = {}) {
+  return new Promise(async (resolve, reject) => {
+    const appStore = useAppStore();
+
+    // 防重复点击
+    if (isLoggingIn) {
+      // 移除了Toast,只在页面显示
+      reject(new Error('登录请求中,请稍候'));
+      return;
+    }
+
+    isLoggingIn = true;
+
+    try {
+      // 用户拒绝授权
+      if (e.detail.errMsg !== 'getPhoneNumber:ok') {
+        const errorCode = e.detail.errMsg;
+        let errorMessage = '请授权手机号以完成登录';
+
+        if (errorCode.includes('deny')) {
+          errorMessage = '您已拒绝授权,如需登录请重新授权';
+        } else if (errorCode.includes('timeout')) {
+          errorMessage = '授权超时,请重试';
+        } else if (errorCode.includes('fail')) {
+          errorMessage = '授权失败,请重试';
+        }
+
+        // 移除了Toast,只在页面显示
+        // 执行失败回调
+        if (options.onFail) {
+          options.onFail({ type: 'auth_denied', message: errorMessage });
+        }
+
+        reject(new Error(errorMessage));
+        return;
+      }
+
+      // 授权成功
+      if (!e.detail.code) {
+        // 移除了Toast,只在页面显示
+        if (options.onFail) {
+          options.onFail({ type: 'auth_code_missing', message: '授权码获取失败' });
+        }
+
+        reject(new Error('授权码获取失败'));
+        return;
+      }
+
+      console.log('开始登录流程,手机号授权码:', e.detail.code.substring(0, 20) + '...');
+
+      // 方案1:先获取微信登录code,再调用后端统一接口
+      // 这种方式更推荐,因为可以保证两个code都是最新的
+      const result = await handleLoginWithPhoneCode(e.detail.code, options);
+      
+      // 执行成功回调
+      if (options.onSuccess) {
+        options.onSuccess(result);
+      }
+      resolve(result);
+    } catch (error) {
+      console.error('登录失败:', error);
+
+      // 处理特定错误
+      let errorMessage = '登录失败,请重试';
+      if (error.message && error.message.includes('超时')) {
+        errorMessage = '请求超时,请检查网络';
+      } else if (error.code === 40029) {
+        errorMessage = '授权码无效,请重新获取';
+      } else if (error.code === 40001) {
+        errorMessage = '登录凭证已过期,请重试';
+      } else if (error.code === 401 || error.code === 40101) {
+        errorMessage = '登录已过期,请重新登录';
+      }
+
+      // 移除了Toast,只在页面显示
+      // 执行失败回调
+      if (options.onFail) {
+        options.onFail({
+          type: 'login_failed',
+          message: errorMessage,
+          originalError: error.message || errorMessage
+        });
+      }
+
+      // 特殊错误处理:token过期
+      if (error.code === 401 || error.code === 40101) {
+        // 清除本地token,让用户重新登录
+        Cache.remove(TOKEN);
+        Cache.remove(USER_INFO);
+        appStore.LOGOUT();
+
+        setTimeout(() => {
+          uni.showModal({
+            title: '登录已过期',
+            content: '您的登录状态已过期,请重新登录',
+            showCancel: false,
+            confirmText: '重新登录'
+          });
+        }, 1000);
+      }
+
+      reject(error);
+    } finally {
+      isLoggingIn = false;
+    }
+  });
+}
+
+/**
+ * 处理登录和获取手机号
+ * 方案1:先获取微信登录code,再调用后端统一接口
+ */
+async function handleLoginWithPhoneCode(phoneCode, options) {
+  // 1. 先获取微信登录code
+  const loginRes = await new Promise((resolve, reject) => {
+    uni.login({
+      provider: 'weixin',
+      timeout: 10000,
+      success: resolve,
+      fail: reject
+    });
+  });
+
+  if (!loginRes.code) {
+    throw new Error('获取登录凭证失败');
+  }
+
+  console.log('获取到登录code:', loginRes.code.substring(0, 20) + '...');
+
+  // 2. 获取本地token(如果有的话)
+  const localToken = Cache.get(TOKEN);
+
+  // 3. 调用后端统一登录接口,同时传递登录code和手机号code
+  const requestData = {
+    jsCode: loginRes.code,    // 微信登录code,用于获取openid
+    code: phoneCode,        // 手机号授权code
+    timestamp: Date.now()        // 防止缓存
+  };
+
+  // 如果有本地token,也带上(用于关联已有账号)
+  if (localToken) {
+    requestData.oldToken = localToken;
+  }
+
+  console.log('发送登录请求,数据:', { ...requestData, phoneCode: '已隐藏' });
+
+  // 4. 调用后端接口
+  const res = await wxLogin(requestData);
+
+  console.log('登录接口返回:', res);
+
+  // 5. 处理后端响应
+  if (res.code === 200) {
+    const appStore = useAppStore();
+    
+    // 保存token
+    appStore.UPDATE_TOKEN(res.data.access_token || res.data);
+
+    // 获取用户信息
+    await appStore.USERINFO();
+
+    // 移除了Toast,只在页面显示
+    // 触发登录成功事件
+    uni.$emit('loginSuccess');
+
+    // 登录成功后的处理(例如跳转)
+    await handleAfterLoginSuccess(options);
+
+    return {
+      success: true,
+      data: res.data,
+      message: res.msg || '登录成功',
+      userInfo: appStore.userInfo
+    };
+  } else {
+    // 处理业务错误
+    const error = {
+      code: res.code,
+      message: res.msg || res.data?.message || '登录失败',
+      data: res.data
+    };
+    throw error;
+  }
+}
+
+/**
+ * 登录成功后的处理
+ */
+async function handleAfterLoginSuccess(options) {
+  try {
+    // 如果有回调URL,则跳转
+    if (options.redirectUrl) {
+      setTimeout(() => {
+        if (options.redirectUrl.startsWith('/')) {
+          uni.redirectTo({ url: options.redirectUrl });
+        } else {
+          uni.switchTab({ url: options.redirectUrl });
+        }
+      }, 1500);
+      return;
+    }
+
+    // 默认跳转逻辑
+    const pages = getCurrentPages();
+    const currentRoute = pages[pages.length - 1]?.route || '';
+
+    // 如果当前在个人中心页面,刷新页面即可
+    if (currentRoute.includes('mine')) {
+      // 可以触发页面刷新
+      const eventChannel = pages[pages.length - 1];
+      if (eventChannel && eventChannel.onLoad) {
+        eventChannel.onLoad();
+      }
+    } else {
+      // 其他页面跳转到个人中心
+      setTimeout(() => {
+        uni.switchTab({ url: '/pages/mine/mine' });
+      }, 1500);
+    }
+  } catch (error) {
+    console.error('登录后处理失败:', error);
+  }
+}
+
+/**
+ * 快捷登录方法(适用于页面直接调用)
+ * @param {Object} e - 微信获取手机号事件对象
+ * @param {Object} customOptions - 自定义配置选项
+ * @returns {Promise}
+ */
+export function quickLogin(e, customOptions = {}) {
+  const defaultOptions = {
+    onSuccess: (result) => {
+      console.log('登录成功,用户数据:', result);
+    },
+    onFail: (error) => {
+      console.log('登录失败:', error);
+    },
+    redirectUrl: '/pages/mine/mine'
+  };
+
+  const options = { ...defaultOptions, ...customOptions };
+  
+  return getPhoneNumber(e, options);
+}
+
+//model是ture,属于重新定位,直接更新位置
+export function getLocation(model=true,callback) { 
+  uni.getLocation({
+    type: 'wgs84',
+    geocode: true,
+    success: function (res) {
+      // console.log('当前位置的经度:' + res.longitude);
+      // console.log('当前位置的纬度:' + res.latitude);
+      reverseGeocoder(res.latitude, res.longitude, model,callback);
+    },
+    complete: function (res) {
+      // console.log('getLocation-complete:' ,res);
+      // const appStore = useAppStore();
+      // appStore.UPDATE_CITY("洛阳市")
+    }
+  });
+}
+// 逆地理编码示例(使用腾讯地图)
+export function  reverseGeocoder(latitude, longitude, model,callback) {
+  getCityInfo({latitude,longitude}).then((res)=>{
+    console.log('reverseGeocoder-res:' ,res);
+    if (res.code === 200){
+      const appStore = useAppStore();
+      if(model){
+        appStore.UPDATE_CITYINFO(res.data);
+        callback();
+      }else if(res.data?.name && res.data.name != appStore.cityInfo?.name){
+        uni.showModal({
+          title: '提示',
+          content: '你当前的城市有更新,是否切换到'+ res.data.name,
+          success: function (e) {
+            if (e.confirm) {
+              appStore.UPDATE_CITYINFO(res.data)
+            } else if (e.cancel) {
+              console.log('用户点击取消');
+            }
+          }
+        });
+      }
+    }
+  })
+}
+export function  getNavbarHeight() {
+  const appStore = useAppStore();
+  // 获取系统信息
+  const sysInfo = uni.getSystemInfoSync()
+  // 状态栏高度(不同设备不一致)
+  appStore.UPDATE_statusBarHeight(sysInfo.statusBarHeight)
+  // 导航栏总高度 = 状态栏高度 + 自定义导航内容高度(通常 44px)
+  // navbarHeight.value = statusBarHeight.value + 44;
+}
+export function  setClipboardData(data="") {
+  uni.setClipboardData({
+    data,
+    success: function () {
+      Toast({ title: "复制成功" });
+    }
+  });
+}
+export function checkLoginShowModal() {
+  return new Promise((resolve, reject) => {
+    if (!checkLogin()) {
+      uni.showModal({
+        title: '温馨提示',
+        content: '登录后将享受更多优质权益,请先登录',
+        // showCancel:false,
+        success: function (res) {
+          if (res.confirm) {
+            //跳转到我的页
+            uni.switchTab({
+              url: "/pages/mine/mine",
+            });
+          } else if (res.cancel) {
+            console.log('用户点击取消');
+          }
+          resolve(false);
+        }
+      });
+    }else { 
+      resolve(true);
+    }
+  })
+}
+// 检测今日免费提问次数
+export function checkAiQuotaDailyModal() {
+  return new Promise((resolve, reject) => {
+    const appStore = useAppStore();
+    if (appStore.userInfo?.aiQuotaDaily <= 0) {
+      let content = '';
+      if(appStore.userInfo?.rechargeBalance <= 0){
+        content = ' AI调用次数不足且余额不足,请充值或明日再试';
+      }else if(appStore.userInfo?.rechargeBalance > 0){
+        if(appStore.useBalance){
+          resolve(true);
+          return;
+        }
+        content = '当日免费ai调用次数已用完,是否使用晓豆进行支付';
+
+      }
+      uni.showModal({
+        title: '温馨提示',
+        content,
+        // showCancel:false,
+        success: function (res) {
+          if (res.confirm) {
+            if(appStore.userInfo?.rechargeBalance <= 0){
+              //跳转到充值页
+              uni.navigateTo({
+                url: "/pages/recharge/recharge",
+              });
+            }else{
+              appStore.useBalance = true;
+              resolve(true);
+            }
+          } else if (res.cancel) {
+            console.log('用户点击取消');
+          }
+          resolve(false);
+        }
+      });
+    }else { 
+      resolve(true);
+    }
+  })
+}
+// 方法:按指定key将一维对象数组转为二维数组
+export function groupByKey(arr, key) {
+    // 创建一个临时对象用于分组
+    const groupObj = {};
+    
+    // 遍历原数组
+    arr.forEach(item => {
+        // 获取当前项的key值作为分组标识
+        const groupKey = item[key];
+        
+        // 如果该分组不存在,则初始化一个空数组
+        if (!groupObj[groupKey]) {
+            groupObj[groupKey] = [];
+        }
+        
+        // 将当前项添加到对应的分组中
+        groupObj[groupKey].push(item);
+    });
+    
+    // 将对象的值转换为数组,得到二维数组
+    return Object.values(groupObj);
+}
+// 支付
+export function wxPay({timeStamp,nonceStr,packageVal,signType,paySign},callback) {
+  uni.requestPayment({
+    provider: 'wxpay',
+    timeStamp,
+    nonceStr,
+    package:packageVal,
+    signType,
+    paySign,
+    success: function (res) {
+      console.log('wxPay-success:' + JSON.stringify(res));
+      callback({isSuccess:1});
+    },
+    fail: function (err) {
+      console.log('wxPay-fail:' + JSON.stringify(err));
+      Toast({ title: "支付失败" });
+      callback({isSuccess:0});
+    }
+  });
+}
+/**
+ * 计算两点之间的直线距离(Haversine公式)
+ * @param {Number} lat1 起点纬度
+ * @param {Number} lon1 起点经度
+ * @param {Number} lat2 终点纬度
+ * @param {Number} lon2 终点经度
+ * @returns {Number} 距离(千米)
+ */
+export function calculateDistance (lat1, lon1, lat2, lon2) {
+  // 将角度转换为弧度
+  const radLat1 = (lat1 * Math.PI) / 180;
+  const radLat2 = (lat2 * Math.PI) / 180;
+  const a = radLat1 - radLat2;
+  const b = (lon1 * Math.PI) / 180 - (lon2 * Math.PI) / 180;
+  
+  // Haversine公式计算距离(地球半径取6371千米)
+  let s = 2 * Math.asin(
+    Math.sqrt(
+      Math.pow(Math.sin(a / 2), 2) +
+      Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)
+    )
+  );
+  s = s * 6371; // 地球半径(千米)
+  return s.toFixed(2);
+};
+// 获取邀请码
+export function getSceneInfo (e) {
+  console.log("getSceneInfo",e)
+  if(e.scene){
+    const decodedScene = decodeURIComponent(e.scene);
+    // 2. 分割参数(格式如:key1=value1&key2=value2)
+    const params = {};
+    if (decodedScene) {
+      decodedScene.split('&').forEach(item => {
+        const [key, value] = item.split('=');
+        if (key && value) {
+          params[key] = value;
+        }
+      });
+    }
+    console.log("getSceneInfo-params",params)
+    // 邀请码
+    const appStore = useAppStore();
+    if(params.inviteCode)appStore.UPDATE_inviteCode(params.inviteCode);
+    return params;
+  }
+};
+//获取客户信息
+export function getUserInfo (e) {
+  const appStore = useAppStore();
+  if(Cache.get(TOKEN))appStore.USERINFO();
+};
+export async function chooseImageOne(apiUrl="chat/file/imageUpload") {
+  return new Promise((resolve, reject) => {
+    uni.chooseImage({
+      count: 1,
+      success: (chooseImageRes) => {
+        const tempFilePaths = chooseImageRes.tempFilePaths;
+        console.log("chooseImageRes",chooseImageRes);
+        const appStore = useAppStore();
+        uni.uploadFile({
+          url: `${HTTP_REQUEST_URL}/mini/${apiUrl}`, //仅为示例,非真实的接口地址
+          filePath: tempFilePaths[0],
+          name: 'file',
+          header: {
+            [TOKENNAME]: appStore.token,
+          },
+          success: (uploadFileRes) => {
+            console.log('uni.uploadFile',uploadFileRes);
+            const data = JSON.parse(uploadFileRes.data);
+            if(data.code==200){
+              resolve(data);
+            }else{
+              reject(data);
+              Toast({ title: "图片上传失败" });
+            }
+          }
+        });
+      }
+    });
+  })
+}
+export async function uploadFile(filePath,apiUrl="chat/file/imageUpload") {
+  return new Promise((resolve, reject) => {
+    const appStore = useAppStore();
+    uni.uploadFile({
+      url: `${HTTP_REQUEST_URL}/mini/${apiUrl}`, //仅为示例,非真实的接口地址
+      filePath,
+      name: 'file',
+      header: {
+        [TOKENNAME]: appStore.token,
+      },
+      success: (uploadFileRes) => {
+        console.log('uni.uploadFile',uploadFileRes);
+        const data = JSON.parse(uploadFileRes.data);
+        if(data.code==200){
+          resolve(data);
+        }else{
+          reject(data);
+          Toast({ title: "图片上传失败" });
+        }
+      }
+    });
+  })
+}

+ 175 - 0
jd_logistics-app/utils/wsUtil.js

@@ -0,0 +1,175 @@
+export default class WSClient {
+  constructor(options) {
+    // 基础配置
+    this.url = options.url;
+    this.timeout = options.timeout || 60000;
+    this.headers = {
+      ...options.headers
+    };
+    
+    // 状态管理
+    this.ws = null;
+    this.isConnected = false;
+    this.fullThinking = '';
+    this.fullAnswer = '';
+    this.timeoutId = null;
+
+    // 事件回调
+    this.callbacks = {
+      open: [],
+      close: [],
+      error: [],
+      start: [],
+      status: [],
+      thinking: [],
+      answer: [],
+      finish: []
+    };
+  }
+
+  // 注册事件监听
+  on(event, callback) {
+    if (this.callbacks[event]) {
+      this.callbacks[event].push(callback);
+    } else {
+      console.warn(`未知事件类型: ${event}`);
+    }
+  }
+
+  // 触发事件
+  emit(event, data) {
+    if (this.callbacks[event]) {
+      this.callbacks[event].forEach(callback => {
+        try {
+          callback({ ...data, timestamp: Date.now() });
+        } catch (e) {
+          console.error(`事件${event}回调错误:`, e);
+        }
+      });
+    }
+  }
+
+  // 建立连接
+  open(data) {
+    // 关闭现有连接
+    this.close();
+    
+    // 重置状态
+    this.fullThinking = '';
+    this.fullAnswer = '';
+
+    try {
+      // 创建 WebSocket 连接
+      this.ws = uni.connectSocket({
+        url: this.url,
+        header: this.headers,
+        complete: () => {
+          // 连接完成回调
+        }
+      });
+
+      // uni.connectSocket 是异步的,需要在 success 回调中处理
+      this.ws.onOpen((res) => {
+        clearTimeout(this.timeoutId);
+        this.isConnected = true;
+        this.emit('open', { res });
+        
+        // 连接建立后发送数据
+        // if (data) {
+        //   this.send(data);
+        // }
+      });
+
+      this.ws.onMessage((res) => {
+        console.log('接收到数据:', res,);
+        let segmentText = '';
+        try {
+          // 先检查数据是否为字符串
+          if (typeof res.data === 'string' && res.data.trim().startsWith('{') || res.data.trim().startsWith('[')) {
+            // 只有看起来像JSON的字符串才尝试解析
+            const parsedData = JSON.parse(res.data);
+            segmentText = parsedData.text || parsedData.message || JSON.stringify(parsedData);
+          } else {
+            // 直接使用原始数据
+            segmentText = res.data;
+          }
+        } catch (e) {
+          console.error('JSON解析错误:', e, '原始数据:', res.data);
+          // 解析失败,直接使用原始数据
+          segmentText = res.data;
+        }
+        console.log('处理后的文本段:', segmentText);
+        this.emit('answer', {
+          segment: segmentText,
+        });
+      });
+
+      this.ws.onError((error) => {
+        clearTimeout(this.timeoutId);
+        this.emit('error', { type: 'ws.onError', message: error.errMsg || '连接错误' });
+      });
+
+      this.ws.onClose((res) => {
+        console.log('ws连接已关闭:', res);
+        this.emit('close', { 
+          type: 'ws.onClose',
+          reason: res.reason || '连接关闭', 
+          code: res.code 
+        });
+        clearTimeout(this.timeoutId);
+        this.isConnected = false;
+      });
+
+      // 设置连接超时
+      // this.timeoutId = setTimeout(() => {
+      //   if (!this.isConnected) {
+      //     this.emit('error', { type: 'timeout', message: '连接超时' });
+      //     this.close();
+      //   }
+      // }, this.timeout);
+
+    } catch (error) {
+      clearTimeout(this.timeoutId);
+      this.emit('error', { type: 'init-try-catch', message: error.message || '初始化错误' });
+    }
+  }
+
+  // 发送数据
+  send(data) {
+    console.log('ws发送数据:', data);
+    if (this.ws && this.isConnected) {
+      this.ws.send({
+        data: JSON.stringify(data),
+        success: (res) => {
+          // 发送成功
+           console.log('ws发送成功:', res);
+        },
+        fail: (err) => {
+          this.emit('error', { type: 'send', message: '发送失败: ' + err.errMsg });
+        }
+      });
+    } else {
+      this.emit('error', { type: 'send', message: '连接未建立' });
+    }
+  }
+  // 关闭连接
+  close(reason) {
+    if (this.ws) {
+      this.ws.close({
+        success: () => {
+          // 关闭成功
+        },
+        fail: (err) => {
+          console.error('关闭连接失败:', err);
+        }
+      });
+      this.ws = null;
+    }
+    this.isConnected = false;
+    clearTimeout(this.timeoutId);
+    
+    if (reason) {
+      this.emit('close', { reason });
+    }
+  }
+}

+ 45 - 0
jd_logistics-app/vite.config.js

@@ -0,0 +1,45 @@
+import { defineConfig } from "vite";
+import uni from "@dcloudio/vite-plugin-uni";
+
+/**
+ * @type {import('vite').UserConfig}
+ */
+
+export default defineConfig({
+  build: {
+    sourcemap: false,
+    // commonjsOptions: {
+    //   include: [/pako/, /node_modules/],
+    // },
+  },
+  // server: {
+  //   proxy: {
+  //       '/front-api':{
+  //         target: 'http://192.168.100.254:8024',
+  //         changeOrigin: true,
+  //         // rewrite: (path) => path.replace(/^\/front-api/, '/api'),
+  //         configure: (proxy, options) => {
+
+  //           proxy.on('error', (err, req, res) => {
+  //             console.error('proxy error', err)
+  //           });
+
+  //           proxy.on('proxyReq', function (proxyReq, req, res) {
+  //             console.log('proxy request', req.method, req.url);
+  //           });
+
+  //           proxy.on('proxyRes', function (proxyRes, req, res) {
+  //             console.log('proxy response', proxyRes.statusCode, req.url);
+  //           });
+  //         }
+  //       }
+  //     }
+  // },
+  alias: {
+    // "@": "/src"
+  },
+  // optimizeDeps: {
+  //   exclude: ["pako"],
+  // },
+  plugins: [uni()],
+});