Compare commits
	
		
			No commits in common. "master" and "v2.0.0" have entirely different histories. 
		
	
	
		
							
								
								
									
										34
									
								
								.env
								
								
								
								
							
							
						
						
									
										34
									
								
								.env
								
								
								
								
							|  | @ -1,35 +1,21 @@ | |||
| # 版本号 | ||||
| SHOPRO_VERSION=v2.4.1 | ||||
| SHOPRO_VERSION = v1.8.3 | ||||
| 
 | ||||
| # 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development) | ||||
| SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn | ||||
| # 后端接口 - 正式环境(通过 process.env.NODE_ENV = development) | ||||
| SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn | ||||
| 
 | ||||
| # 后端接口 - 测试环境(通过 process.env.NODE_ENV = development) | ||||
| SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080 | ||||
| ### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080 | ||||
| # 后端接口 - 测试环境(通过 process.env.NODE_ENV 非 development) | ||||
| SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080 | ||||
| ### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc | ||||
| 
 | ||||
| # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务 | ||||
| SHOPRO_UPLOAD_TYPE=server | ||||
| 
 | ||||
| # 后端接口前缀(一般不建议调整) | ||||
| SHOPRO_API_PATH=/app-api | ||||
| 
 | ||||
| # 后端 websocket 接口前缀 | ||||
| SHOPRO_WEBSOCKET_PATH=/infra/ws | ||||
| SHOPRO_API_PATH = /app-api | ||||
| 
 | ||||
| # 开发环境运行端口 | ||||
| SHOPRO_DEV_PORT=3000 | ||||
| SHOPRO_DEV_PORT = 3000 | ||||
| 
 | ||||
| # 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地  |  http(s)://xxx.xxx=自定义静态资源地址前缀 | ||||
| SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn | ||||
| ### SHOPRO_STATIC_URL = https://file.sheepjs.com | ||||
| SHOPRO_STATIC_URL = https://file.sheepjs.com | ||||
| 
 | ||||
| # 前端 H5 访问域名 | ||||
| SHOPRO_H5_URL=http://127.0.0.1:3000 | ||||
| 
 | ||||
| # 是否开启直播  1 开启直播 | 0 关闭直播 | ||||
| SHOPRO_MPLIVE_ON=0 | ||||
| 
 | ||||
| # 租户ID 默认 1 | ||||
| SHOPRO_TENANT_ID=1 | ||||
| # 是否开启直播  1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启) | ||||
| SHOPRO_MPLIVE_ON = 0 | ||||
|  | @ -5,6 +5,7 @@ deploy.sh | |||
| .hbuilderx/ | ||||
| .vscode/ | ||||
| **/.DS_Store | ||||
| .env | ||||
| yarn.lock | ||||
| package-lock.json | ||||
| *.keystore | ||||
|  |  | |||
							
								
								
									
										19
									
								
								App.vue
								
								
								
								
							
							
						
						
									
										19
									
								
								App.vue
								
								
								
								
							|  | @ -4,26 +4,33 @@ | |||
| 
 | ||||
|   onLaunch(() => { | ||||
|     // 隐藏原生导航栏 使用自定义底部导航 | ||||
|     uni.hideTabBar({ | ||||
|       fail: () => {}, | ||||
|     }); | ||||
|     uni.hideTabBar(); | ||||
| 
 | ||||
|     // 加载Shopro底层依赖 | ||||
|     ShoproInit(); | ||||
|   }); | ||||
| 
 | ||||
|   onShow(() => { | ||||
|   onError((err) => { | ||||
|     console.log('AppOnError:', err); | ||||
|   }); | ||||
| 
 | ||||
|   onShow((options) => { | ||||
|     // #ifdef APP-PLUS | ||||
|     // 获取urlSchemes参数 | ||||
|     const args = plus.runtime.arguments; | ||||
|     if (args) { | ||||
|     } | ||||
|     }  | ||||
| 
 | ||||
|     // 获取剪贴板 | ||||
|     uni.getClipboardData({ | ||||
|       success: (res) => {}, | ||||
|       success: (res) => { }, | ||||
|     }); | ||||
|     // #endif | ||||
| 
 | ||||
|     // #ifdef MP-WEIXIN | ||||
|     // 确认收货回调结果 | ||||
|     console.log(options,'options'); | ||||
|     // #endif | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,11 +30,11 @@ | |||
| 
 | ||||
| 支持 Spring Boot、Spring Cloud 两种架构: | ||||
| 
 | ||||
| ① Spring Boot 单体架构:<https://doc.iocoder.cn> | ||||
| ① Spring Boot 单体架构:<https://github.com/YunaiV/ruoyi-vue-pro> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ② Spring Cloud 微服务架构:<https://cloud.iocoder.cn> | ||||
| ② Spring Cloud 微服务架构:<https://github.com/YunaiV/yudao-cloud> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| { | ||||
|   "name": "芋道商城", | ||||
|   "appid": "__UNI__460BC4C", | ||||
|   "appid": "__UNI__082C0BA", | ||||
|   "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。", | ||||
|   "versionName": "2025.09", | ||||
|   "versionCode": "183", | ||||
|   "versionName": "1.8.3", | ||||
|   "versionCode": 183, | ||||
|   "transformPx": false, | ||||
|   "app-plus": { | ||||
|     "usingComponents": true, | ||||
|  | @ -57,6 +57,7 @@ | |||
|           "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", | ||||
|           "<uses-permission android:name=\"android.permission.SEND_SMS\"/>", | ||||
|           "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>", | ||||
|           "<uses-permission android:name=\"android.permission.VIBRATE\"/>", | ||||
|           "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", | ||||
|           "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>", | ||||
|           "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>", | ||||
|  | @ -90,7 +91,9 @@ | |||
|         "idfa": true | ||||
|       }, | ||||
|       "sdkConfigs": { | ||||
|         "speech": {}, | ||||
|         "speech": { | ||||
|           "ifly": {} | ||||
|         }, | ||||
|         "ad": {}, | ||||
|         "oauth": { | ||||
|           "apple": {}, | ||||
|  | @ -182,7 +185,7 @@ | |||
|     "versionCode": 100 | ||||
|   }, | ||||
|   "mp-weixin": { | ||||
|     "appid": "wx66186af0759f47c9", | ||||
|     "appid": "wx63c280fe3248a3e7", | ||||
|     "setting": { | ||||
|       "urlCheck": false, | ||||
|       "minified": true, | ||||
|  | @ -214,8 +217,8 @@ | |||
|   "h5": { | ||||
|     "template": "index.html", | ||||
|     "router": { | ||||
|       "mode": "history", | ||||
|       "base": "/" | ||||
|       "mode": "hash", | ||||
|       "base": "./" | ||||
|     }, | ||||
|     "sdkConfigs": { | ||||
|       "maps": {} | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
|   "id": "shopro", | ||||
|   "name": "shopro", | ||||
|   "displayName": "芋道商城", | ||||
|   "version": "2025.09", | ||||
|   "version": "1.0.1", | ||||
|   "description": "芋道商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能", | ||||
|   "scripts": { | ||||
|     "prettier": "prettier --write  \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"" | ||||
|  | @ -88,12 +88,13 @@ | |||
|     } | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@hyoga/uni-socket.io": "^1.0.1", | ||||
|     "dayjs": "^1.11.7", | ||||
|     "lodash": "^4.17.21", | ||||
|     "lodash-es": "^4.17.21", | ||||
|     "luch-request": "^3.0.8", | ||||
|     "pinia": "^2.0.33", | ||||
|     "pinia-plugin-persist-uni": "^1.2.0", | ||||
|     "qs-canvas": "^1.0.11", | ||||
|     "weixin-js-sdk": "^1.6.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|  |  | |||
							
								
								
									
										66
									
								
								pages.json
								
								
								
								
							
							
						
						
									
										66
									
								
								pages.json
								
								
								
								
							|  | @ -120,17 +120,6 @@ | |||
| 						"group": "商品" | ||||
| 					} | ||||
| 				}, | ||||
|                 { | ||||
|                     "path": "point", | ||||
|                     "style": { | ||||
|                       "navigationBarTitleText": "积分商品" | ||||
|                     }, | ||||
|                     "meta": { | ||||
|                       "sync": true, | ||||
|                       "title": "积分商品", | ||||
|                       "group": "商品" | ||||
|                     } | ||||
|                 }, | ||||
| 				{ | ||||
| 					"path": "list", | ||||
| 					"style": { | ||||
|  | @ -318,18 +307,6 @@ | |||
| 						"title": "编辑地址" | ||||
| 					} | ||||
| 				}, | ||||
|                 { | ||||
|                   "path": "goods_details_store/index", | ||||
|                   "style": { | ||||
|                     "navigationBarTitleText": "自提门店" | ||||
|                   }, | ||||
|                   "meta": { | ||||
|                     "auth": true, | ||||
|                     "sync": true, | ||||
|                     "title": "地址管理", | ||||
|                     "group": "用户中心" | ||||
|                   } | ||||
|                 }, | ||||
| 				{ | ||||
| 					"path": "wallet/money", | ||||
| 					"style": { | ||||
|  | @ -549,10 +526,7 @@ | |||
| 			"pages": [{ | ||||
| 				"path": "index", | ||||
| 				"style": { | ||||
| 					"navigationBarTitleText": "客服", | ||||
|                     "app-plus": { | ||||
|                       "softinputMode": "adjustResize" | ||||
|                     } | ||||
| 					"navigationBarTitleText": "客服" | ||||
| 				}, | ||||
| 				"meta": { | ||||
| 					"auth": true, | ||||
|  | @ -656,17 +630,28 @@ | |||
| 						"group": "营销活动" | ||||
| 					} | ||||
| 				}, | ||||
|                 { | ||||
|                   "path": "point/list", | ||||
|                   "style": { | ||||
|                     "navigationBarTitleText": "积分商城" | ||||
|                   }, | ||||
|                   "meta": { | ||||
|                     "sync": true, | ||||
|                     "title": "积分商城", | ||||
|                     "group": "营销活动" | ||||
|                   } | ||||
|                 } | ||||
| 				{ | ||||
| 					"path": "bargain/list", | ||||
| 					"style": { | ||||
| 						"navigationBarTitleText": "砍价列表" | ||||
| 					}, | ||||
| 					"meta": { | ||||
| 						"sync": true, | ||||
| 						"title": "砍价列表", | ||||
| 						"group": "营销活动" | ||||
| 					} | ||||
| 				}, | ||||
| 				{ | ||||
| 					"path": "bargain/detail", | ||||
| 					"style": { | ||||
| 						"navigationBarTitleText": "砍价详情" | ||||
| 					}, | ||||
| 					"meta": { | ||||
| 						"sync": true, | ||||
| 						"title": "砍价详情", | ||||
| 						"group": "营销活动" | ||||
| 					} | ||||
| 				} | ||||
| 			] | ||||
| 		} | ||||
| 	], | ||||
|  | @ -681,9 +666,6 @@ | |||
| 		"list": [{ | ||||
| 				"pagePath": "pages/index/index" | ||||
| 			}, | ||||
|             { | ||||
| 				"pagePath": "pages/index/category" | ||||
|             }, | ||||
| 			{ | ||||
| 				"pagePath": "pages/index/cart" | ||||
| 			}, | ||||
|  | @ -692,4 +674,4 @@ | |||
| 			} | ||||
| 		] | ||||
| 	} | ||||
| } | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -0,0 +1,370 @@ | |||
| <template> | ||||
| 	<!-- TODO @科举:参考 groupon/list.vue 和 seckill/list.vue 界面,调整下头部,就是从 5 到 11 行的  --> | ||||
| 	<s-layout navbar="inner" title='砍价列表'> | ||||
| 		<view style='background-color: red;height:100vh;'> | ||||
| 			<view class='bargain-list'> | ||||
| 				<!-- #ifdef H5 --> | ||||
| 				<view class='iconfont icon-xiangzuo' @tap='goBack' :style="'top:'+ (state.navH/2) +'rpx'" | ||||
| 					v-if="state.returnShow"> | ||||
| 				</view> | ||||
| 				<!-- #endif --> | ||||
| 
 | ||||
| 				<!-- 砍价记录的概要 --> | ||||
| 				<view class='header'> | ||||
| 					<view class="pic"> | ||||
| 						<view class='swipers'> | ||||
| 							<swiper indicator-dots="true" autoplay="true" interval="2500" duration="500" vertical="true" | ||||
| 								circular="true"> | ||||
| 								<block v-for="(item,index) in state.bargainSuccessList" :key='index'> | ||||
| 									<swiper-item> | ||||
| 										<view class="acea-row row-middle" style='display:flex'> | ||||
| 											<image :src="item.avatar" class="mr9"></image> | ||||
| 											<view class='mr9 nickName'>{{ item.nickname }}</view> | ||||
| 											<text class='mr9'>拿了</text> | ||||
| 											<view class='line1'>{{ item.activityName }}</view> | ||||
| 										</view> | ||||
| 									</swiper-item> | ||||
| 								</block> | ||||
| 							</swiper> | ||||
| 						</view> | ||||
| 					</view> | ||||
| 					<view class="tit">已有{{ state.bargainTotal }}人砍成功</view> | ||||
| 				</view> | ||||
| 
 | ||||
| 				<!-- 砍价活动列表 --> | ||||
| 				<view class='list'> | ||||
| 					<block v-for="(item,index) in state.bargainList" :key="index"> | ||||
| 						<view style='display:flex' class='item acea-row row-between-wrapper' | ||||
| 							@tap="openSubscribe('/pages/activity/bargain/detail?id='+ item.id)"> | ||||
| 							<view class='pictrue'> | ||||
| 								<image :src='item.picUrl'></image> | ||||
| 							</view> | ||||
| 							<view class='text acea-row row-column-around'> | ||||
| 								<view class='name line2'>{{ item.name }}</view> | ||||
| 								<view class="acea-row" style="margin-bottom: 14rpx;display:flex"> | ||||
| 									<s-count-down :tipText="' '" :bgColor="state.bgColor" :dayText="':'" :hourText="':'" | ||||
| 										:minuteText="':'" :secondText="' '" :datatime="item.endTime / 1000" | ||||
| 										:isDay="true" /> | ||||
| 									<text class="txt">后结束</text> | ||||
| 								</view> | ||||
| 								<view v-if="item.stock === 0"> | ||||
| 									<view style="font-size: 22rpx;" | ||||
| 										@tap="openSubscribe('/pages/activity/goods_bargain_details/index?id='+ item.id +'&startBargainUid='+ uid)"> | ||||
| 										已售罄</view> | ||||
| 								</view> | ||||
| 								<view class='money font-color'>最低: ¥<text | ||||
| 										class='price'>{{ fen2yuan(item.bargainMinPrice) }}</text></view> | ||||
| 							</view> | ||||
| 							<view v-if="item.stock > 0" class='cutBnt bg-color'>参与砍价</view> | ||||
| 							<view v-if="item.stock === 0" class='cutBnt bg-color-hui'>已售罄</view> | ||||
| 						</view> | ||||
| 					</block> | ||||
| 					<view class='loadingicon acea-row row-center-wrapper' v-if='state.bargainList.length > 0' | ||||
| 						style='text-align: center;'> | ||||
| 						<text class='loading iconfont icon-jiazai' :hidden='!loading'></text>{{state.loadTitle}} | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| 	import { | ||||
| 		reactive | ||||
| 	} from 'vue'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import _ from 'lodash'; | ||||
| 	import { | ||||
| 		onLoad, | ||||
| 		onReachBottom | ||||
| 	} from '@dcloudio/uni-app'; | ||||
| 	import { | ||||
| 		fen2yuan | ||||
| 	} from '@/sheep/hooks/useGoods'; | ||||
| 	import BargainApi from '@/sheep/api/promotion/bargain' | ||||
| 	const state = reactive({ | ||||
| 		navH: '', | ||||
| 		returnShow: true, | ||||
| 
 | ||||
| 		// ========== 砍价记录概要的相关变量 ========== | ||||
| 		bargainTotal: 0, | ||||
| 		bargainSuccessList: [], | ||||
| 
 | ||||
| 		// ========== 砍价活动的相关变量 ========== | ||||
| 		bargainList: [], | ||||
| 		page: 1, | ||||
| 		limit: 10, | ||||
| 		loading: false, | ||||
| 		loadend: false, | ||||
| 		bgColor: { | ||||
| 			'bgColor': '#E93323', | ||||
| 			'Color': '#fff', | ||||
| 			'width': '44rpx', | ||||
| 			'timeTxtwidth': '16rpx', | ||||
| 			'isDay': true | ||||
| 		}, | ||||
| 		loadTitle: '加载更多', | ||||
| 	}); | ||||
| 
 | ||||
| 	async function getBargainHeader() { | ||||
| 		let { | ||||
| 			code, | ||||
| 			data | ||||
| 		} = await BargainApi.getBargainRecordSummary() | ||||
| 		if (code == 0) { | ||||
| 			state.bargainTotal = data.successUserCount; | ||||
| 			state.bargainSuccessList = data.successList; | ||||
| 		} else { | ||||
| 			state.$util.Tips({ | ||||
| 				title: data | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	async function getBargainList() { | ||||
| 		// TODO @科举:loading 和 loadTitle 改成现在这个项目的风格,包括组件使用 uni-load-more | ||||
| 		if (state.loadend || state.loading) { | ||||
| 			return; | ||||
| 		} | ||||
| 		state.loading = true; | ||||
| 		state.loadTitle = ''; | ||||
| 		let { | ||||
| 			data, | ||||
| 			code | ||||
| 		} = await BargainApi.getBargainActivityPage({ | ||||
| 			pageNo: state.page, | ||||
| 			pageSize: state.limit | ||||
| 		}) | ||||
| 		if (code == 0) { | ||||
| 			const list = data.list; | ||||
| 			const bargainList = _.concat(state.bargainList, list); | ||||
| 			const loadend = list.length < state.limit; | ||||
| 			state.loadend = loadend; | ||||
| 			state.loading = false; | ||||
| 			state.loadTitle = loadend ? '已全部加载' : '加载更多'; | ||||
| 			// this.$set(this, 'bargainList', bargainList); | ||||
| 			state.bargainList = data.list | ||||
| 			// this.$set(this, 'page', this.page + 1); | ||||
| 			state.page = state.page + 1; | ||||
| 
 | ||||
| 		} else { | ||||
| 			state.loading = false; | ||||
| 			state.loadTitle = '加载更多'; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	function openSubscribe(e) { | ||||
| 		console.log('跳转') | ||||
| 		console.log(e) | ||||
| 		// TODO @科举:参考 pages/pay/result.vue 页面的 subscribeMessage 方法,写订阅逻辑。 | ||||
| 		sheep.$router.go(e) | ||||
| 		return; | ||||
| 		let page = e; | ||||
| 		// #ifndef MP | ||||
| 		uni.navigateTo({ | ||||
| 			url: page | ||||
| 		}); | ||||
| 		// #endif | ||||
| 		// #ifdef MP | ||||
| 		uni.showLoading({ | ||||
| 			title: '正在加载', | ||||
| 		}) | ||||
| 		openBargainSubscribe().then(res => { | ||||
| 			uni.hideLoading(); | ||||
| 
 | ||||
| 		}).catch((err) => { | ||||
| 			uni.hideLoading(); | ||||
| 		}); | ||||
| 		// #endif | ||||
| 	} | ||||
| 
 | ||||
| 	onLoad(function() { | ||||
| 		getBargainHeader(); | ||||
| 		getBargainList(); | ||||
| 	}) | ||||
| 
 | ||||
| 	onReachBottom(() => { | ||||
| 		getBargainList(); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang='scss' scoped> | ||||
| 	page, | ||||
| 	.page-app { | ||||
| 		background-color: #e93323 !important; | ||||
| 	} | ||||
| 
 | ||||
| 	.font-color { | ||||
| 		color: red; | ||||
| 	} | ||||
| 
 | ||||
| 	.mr9 { | ||||
| 		margin-right: 9rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.swipers { | ||||
| 		height: 100%; | ||||
| 		width: 76%; | ||||
| 		margin: auto; | ||||
| 		overflow: hidden; | ||||
| 		font-size: 22rpx; | ||||
| 		color: #fff; | ||||
| 
 | ||||
| 		image { | ||||
| 			width: 24rpx; | ||||
| 			height: 24rpx; | ||||
| 			border-radius: 50%; | ||||
| 			overflow: hidden; | ||||
| 		} | ||||
| 
 | ||||
| 		swiper { | ||||
| 			height: 100%; | ||||
| 			width: 100%; | ||||
| 			overflow: hidden; | ||||
| 		} | ||||
| 
 | ||||
| 		.line1 { | ||||
| 			width: 195rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .icon-xiangzuo { | ||||
| 		font-size: 40rpx; | ||||
| 		color: #fff; | ||||
| 		position: fixed; | ||||
| 		left: 30rpx; | ||||
| 		z-index: 99; | ||||
| 		transform: translateY(-20%); | ||||
| 		height: 100% | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .header { | ||||
| 		/* TODO 芋艿:此处原来采用base64 但是过长编辑到小程序卡死 目前采用网络地址 需解决 */ | ||||
| 		background-image: url('https://huizhizao-1314830814.cos.ap-shanghai.myqcloud.com/huizhizao-1314830814/bdc8a9210710b83bcd88a14703f440fc7091792706b5cb71b54361488a547298.png'); | ||||
| 		babackground-repeat: no-repeat; | ||||
| 		background-size: 100% 100%; | ||||
| 		width: 750rpx; | ||||
| 		height: 420rpx; | ||||
| 
 | ||||
| 		.acea-row { | ||||
| 			height: 50rpx; | ||||
| 			line-height: 50rpx; | ||||
| 			left: 50rpx; | ||||
| 
 | ||||
| 			.nickName { | ||||
| 				width: 65rpx; | ||||
| 				overflow: hidden; | ||||
| 				white-space: nowrap; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		.pic { | ||||
| 			width: 478rpx; | ||||
| 			height: 50rpx; | ||||
| 			margin: 0 auto; | ||||
| 			/* TODO 芋艿:此处原来是本地地址小程序不支持,需改为线上 */ | ||||
| 			background-image: url('https://huizhizao-1314830814.cos.ap-shanghai.myqcloud.com/huizhizao-1314830814/d111ac53e1390618f22fcc03e415bcd584b3f409ae52421aef95c2ab9b02aa30.png'); | ||||
| 			babackground-repeat: no-repeat; | ||||
| 			background-size: 100% 100%; | ||||
| 		} | ||||
| 
 | ||||
| 		.tit { | ||||
| 			color: #FFFFFF; | ||||
| 			font-size: 24rpx; | ||||
| 			font-weight: 400; | ||||
| 			text-align: center; | ||||
| 			margin-top: 304rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list { | ||||
| 		padding: 0 30rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item { | ||||
| 		position: relative; | ||||
| 		height: 250rpx; | ||||
| 		background-color: #fff; | ||||
| 		border-radius: 14rpx; | ||||
| 		margin-bottom: 20rpx; | ||||
| 		padding: 30rpx 25rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .pictrue { | ||||
| 		width: 190rpx; | ||||
| 		height: 190rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .pictrue image { | ||||
| 		width: 100%; | ||||
| 		height: 100%; | ||||
| 		border-radius: 14rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .text { | ||||
| 		width: 432rpx; | ||||
| 		font-size: 28rpx; | ||||
| 		color: #333333; | ||||
| 
 | ||||
| 		.txt { | ||||
| 			font-size: 22rpx; | ||||
| 			margin-left: 4rpx; | ||||
| 			color: #666666; | ||||
| 			line-height: 36rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .text .name { | ||||
| 		width: 100%; | ||||
| 		height: 68rpx; | ||||
| 		line-height: 36rpx; | ||||
| 		font-size: 28rpx; | ||||
| 		margin-bottom: 26rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .text .num { | ||||
| 		font-size: 26rpx; | ||||
| 		color: #999; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .text .num .iconfont { | ||||
| 		font-size: 35rpx; | ||||
| 		margin-right: 7rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .text .money { | ||||
| 		font-size: 24rpx; | ||||
| 		font-weight: bold; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .text .money .price { | ||||
| 		font-size: 38rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .cutBnt { | ||||
| 		position: absolute; | ||||
| 		width: 162rpx; | ||||
| 		height: 52rpx; | ||||
| 		border-radius: 50rpx; | ||||
| 		font-size: 24rpx; | ||||
| 		color: #fff; | ||||
| 		text-align: center; | ||||
| 		line-height: 52rpx; | ||||
| 		right: 24rpx; | ||||
| 		bottom: 30rpx; | ||||
| 		background: linear-gradient(90deg, #FF7931 0%, #E93323 100%); | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .item .cutBnt .iconfont { | ||||
| 		margin-right: 8rpx; | ||||
| 		font-size: 30rpx; | ||||
| 	} | ||||
| 
 | ||||
| 	.bargain-list .list .load { | ||||
| 		font-size: 24rpx; | ||||
| 		height: 85rpx; | ||||
| 		text-align: center; | ||||
| 		line-height: 85rpx; | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,11 +1,6 @@ | |||
| <!-- 拼团订单的详情 --> | ||||
| <template> | ||||
|   <s-layout | ||||
|     title="拼团详情" | ||||
|     class="detail-wrap" | ||||
|     :navbar="state.data && !state.loading ? 'inner' : 'normal'" | ||||
|     :onShareAppMessage="shareInfo" | ||||
|   > | ||||
|   <s-layout title="拼团详情" class="detail-wrap" :navbar="state.data && !state.loading ? 'inner': 'normal'" :onShareAppMessage="shareInfo"> | ||||
|     <view v-if="state.loading"></view> | ||||
|     <view v-if="state.data && !state.loading"> | ||||
|       <!-- 团长信息 + 活动信息 --> | ||||
|  | @ -27,7 +22,7 @@ | |||
|           priceColor="#E1212B" | ||||
|           @tap=" | ||||
|             sheep.$router.go('/pages/goods/groupon', { | ||||
|               id: state.data.headRecord.activityId, | ||||
|               id: state.data.headRecord.activityId | ||||
|             }) | ||||
|           " | ||||
|           :style="[{ top: Number(statusBarHeight + 108) + 'rpx' }]" | ||||
|  | @ -76,9 +71,7 @@ | |||
|           </view> | ||||
|           <view class="countdown-title ss-flex" v-else> | ||||
|             还差 | ||||
|             <view class="num" | ||||
|               >{{ state.data.headRecord.userSize - state.data.headRecord.userCount }}人</view | ||||
|             > | ||||
|             <view class="num">{{ state.data.headRecord.userSize - state.data.headRecord.userCount }}人</view> | ||||
|             拼团成功 | ||||
|             <view class="ss-flex countdown-time"> | ||||
|               <view class="countdown-h ss-flex ss-row-center">{{ endTime.h }}</view> | ||||
|  | @ -98,7 +91,7 @@ | |||
|         <view class="ss-m-t-60 ss-flex ss-flex-wrap ss-row-center"> | ||||
|           <!-- 团长 --> | ||||
|           <view class="header-avatar ss-m-r-24 ss-m-b-20"> | ||||
|             <image :src="sheep.$url.cdn(state.data.headRecord.avatar) || sheep.$url.static('/static/img/shop/default_avatar.png')" class="avatar-img"></image> | ||||
|             <image :src="sheep.$url.cdn(state.data.headRecord.avatar)" class="avatar-img"></image> | ||||
|             <view class="header-tag ss-flex ss-col-center ss-row-center">团长</view> | ||||
|           </view> | ||||
|           <!-- 团员 --> | ||||
|  | @ -107,7 +100,7 @@ | |||
|             v-for="item in state.data.memberRecords" | ||||
|             :key="item.id" | ||||
|           > | ||||
|             <image :src="sheep.$url.cdn(item.avatar) || sheep.$url.static('/static/img/shop/default_avatar.png')" class="avatar-img"></image> | ||||
|             <image :src="sheep.$url.cdn(item.avatar)" class="avatar-img"></image> | ||||
|             <view | ||||
|               class="header-tag ss-flex ss-col-center ss-row-center" | ||||
|               v-if="item.is_leader == '1'" | ||||
|  | @ -116,11 +109,7 @@ | |||
|             </view> | ||||
|           </view> | ||||
|           <!-- 还有几个坑位 --> | ||||
|           <view | ||||
|             class="default-avatar ss-m-r-24 ss-m-b-20" | ||||
|             v-for="item in state.remainNumber" | ||||
|             :key="item" | ||||
|           > | ||||
|           <view class="default-avatar ss-m-r-24 ss-m-b-20" v-for="item in state.remainNumber" :key="item"> | ||||
|             <image | ||||
|               :src="sheep.$url.static('/static/img/shop/avatar/unknown.png')" | ||||
|               class="avatar-img" | ||||
|  | @ -165,7 +154,11 @@ | |||
|         </view> | ||||
|         <view v-else class="ss-flex ss-row-center"> | ||||
|           <view v-if="state.data.orderId"> | ||||
|             <button class="ss-reset-button join-btn" :disabled="endTime.ms <= 0" @tap="onShare"> | ||||
|             <button | ||||
|               class="ss-reset-button join-btn" | ||||
|               :disabled="endTime.ms <= 0" | ||||
|               @tap="onShare" | ||||
|             > | ||||
|               邀请好友来拼团 | ||||
|             </button> | ||||
|           </view> | ||||
|  | @ -181,11 +174,11 @@ | |||
|         </view> | ||||
|       </view> | ||||
| 
 | ||||
|       <view v-if="!isEmpty(state.goodsInfo)"> | ||||
|         <!-- 规格与数量弹框 --> | ||||
|       <!-- TODO 芋艿:这里暂时没接入 --> | ||||
|       <view v-if="state.data.goods"> | ||||
|         <s-select-groupon-sku | ||||
|           :show="state.showSelectSku" | ||||
|           :goodsInfo="state.goodsInfo" | ||||
|           :goodsInfo="state.data.goods" | ||||
|           :grouponAction="state.grouponAction" | ||||
|           :grouponNum="state.grouponNum" | ||||
|           @buy="onBuy" | ||||
|  | @ -203,29 +196,25 @@ | |||
|   import { computed, reactive } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods'; | ||||
|   import { useDurationTime } from '@/sheep/hooks/useGoods'; | ||||
|   import { showShareModal } from '@/sheep/hooks/useModal'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
|   import CombinationApi from '@/sheep/api/promotion/combination'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { SharePageEnum } from '@/sheep/helper/const'; | ||||
|   import { isEmpty } from 'lodash'; | ||||
|   import CombinationApi from "@/sheep/api/promotion/combination"; | ||||
| 
 | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png'); | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const state = reactive({ | ||||
|     data: {}, // 拼团详情 | ||||
|     goodsId: 0, // 商品ID | ||||
|     goodsInfo: {}, // 商品信息 | ||||
|     showSelectSku: false, // 显示规格弹框 | ||||
|     selectedSkuPrice: {}, // 选中的规格价格 | ||||
|     activity: {}, // 团购活动 | ||||
|     grouponId: 0, // 团购ID | ||||
|     grouponNum: 0, // 团购人数 | ||||
|     grouponAction: 'create', // 团购操作 | ||||
|     combinationHeadId: null, // 拼团团长编号 | ||||
|     loading: true, | ||||
|     grouponAction: 'create', | ||||
|     showSelectSku: false, | ||||
|     grouponNum: 0, | ||||
|     number: 0, | ||||
|     activity: {}, | ||||
|     combinationHeadId: null, // 拼团团长编号 | ||||
|   }); | ||||
| 
 | ||||
|   // todo 芋艿:分享要再接下 | ||||
|   const shareInfo = computed(() => { | ||||
|     if (isEmpty(state.data)) return {}; | ||||
|     return sheep.$platform.share.getShareInfo( | ||||
|  | @ -234,16 +223,16 @@ | |||
|         image: sheep.$url.cdn(state.data.headRecord.picUrl), | ||||
|         desc: state.data.goods?.subtitle, | ||||
|         params: { | ||||
|           page: SharePageEnum.GROUPON_DETAIL.value, | ||||
|           query: state.data.headRecord.id, | ||||
|           page: '5', | ||||
|           query: state.data.id, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         type: 'groupon', // 邀请拼团海报 | ||||
|         title: state.data.headRecord.spuName, // 商品标题 | ||||
|         image: sheep.$url.cdn(state.data.headRecord.picUrl), // 商品主图 | ||||
|         price: fen2yuan(state.data.headRecord.combinationPrice), // 商品价格 | ||||
|         grouponNum: state.data.headRecord.userSize, // 拼团人数 | ||||
|         price: state.data.goods?.price, // 商品价格 | ||||
|         original_price: state.data.goods?.original_price, // 商品原价 | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
|  | @ -255,33 +244,33 @@ | |||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 去开团 | ||||
|   // 去开团 TODO 芋艿:这里没接入 | ||||
|   function onCreateGroupon() { | ||||
|     state.grouponAction = 'create'; | ||||
|     state.grouponId = 0; | ||||
|     state.showSelectSku = true; | ||||
|   } | ||||
| 
 | ||||
|   // 规格变更 | ||||
|   // 规格变更 TODO 芋艿:这里没接入 | ||||
|   function onSkuChange(e) { | ||||
|     state.selectedSkuPrice = e; | ||||
|   } | ||||
| 
 | ||||
|   // 立即参团 | ||||
|   // 立即参团 TODO 芋艿:这里没接入 | ||||
|   function onJoinGroupon() { | ||||
|     state.grouponAction = 'join'; | ||||
|     state.grouponId = state.data.headRecord.activityId; | ||||
|     state.combinationHeadId = state.data.headRecord.id; | ||||
|     state.grouponNum = state.data.headRecord.userSize; | ||||
|     state.grouponId = state.data.activityId; | ||||
|     state.combinationHeadId = state.data.id; | ||||
|     state.grouponNum = state.data.num; | ||||
|     state.showSelectSku = true; | ||||
|   } | ||||
| 
 | ||||
|   // 立即购买 | ||||
|   // 立即购买 TODO 芋艿:这里没接入 | ||||
|   function onBuy(sku) { | ||||
|     sheep.$router.go('/pages/order/confirm', { | ||||
|       data: JSON.stringify({ | ||||
|         order_type: 'goods', | ||||
|         combinationActivityId: state.activity.id, | ||||
|         combinationActivityId: state.data.activity.id, | ||||
|         combinationHeadId: state.combinationHeadId, | ||||
|         items: [ | ||||
|           { | ||||
|  | @ -306,29 +295,8 @@ | |||
|       state.remainNumber = remainNumber > 0 ? remainNumber : 0; | ||||
| 
 | ||||
|       // 获取活动信息 | ||||
|       const { data: activity } = await CombinationApi.getCombinationActivity( | ||||
|         data.headRecord.activityId, | ||||
|       ); | ||||
|       const { data: activity } = await CombinationApi.getCombinationActivity(data.headRecord.activityId); | ||||
|       state.activity = activity; | ||||
|       state.grouponNum = activity.userSize; | ||||
|       // 加载商品信息 | ||||
|       const { data: spu } = await SpuApi.getSpuDetail(activity.spuId); | ||||
|       state.goodsId = spu.id; | ||||
|       // 默认显示最低价 | ||||
|       activity.products.forEach((product) => { | ||||
|         spu.price = Math.min(spu.price, product.combinationPrice); // 设置 SPU 的最低价格 | ||||
|       }); | ||||
|       state.goodsInfo = spu; | ||||
|       // 价格、库存使用活动的 | ||||
|       spu.skus.forEach((sku) => { | ||||
|         const product = activity.products.find((product) => product.skuId === sku.id); | ||||
|         if (product) { | ||||
|           sku.price = product.combinationPrice; | ||||
|         } else { | ||||
|           // 找不到可能是没配置,则不能发起秒杀 | ||||
|           sku.stock = 0; | ||||
|         } | ||||
|       }); | ||||
|     } else { | ||||
|       state.data = null; | ||||
|     } | ||||
|  | @ -348,7 +316,8 @@ | |||
|   .recharge-box { | ||||
|     position: relative; | ||||
|     margin-bottom: 120rpx; | ||||
|     background: v-bind(headerBg) center/750rpx 100% no-repeat, | ||||
|     background: v-bind(headerBg) center/750rpx 100% | ||||
|         no-repeat, | ||||
|       linear-gradient(115deg, #f44739 0%, #ff6600 100%); | ||||
|     border-radius: 0 0 5% 5%; | ||||
|     height: 100rpx; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 拼团活动列表 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FE832A' }" navbar="inner"> | ||||
|   <s-layout navbar="inner" :bgStyle="{ color: '#FE832A' }"> | ||||
|     <view class="page-bg" :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]" /> | ||||
|     <view class="list-content"> | ||||
|       <!-- 参团会员统计 --> | ||||
|  |  | |||
|  | @ -24,9 +24,9 @@ | |||
|         </view> | ||||
|         <view class="border-bottom"> | ||||
|           <s-goods-item | ||||
|             :img="record.picUrl" | ||||
|             :title="record.spuName" | ||||
|             :price="record.combinationPrice" | ||||
|               :img="record.picUrl" | ||||
|               :title="record.spuName" | ||||
|               :price="record.combinationPrice" | ||||
|           > | ||||
|             <template #groupon> | ||||
|               <view class="ss-flex"> | ||||
|  | @ -67,9 +67,9 @@ | |||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { formatOrderColor } from '@/sheep/hooks/useGoods'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import _ from 'lodash'; | ||||
|   import {formatOrderColor} from "@/sheep/hooks/useGoods"; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
|   import CombinationApi from '@/sheep/api/promotion/combination'; | ||||
| 
 | ||||
|   // 数据 | ||||
|  | @ -121,7 +121,7 @@ | |||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list) | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|   } | ||||
|  | @ -149,7 +149,6 @@ | |||
| 
 | ||||
|   //下拉刷新 | ||||
|   onPullDownRefresh(() => { | ||||
|     resetPagination(state.pagination); | ||||
|     getGrouponList(); | ||||
|     setTimeout(function () { | ||||
|       uni.stopPullDownRefresh(); | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ | |||
|         <view class="type-text ss-flex ss-row-center">满减:</view> | ||||
|         <view class="ss-flex-1"> | ||||
|           <view class="tip-content" v-for="item in state.activityInfo.rules" :key="item"> | ||||
|             {{ item.description }} | ||||
|             {{ formatRewardActivityRule(state.activityInfo, item) }} | ||||
|           </view> | ||||
|         </view> | ||||
|         <image class="activity-left-image" src="/static/activity-left.png" /> | ||||
|  | @ -63,11 +63,10 @@ | |||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import RewardActivityApi from '@/sheep/api/promotion/rewardActivity'; | ||||
|   import { formatRewardActivityRule } from '@/sheep/hooks/useGoods'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { appendSettlementProduct } from '@/sheep/hooks/useGoods'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     activityId: 0, // 获得编号 | ||||
|  | @ -119,18 +118,11 @@ | |||
|     const { code, data } = await SpuApi.getSpuPage({ | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       ...params, | ||||
|       ...params | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     // 拼接结算信息(营销) | ||||
|     await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => { | ||||
|       if (res.code !== 0) { | ||||
|         return; | ||||
|       } | ||||
|       appendSettlementProduct(data.list, res.data); | ||||
|     }); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|  |  | |||
|  | @ -1,76 +0,0 @@ | |||
| <!-- 积分商城:商品列表  --> | ||||
| <template> | ||||
|   <s-layout title="积分商城" navbar="normal" :leftWidth="0" :rightWidth="0"> | ||||
|     <scroll-view | ||||
|       class="scroll-box" | ||||
|       :style="{ height: pageHeight + 'rpx' }" | ||||
|       scroll-y="true" | ||||
|       :scroll-with-animation="false" | ||||
|       :enable-back-to-top="true" | ||||
|     > | ||||
|       <s-point-card ref="sPointCardRef" class="ss-p-x-20 ss-m-t-20" /> | ||||
|       <s-empty | ||||
|         v-if="activityTotal === 0" | ||||
|         icon="/static/goods-empty.png" | ||||
|         text="暂无积分商品" | ||||
|       ></s-empty> | ||||
|       <uni-load-more | ||||
|         v-if="activityTotal > 0" | ||||
|         :status="loadStatus" | ||||
|         :content-text="{ | ||||
|           contentdown: '上拉加载更多', | ||||
|         }" | ||||
|         @tap="loadMore" | ||||
|       /> | ||||
|     </scroll-view> | ||||
|   </s-layout> | ||||
| </template> | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive, ref } from 'vue'; | ||||
|   import PointApi from '@/sheep/api/promotion/point'; | ||||
|   import SLayout from '@/sheep/components/s-layout/s-layout.vue'; | ||||
| 
 | ||||
|   // 计算页面高度 | ||||
|   const { safeAreaInsets, safeArea } = sheep.$platform.device; | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const pageHeight = | ||||
|     (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350; | ||||
| 
 | ||||
|   const sPointCardRef = ref(); | ||||
|   // 查询活动列表 | ||||
|   const activityPageParams = reactive({ | ||||
|     pageNo: 1, // 页码 | ||||
|     pageSize: 5, // 每页数量 | ||||
|   }); | ||||
| 
 | ||||
|   const activityTotal = ref(0); // 活动总数 | ||||
|   const activityCount = ref(0); // 已加载活动数量 | ||||
|   const loadStatus = ref(''); // 页面加载状态 | ||||
|   async function getActivityList() { | ||||
|     loadStatus.value = 'loading'; | ||||
|     const { data } = await PointApi.getPointActivityPage(activityPageParams); | ||||
|     await sPointCardRef.value.concatActivity(data.list); | ||||
|     activityCount.value = sPointCardRef.value.getActivityCount(); | ||||
|     activityTotal.value = data.total; | ||||
| 
 | ||||
|     loadStatus.value = activityCount.value < activityTotal.value ? 'more' : 'noMore'; | ||||
|   } | ||||
| 
 | ||||
|   // 加载更多 | ||||
|   function loadMore() { | ||||
|     if (loadStatus.value !== 'noMore') { | ||||
|       activityPageParams.pageNo += 1; | ||||
|       getActivityList(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 上拉加载更多 | ||||
|   onReachBottom(() => { | ||||
|     loadMore(); | ||||
|   }); | ||||
|   onLoad(() => { | ||||
|     getActivityList(); | ||||
|   }); | ||||
| </script> | ||||
|  | @ -1,22 +1,15 @@ | |||
| <!-- 秒杀活动列表 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: 'rgb(245,28,19)' }" navbar="inner"> | ||||
|   <s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }"> | ||||
|     <!--顶部背景图--> | ||||
|     <view | ||||
|       class="page-bg" | ||||
|       :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]" | ||||
|         class="page-bg" | ||||
|         :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]" | ||||
|     ></view> | ||||
|     <!-- 时间段轮播图 --> | ||||
|     <view class="header" v-if="activeTimeConfig?.sliderPicUrls?.length > 0"> | ||||
|       <swiper | ||||
|         indicator-dots="true" | ||||
|         autoplay="true" | ||||
|         :circular="true" | ||||
|         interval="3000" | ||||
|         duration="1500" | ||||
|         indicator-color="rgba(255,255,255,0.6)" | ||||
|         indicator-active-color="#fff" | ||||
|       > | ||||
|       <swiper indicator-dots="true" autoplay="true" :circular="true" interval="3000" duration="1500" | ||||
|               indicator-color="rgba(255,255,255,0.6)" indicator-active-color="#fff"> | ||||
|         <block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index"> | ||||
|           <swiper-item class="borRadius14"> | ||||
|             <image :src="picUrl" class="slide-image borRadius14" lazy-load /> | ||||
|  | @ -28,28 +21,18 @@ | |||
|     <view class="flex align-center justify-between ss-p-25"> | ||||
|       <!-- 左侧图标 --> | ||||
|       <view class="time-icon"> | ||||
|         <image | ||||
|           class="ss-w-100 ss-h-100" | ||||
|           :src="sheep.$url.static('/static/img/shop/priceTag.png')" | ||||
|         /> | ||||
|         <!-- TODO 芋艿:图片统一维护 --> | ||||
|         <image class="ss-w-100 ss-h-100" src="http://mall.yudao.iocoder.cn/static/images/priceTag.png" /> | ||||
|       </view> | ||||
|       <scroll-view | ||||
|         class="time-list" | ||||
|         :scroll-into-view="activeTimeElId" | ||||
|         scroll-x | ||||
|         scroll-with-animation | ||||
|       > | ||||
|         <view | ||||
|           v-for="(config, index) in timeConfigList" | ||||
|           :key="index" | ||||
|           :class="['item', { active: activeTimeIndex === index }]" | ||||
|           :id="`timeItem${index}`" | ||||
|           @tap="handleChangeTimeConfig(index, config.id)" | ||||
|         > | ||||
|       <scroll-view class="time-list" :scroll-into-view="activeTimeElId" scroll-x scroll-with-animation> | ||||
|         <view v-for="(config, index) in timeConfigList" :key="index" | ||||
|               :class="['item', { active: activeTimeIndex === index}]" | ||||
|               :id="`timeItem${index}`" | ||||
|               @tap="handleChangeTimeConfig(index)"> | ||||
|           <!-- 活动起始时间 --> | ||||
|           <view class="time">{{ config.startTime }}</view> | ||||
|           <!-- 活动状态 --> | ||||
|           <view class="status">{{ config?.status }}</view> | ||||
|           <view class="status">{{ config.status }}</view> | ||||
|         </view> | ||||
|       </scroll-view> | ||||
|     </view> | ||||
|  | @ -59,10 +42,7 @@ | |||
|       <!-- 活动倒计时 --> | ||||
|       <view class="content-header ss-flex-col ss-col-center ss-row-center"> | ||||
|         <view class="content-header-box ss-flex ss-row-center"> | ||||
|           <view | ||||
|             class="countdown-box ss-flex" | ||||
|             v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED" | ||||
|           > | ||||
|           <view class="countdown-box ss-flex" v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"> | ||||
|             <view class="countdown-title ss-m-r-12">距结束</view> | ||||
|             <view class="ss-flex countdown-time"> | ||||
|               <view class="ss-flex countdown-h">{{ countDown.h }}</view> | ||||
|  | @ -90,44 +70,19 @@ | |||
|             :data="{ ...activity, price: activity.seckillPrice }" | ||||
|             :goodsFields="goodsFields" | ||||
|             :seckillTag="true" | ||||
|             @click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })" | ||||
|           > | ||||
|             <!-- 抢购进度 --> | ||||
|             <template #activity> | ||||
|               <view class="limit"> | ||||
|                 限量 | ||||
|                 <text class="ss-m-l-5">{{ activity.stock }} {{ activity.unitName }}</text> | ||||
|               </view> | ||||
|               <view class="limit">限量 <text class="ss-m-l-5">{{ activity.stock}} {{activity.unitName}}</text></view> | ||||
|               <su-progress :percentage="activity.percent" strokeWidth="10" textInside isAnimate /> | ||||
|             </template> | ||||
|             <!-- 抢购按钮 --> | ||||
|             <template #cart> | ||||
|               <button | ||||
|                 :class="[ | ||||
|                   'ss-reset-button cart-btn', | ||||
|                   { disabled: activeTimeConfig?.status === TimeStatusEnum.END }, | ||||
|                 ]" | ||||
|                 v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START" | ||||
|               > | ||||
|                 <span>未开始</span> | ||||
|               </button> | ||||
|               <button | ||||
|                 :class="[ | ||||
|                   'ss-reset-button cart-btn', | ||||
|                   { disabled: activeTimeConfig?.status === TimeStatusEnum.END }, | ||||
|                 ]" | ||||
|                 @click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })" | ||||
|                 v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED" | ||||
|               > | ||||
|                 <span>马上抢</span> | ||||
|               </button> | ||||
|               <button | ||||
|                 :class="[ | ||||
|                   'ss-reset-button cart-btn', | ||||
|                   { disabled: activeTimeConfig?.status === TimeStatusEnum.END }, | ||||
|                 ]" | ||||
|                 v-else | ||||
|               > | ||||
|                 <span>已结束</span> | ||||
|               <button :class="['ss-reset-button cart-btn', { disabled: activeTimeConfig.status === TimeStatusEnum.END }]"> | ||||
|                 <span v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START">未开始</span> | ||||
|                 <span v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">马上抢</span> | ||||
|                 <span v-else>已结束</span> | ||||
|               </button> | ||||
|             </template> | ||||
|           </s-goods-column> | ||||
|  | @ -145,51 +100,40 @@ | |||
|   </s-layout> | ||||
| </template> | ||||
| <script setup> | ||||
|   import { computed, nextTick, reactive, ref } from 'vue'; | ||||
|   import {reactive, computed, ref, nextTick} from 'vue'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { useDurationTime } from '@/sheep/hooks/useGoods'; | ||||
|   import SeckillApi from '@/sheep/api/promotion/seckill'; | ||||
|   import dayjs from 'dayjs'; | ||||
|   import { TimeStatusEnum } from '@/sheep/helper/const'; | ||||
|   import SeckillApi from "@/sheep/api/promotion/seckill"; | ||||
|   import dayjs from "dayjs"; | ||||
|   import {TimeStatusEnum} from "@/sheep/util/const"; | ||||
| 
 | ||||
|   // 计算页面高度 | ||||
|   const { safeAreaInsets, safeArea } = sheep.$platform.device; | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const pageHeight = | ||||
|     (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350; | ||||
|   const pageHeight = (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350; | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-header.png'); | ||||
| 
 | ||||
|   // 商品控件显示的字段(不显示库存、销量。改为显示自定义的进度条) | ||||
|   const goodsFields = { | ||||
|     name: { | ||||
|       show: true, | ||||
|     }, | ||||
|     introduction: { | ||||
|       show: true, | ||||
|     }, | ||||
|     price: { | ||||
|       show: true, | ||||
|     }, | ||||
|     marketPrice: { | ||||
|       show: true, | ||||
|     }, | ||||
|     name: { show: true }, | ||||
|     introduction: { show: true }, | ||||
|     price: { show: true }, | ||||
|     marketPrice: { show: true }, | ||||
|   }; | ||||
| 
 | ||||
|   //#region 时间段 | ||||
|   // 时间段列表 | ||||
|   const timeConfigList = ref([]); | ||||
|   const timeConfigList = ref([]) | ||||
|   // 查询时间段 | ||||
|   const getSeckillConfigList = async () => { | ||||
|     const { data } = await SeckillApi.getSeckillConfigList(); | ||||
|     const { data } = await SeckillApi.getSeckillConfigList() | ||||
|     const now = dayjs(); | ||||
|     const today = now.format('YYYY-MM-DD'); | ||||
|     const select = ref([]); | ||||
|     const today = now.format('YYYY-MM-DD') | ||||
|     // 判断时间段的状态 | ||||
|     data.forEach((config, index) => { | ||||
|       const startTime = dayjs(`${today} ${config.startTime}`); | ||||
|       const endTime = dayjs(`${today} ${config.endTime}`); | ||||
|       select.value[index] = config.id; | ||||
|       const startTime = dayjs(`${today} ${config.startTime}`) | ||||
|       const endTime = dayjs(`${today} ${config.endTime}`) | ||||
|       if (now.isBefore(startTime)) { | ||||
|         config.status = TimeStatusEnum.WAIT_START; | ||||
|       } else if (now.isAfter(endTime)) { | ||||
|  | @ -198,36 +142,35 @@ | |||
|         config.status = TimeStatusEnum.STARTED; | ||||
|         activeTimeIndex.value = index; | ||||
|       } | ||||
|     }); | ||||
|     timeConfigList.value = data; | ||||
|     }) | ||||
|     timeConfigList.value = data | ||||
|     // 默认选中进行中的活动 | ||||
|     handleChangeTimeConfig(activeTimeIndex.value, select.value[activeTimeIndex.value]); | ||||
|     handleChangeTimeConfig(activeTimeIndex.value); | ||||
|     // 滚动到进行中的时间段 | ||||
|     scrollToTimeConfig(activeTimeIndex.value); | ||||
|   }; | ||||
|     scrollToTimeConfig(activeTimeIndex.value) | ||||
|   } | ||||
| 
 | ||||
|   // 滚动到指定时间段 | ||||
|   const activeTimeElId = ref(''); // 当前选中的时间段的元素ID | ||||
|   const activeTimeElId = ref('') // 当前选中的时间段的元素ID | ||||
|   const scrollToTimeConfig = (index) => { | ||||
|     nextTick(() => (activeTimeElId.value = `timeItem${index}`)); | ||||
|   }; | ||||
|     nextTick(() => activeTimeElId.value = `timeItem${index}`) | ||||
|   } | ||||
| 
 | ||||
|   // 切换时间段 | ||||
|   const activeTimeIndex = ref(0); // 当前选中的时间段的索引 | ||||
|   const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]); // 当前选中的时间段 | ||||
|   const handleChangeTimeConfig = (index, id) => { | ||||
|     activeTimeIndex.value = index; | ||||
|   const activeTimeIndex = ref(0) // 当前选中的时间段的索引 | ||||
|   const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]) // 当前选中的时间段 | ||||
|   const handleChangeTimeConfig = (index) => { | ||||
|     activeTimeIndex.value = index | ||||
| 
 | ||||
|     // 查询活动列表 | ||||
|     activityPageParams.pageNo = 1; | ||||
|     activityPageParams.configId = id; | ||||
|     activityList.value = []; | ||||
|     activityPageParams.pageNo = 1 | ||||
|     activityList.value = [] | ||||
|     getActivityList(); | ||||
|   }; | ||||
|   } | ||||
| 
 | ||||
|   // 倒计时 | ||||
|   const countDown = computed(() => { | ||||
|     const endTime = activeTimeConfig.value?.endTime; | ||||
|     const endTime = activeTimeConfig.value?.endTime | ||||
|     if (endTime) { | ||||
|       return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`); | ||||
|     } | ||||
|  | @ -239,22 +182,20 @@ | |||
| 
 | ||||
|   // 查询活动列表 | ||||
|   const activityPageParams = reactive({ | ||||
|     configId: 0, // 时间段 ID | ||||
|     id: 0, // 时间段 ID | ||||
|     pageNo: 1, // 页码 | ||||
|     pageSize: 5, // 每页数量 | ||||
|   }); | ||||
|   const activityTotal = ref(0); // 活动总数 | ||||
|   const activityList = ref([]); // 活动列表 | ||||
|   const loadStatus = ref(''); // 页面加载状态 | ||||
|   }) | ||||
|   const activityTotal = ref(0) // 活动总数 | ||||
|   const activityList = ref([]) // 活动列表 | ||||
|   const loadStatus = ref('') // 页面加载状态 | ||||
|   async function getActivityList() { | ||||
|     loadStatus.value = 'loading'; | ||||
|     const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams); | ||||
|     data.list.forEach((activity) => { | ||||
|     const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams) | ||||
|     data.list.forEach(activity => { | ||||
|       // 计算抢购进度 | ||||
|       activity.percent = parseInt( | ||||
|         (100 * (activity.totalStock - activity.stock)) / activity.totalStock, | ||||
|       ); | ||||
|     }); | ||||
|       activity.percent = parseInt(100 * (activity.totalStock - activity.stock) / activity.totalStock); | ||||
|     }) | ||||
|     activityList.value = activityList.value.concat(...data.list); | ||||
|     activityTotal.value = data.total; | ||||
| 
 | ||||
|  | @ -264,7 +205,7 @@ | |||
|   // 加载更多 | ||||
|   function loadMore() { | ||||
|     if (loadStatus.value !== 'noMore') { | ||||
|       activityPageParams.pageNo += 1; | ||||
|       activityPageParams.pageNo += 1 | ||||
|       getActivityList(); | ||||
|     } | ||||
|   } | ||||
|  | @ -275,7 +216,7 @@ | |||
| 
 | ||||
|   // 页面初始化 | ||||
|   onLoad(async () => { | ||||
|     await getSeckillConfigList(); | ||||
|     await getSeckillConfigList() | ||||
|   }); | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
|  | @ -294,8 +235,7 @@ | |||
|     margin: -276rpx auto 0 auto; | ||||
|     border-radius: 14rpx; | ||||
|     overflow: hidden; | ||||
| 
 | ||||
|     swiper { | ||||
|     swiper{ | ||||
|       height: 330rpx !important; | ||||
|       border-radius: 14rpx; | ||||
|       overflow: hidden; | ||||
|  | @ -306,8 +246,7 @@ | |||
|       height: 100%; | ||||
|       border-radius: 14rpx; | ||||
|       overflow: hidden; | ||||
| 
 | ||||
|       img { | ||||
|       img{ | ||||
|         border-radius: 14rpx; | ||||
|       } | ||||
|     } | ||||
|  | @ -318,12 +257,10 @@ | |||
|     width: 75rpx; | ||||
|     height: 70rpx; | ||||
|   } | ||||
| 
 | ||||
|   // 时间段列表 | ||||
|   .time-list { | ||||
|     width: 596rpx; | ||||
|     white-space: nowrap; | ||||
| 
 | ||||
|     // 时间段 | ||||
|     .item { | ||||
|       display: inline-block; | ||||
|  | @ -333,20 +270,17 @@ | |||
|       box-sizing: border-box; | ||||
|       margin-right: 30rpx; | ||||
|       width: 130rpx; | ||||
| 
 | ||||
|       // 开始时间 | ||||
|       .time { | ||||
|         font-size: 36rpx; | ||||
|         font-weight: 600; | ||||
|         color: #333; | ||||
|       } | ||||
| 
 | ||||
|       // 选中的时间段 | ||||
|       &.active { | ||||
|         .time { | ||||
|           color: var(--ui-BG-Main); | ||||
|         } | ||||
| 
 | ||||
|         // 状态 | ||||
|         .status { | ||||
|           height: 30rpx; | ||||
|  | @ -367,7 +301,6 @@ | |||
|     margin: 0 20rpx 0 20rpx; | ||||
|     background: #fff; | ||||
|     border-radius: 20rpx 20rpx 0 0; | ||||
| 
 | ||||
|     .content-header { | ||||
|       width: 100%; | ||||
|       border-radius: 20rpx 20rpx 0 0; | ||||
|  | @ -379,7 +312,6 @@ | |||
|         height: 64rpx; | ||||
|         background: rgba($color: #fff, $alpha: 0.66); | ||||
|         border-radius: 32px; | ||||
| 
 | ||||
|         // 场次倒计时内容 | ||||
|         .countdown-title { | ||||
|           font-size: 28rpx; | ||||
|  | @ -387,12 +319,10 @@ | |||
|           color: #333333; | ||||
|           line-height: 28rpx; | ||||
|         } | ||||
| 
 | ||||
|         // 场次倒计时 | ||||
|         .countdown-time { | ||||
|           font-size: 28rpx; | ||||
|           color: rgba(#ed3c30, 0.23); | ||||
| 
 | ||||
|           // 场次倒计时:小时部分 | ||||
|           .countdown-h { | ||||
|             font-size: 24rpx; | ||||
|  | @ -404,7 +334,6 @@ | |||
|             background: rgba(#ed3c30, 0.23); | ||||
|             border-radius: 6rpx; | ||||
|           } | ||||
| 
 | ||||
|           // 场次倒计时:分钟、秒 | ||||
|           .countdown-num { | ||||
|             font-size: 24rpx; | ||||
|  | @ -419,15 +348,12 @@ | |||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // 活动列表 | ||||
|     .scroll-box { | ||||
|       height: 900rpx; | ||||
| 
 | ||||
|       // 活动 | ||||
|       .goods-box { | ||||
|         position: relative; | ||||
| 
 | ||||
|         // 抢购按钮 | ||||
|         .cart-btn { | ||||
|           position: absolute; | ||||
|  | @ -447,7 +373,6 @@ | |||
|             color: #fff; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         // 秒杀限量商品数 | ||||
|         .limit { | ||||
|           font-size: 22rpx; | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
|           <view class="item" v-for="(item, index) in state.signConfigList" :key="index"> | ||||
|             <view | ||||
|               :class=" | ||||
|                 (index === state.signConfigList.length ? 'reward' : '') + | ||||
|                 (index  === state.signConfigList.length ? 'reward' : '') + | ||||
|                 ' ' + | ||||
|                 (state.signInfo.continuousDay >= item.day ? 'rewardTxt' : '') | ||||
|               " | ||||
|  | @ -62,14 +62,13 @@ | |||
|         </view> | ||||
|       </view> | ||||
| 
 | ||||
|       <!-- 签到说明 --> | ||||
|       <!-- 签到说明 TODO @科举:这里改成【已累计签到】 --> | ||||
|       <view class="bg-white ss-m-t-16 ss-p-t-30 ss-p-b-60 ss-p-x-40"> | ||||
|         <view class="activity-title ss-m-b-30">签到说明</view> | ||||
|         <view class="activity-des">1.已累计签到{{ state.signInfo.totalDay }}天</view> | ||||
|         <view class="activity-des">1、已累计签到{{state.signInfo.totalDay}}天</view> | ||||
|         <view class="activity-des"> | ||||
|           2.据说连续签到第 {{ state.maxDay }} 天可获得超额积分,要坚持签到哦~~ | ||||
|           2、据说连续签到第 {{ state.maxDay }} 天可获得超额积分,一定要坚持签到哦~~~ | ||||
|         </view> | ||||
|         <view class="activity-des"> 3.积分可以在购物时抵现金结算的哦 ~~</view> | ||||
|       </view> | ||||
|     </view> | ||||
| 
 | ||||
|  | @ -111,7 +110,7 @@ | |||
|     signInfo: {}, // 签到信息 | ||||
| 
 | ||||
|     signConfigList: [], // 签到配置列表 | ||||
|     maxDay: 0, // 最大的签到天数 | ||||
|     maxDay: 0, // 最大的签到天数  | ||||
| 
 | ||||
|     showModel: false, // 签到弹框 | ||||
|     signResult: {}, // 签到结果 | ||||
|  | @ -160,6 +159,7 @@ | |||
|     getSignInfo(); | ||||
|     getSignConfigList(); | ||||
|   }); | ||||
|   // TODO 芋艿:1)css 需要优化,例如说引入的图片;2)删除多余的样式 | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  | @ -347,7 +347,8 @@ | |||
|       .title { | ||||
|         font-size: 34rpx; | ||||
|         font-weight: bold; | ||||
|         color: var(--ui-BG-Main-TC); | ||||
|         // color: var(--ui-BG-Main); | ||||
|         color: #ff6000; | ||||
|       } | ||||
| 
 | ||||
|       .subtitle { | ||||
|  | @ -409,10 +410,12 @@ | |||
|     background-color: #f4b409; | ||||
|     border-radius: 16rpx; | ||||
|     font-size: 20rpx; | ||||
|     color: var(--ui-BG-Main-TC); | ||||
|     color: #a57d3f; | ||||
|     line-height: 32rpx; | ||||
|     text-align: center; | ||||
|     padding: 2rpx; | ||||
|   } | ||||
| 
 | ||||
|   .venusSelect { | ||||
|     background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowYWFmYjU3Mi03MGJhLTRiNDctOTI2Yi0zOThlZDkzZDkxMDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzkwRkI4NEFEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzkwRkI4NDlEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZmRjNTM0MmUtNmFkOC1iMDRhLThjZTEtMjk2YWYzM2FkMmUxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBhYWZiNTcyLTcwYmEtNGI0Ny05MjZiLTM5OGVkOTNkOTEwOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkX00M0AAAfVSURBVHjaxFpdcBNVFD67STbppn9poU0LtSBYykBlgKrjqKOtihbq8ICKILyJwxM+6fjCk2/6pC+O4hs+6fgAbWFw1OID+ICMAwwKlRmKLW3Tpvlrskk3m13Pvdm0NNn//HA73+ymd3Pv+e4595xzT5bJ3DgJNWyMelUqNaD36a8N+9kaEeMR3yAERES952sxsbtya2lIbgQxqH72IT5EbEW8pZKuHkGlugyLyT3aBtW+qpJkKb/qgEeMIAYNnhlUn+Edz2NuokqtNVdTTbqVyhO0Q67qJMt2MnXbTq/cp+9+ZkyOYYFxN4EiLaF5SbokccyKkWRS1z4we4ZDfIE4hmhxvJLNu8HTtg85ekGRRcjOXwIp9pfT4aKIs4iP+f4zYrl78HPEqbLMJNAPXHBI/SQjSTd+PoD3LpCi15wMGVBlSiM+NfSihJ8Jjlt4Rheu5n7wtL+B93IJPO37aH8Z45+ohBdtKUtz7a+jJLK+/dN+BTX5p5MpWh6HF6XNE9iLwr9mSG6VZP65bPR6NVI1BwQZF3BtA+Bq2oG3Pt3HFCVnfUHaX6XQHCeXgVz8Nojz4+SDzTgo2yfIBV9G89utzi5DtRvDcnQ+JSciyd+rH+hdjb22tFOp5mreCUrocg0CPet5LATJvHbldWSiGllIzZpdeR2ZqFPtZTNJSIQeQGv3DucEbcrLmkRSXvP/cs4RZm6Ow/T1McffpyiSJTZ+jDfOZFDlOuARo5p9aKJ2IYkpCN+9AmLsIcSm/3Y0BkWpPCPRX9/nDVI1BTTAI0YRA9r99gWbv3MF90MGvG4WQrfHnRMslWcQMRL55SivbaJk064FjxhFDGj0gad5u22zkrMZiExcBQ7JcW4GlhfuQ3L+gSMTzc9fItcgYiTy8xHeTIOGmnMHtoEn2G971cP3MAUTk+B2MeBBkh4kGbp92ZEGyfxEDj1NLl56j9fbg4U9N6C179zNPZhK7cV7yRZkSYTI3atIjgUXywCLINf03L8gRGZtj0dA5CDyaMg5SPZk+OLhFZLMwtg7hTLDKGJAM09s6QGuY6+upxLTKcgkFmE5FUdFIYQEiHgv4VURl4BjgZon0SA9EeKKi1kZliU8Nnn84OabwIPw+huB8zdR+OqbwdcYMAwB4ux1yEYmtLp+I5WBdft/EApx8Cs9cu6mblyxXXTl9FpOFGDyjzGQMwlwIQlUEDAMagqvhBTVHl4ZplDezpsq+UdOTkMuIcByfBYElCWHmsgpLHQ/NwTe+gaTBH0XWkgGpPgDrfLHl4gTTOj8IVLdTqkF2aIslwF+2zCeGDjzIJ5OwtSVc6Cko9QMCUmWyZMiZJn8cI8E7Lwm11wJOTypBPuHoCG4yVrgz2VBmFBj69pGTvv1hVSN0XSxHE8LRUbaW9G01wdPvHgQQtcugJwMU5LFpIrWDjXMUGIsMNRR5DwcBPcMQV1L0NKchYHIkU2WkiVrTvgT6XEjyN/TY08R5KyAaWc6n3tagMvjhuCzQ+jlOmylVPRZjw/an9kPdYF1lucjIPLJmERoyP9j+8GflIIXPYW4XOKVJAmWZ27Y8nAMaq5t9yB4WjeumJ4hOaIErx/W978JXH2TbY9K5ZNKMhzC5dSjyTapQ5IyFxozvLJGz9Hp/Am+Y7utH8kan+yDhYVp8lVgdDWX33f+ji3gruNpnLPTxNl/8vKtbeTAeKDj0DmhuGShT3Jxkp4guGCv5cnT0QX0hgq4dOnBSshIxxehUbZJbu4OSJEpbXJvnxf0zoP6JMP/keVGTfZYEiCDQlttmXjE1hlTnJ3A+Ketuc53RwSz86AuSXFxitYzueBWUyGWE9E1pqiozoRhXdQJMLAaEyUMMdJyGp2Ux4Lm7iG5h5rkNhweFUpLFtonZANNTmOA3WzmFiGLGY3XlSel0DpOCzRs6gWXj4fkgwnIhKZIcKDhgsTLTCICfMs683gb1tbchiNjgt0TfYFkym7JQkyl8nkoixkMesiGzT1Q19q20t+0dTvwnU/A0iQSDYeoNinBQLOTE/2BjUcvCE5rMoJ2XcSYYBr3FOPmoHHLU+APdtJMpvg7bp8XAr19mKJ1weK9u5CJxUDpMt+HxfJ2HbsoVLzwa1aT4fw8dD3/Au43lv7YYjQF19gAHXv6UYMJa7UepQZFJzMT9fp9lJidorCvgbfkSRXbBB2UDRX5MdREnZYNwZECRcxQXLUnl8s5KPw6MNFsdBFzzdaaE8xGwrUx0eXZORrw3c1NNdEk0ZwUi2OQn7fvZBz9fEZKDjNzFLqn7dYApnVtNtKvecx5oxVfHCsmSt4ts/0rrxiO5NO6jvUWyC0guZgT+SPllu4Jzjr9AT0bjqKWQ1qH0RWQfvKcwzm+s6BB01X6RC1pHIf82w02NRmnsng7WjX28iJqLu5Ec4XXSE5XYg+S91A+UlHS/H2riXfq1n3N8mM2HKOOgutsodkNqZKIMxGQokvFw40jhnFMoZZ70CzyrpLd2S0kb00Oa5KMJDC8LAHL4QEmK4HGKYaSq+/bJFTyZ/GyX+VK3pzUStA1SRJrkTNZrWHG1e8IGuMZt5eqrUH9U8iwUbVci1w1BKnW65RWSVaVnBomAKoI3E9IQEEipX3jap9Q1hxmBElBocp/AmIYcQaRQSQQ36r/E8odvepOxoa5khfRT1pf+8q0/wUYAFU/P0XyeZQPAAAAAElFTkSuQmCC'); | ||||
|   } | ||||
| 
 | ||||
|   .venus { | ||||
|  | @ -421,11 +424,7 @@ | |||
|     background-size: 100% 100%; | ||||
|     width: 56rpx; | ||||
|     height: 56rpx; | ||||
|     margin: 10rpx auto; | ||||
|   } | ||||
| 
 | ||||
|   .venusSelect { | ||||
|     background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowYWFmYjU3Mi03MGJhLTRiNDctOTI2Yi0zOThlZDkzZDkxMDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzkwRkI4NEFEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzkwRkI4NDlEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZmRjNTM0MmUtNmFkOC1iMDRhLThjZTEtMjk2YWYzM2FkMmUxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBhYWZiNTcyLTcwYmEtNGI0Ny05MjZiLTM5OGVkOTNkOTEwOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkX00M0AAAfVSURBVHjaxFpdcBNVFD67STbppn9poU0LtSBYykBlgKrjqKOtihbq8ICKILyJwxM+6fjCk2/6pC+O4hs+6fgAbWFw1OID+ICMAwwKlRmKLW3Tpvlrskk3m13Pvdm0NNn//HA73+ymd3Pv+e4595xzT5bJ3DgJNWyMelUqNaD36a8N+9kaEeMR3yAERES952sxsbtya2lIbgQxqH72IT5EbEW8pZKuHkGlugyLyT3aBtW+qpJkKb/qgEeMIAYNnhlUn+Edz2NuokqtNVdTTbqVyhO0Q67qJMt2MnXbTq/cp+9+ZkyOYYFxN4EiLaF5SbokccyKkWRS1z4we4ZDfIE4hmhxvJLNu8HTtg85ekGRRcjOXwIp9pfT4aKIs4iP+f4zYrl78HPEqbLMJNAPXHBI/SQjSTd+PoD3LpCi15wMGVBlSiM+NfSihJ8Jjlt4Rheu5n7wtL+B93IJPO37aH8Z45+ohBdtKUtz7a+jJLK+/dN+BTX5p5MpWh6HF6XNE9iLwr9mSG6VZP65bPR6NVI1BwQZF3BtA+Bq2oG3Pt3HFCVnfUHaX6XQHCeXgVz8Nojz4+SDzTgo2yfIBV9G89utzi5DtRvDcnQ+JSciyd+rH+hdjb22tFOp5mreCUrocg0CPet5LATJvHbldWSiGllIzZpdeR2ZqFPtZTNJSIQeQGv3DucEbcrLmkRSXvP/cs4RZm6Ow/T1McffpyiSJTZ+jDfOZFDlOuARo5p9aKJ2IYkpCN+9AmLsIcSm/3Y0BkWpPCPRX9/nDVI1BTTAI0YRA9r99gWbv3MF90MGvG4WQrfHnRMslWcQMRL55SivbaJk064FjxhFDGj0gad5u22zkrMZiExcBQ7JcW4GlhfuQ3L+gSMTzc9fItcgYiTy8xHeTIOGmnMHtoEn2G971cP3MAUTk+B2MeBBkh4kGbp92ZEGyfxEDj1NLl56j9fbg4U9N6C179zNPZhK7cV7yRZkSYTI3atIjgUXywCLINf03L8gRGZtj0dA5CDyaMg5SPZk+OLhFZLMwtg7hTLDKGJAM09s6QGuY6+upxLTKcgkFmE5FUdFIYQEiHgv4VURl4BjgZon0SA9EeKKi1kZliU8Nnn84OabwIPw+huB8zdR+OqbwdcYMAwB4ux1yEYmtLp+I5WBdft/EApx8Cs9cu6mblyxXXTl9FpOFGDyjzGQMwlwIQlUEDAMagqvhBTVHl4ZplDezpsq+UdOTkMuIcByfBYElCWHmsgpLHQ/NwTe+gaTBH0XWkgGpPgDrfLHl4gTTOj8IVLdTqkF2aIslwF+2zCeGDjzIJ5OwtSVc6Cko9QMCUmWyZMiZJn8cI8E7Lwm11wJOTypBPuHoCG4yVrgz2VBmFBj69pGTvv1hVSN0XSxHE8LRUbaW9G01wdPvHgQQtcugJwMU5LFpIrWDjXMUGIsMNRR5DwcBPcMQV1L0NKchYHIkU2WkiVrTvgT6XEjyN/TY08R5KyAaWc6n3tagMvjhuCzQ+jlOmylVPRZjw/an9kPdYF1lucjIPLJmERoyP9j+8GflIIXPYW4XOKVJAmWZ27Y8nAMaq5t9yB4WjeumJ4hOaIErx/W978JXH2TbY9K5ZNKMhzC5dSjyTapQ5IyFxozvLJGz9Hp/Am+Y7utH8kan+yDhYVp8lVgdDWX33f+ji3gruNpnLPTxNl/8vKtbeTAeKDj0DmhuGShT3Jxkp4guGCv5cnT0QX0hgq4dOnBSshIxxehUbZJbu4OSJEpbXJvnxf0zoP6JMP/keVGTfZYEiCDQlttmXjE1hlTnJ3A+Ketuc53RwSz86AuSXFxitYzueBWUyGWE9E1pqiozoRhXdQJMLAaEyUMMdJyGp2Ux4Lm7iG5h5rkNhweFUpLFtonZANNTmOA3WzmFiGLGY3XlSel0DpOCzRs6gWXj4fkgwnIhKZIcKDhgsTLTCICfMs683gb1tbchiNjgt0TfYFkym7JQkyl8nkoixkMesiGzT1Q19q20t+0dTvwnU/A0iQSDYeoNinBQLOTE/2BjUcvCE5rMoJ2XcSYYBr3FOPmoHHLU+APdtJMpvg7bp8XAr19mKJ1weK9u5CJxUDpMt+HxfJ2HbsoVLzwa1aT4fw8dD3/Au43lv7YYjQF19gAHXv6UYMJa7UepQZFJzMT9fp9lJidorCvgbfkSRXbBB2UDRX5MdREnZYNwZECRcxQXLUnl8s5KPw6MNFsdBFzzdaaE8xGwrUx0eXZORrw3c1NNdEk0ZwUi2OQn7fvZBz9fEZKDjNzFLqn7dYApnVtNtKvecx5oxVfHCsmSt4ts/0rrxiO5NO6jvUWyC0guZgT+SPllu4Jzjr9AT0bjqKWQ1qH0RWQfvKcwzm+s6BB01X6RC1pHIf82w02NRmnsng7WjX28iJqLu5Ec4XXSE5XYg+S91A+UlHS/H2riXfq1n3N8mM2HKOOgutsodkNqZKIMxGQokvFw40jhnFMoZZ70CzyrpLd2S0kb00Oa5KMJDC8LAHL4QEmK4HGKYaSq+/bJFTyZ/GyX+VK3pzUStA1SRJrkTNZrWHG1e8IGuMZt5eqrUH9U8iwUbVci1w1BKnW65RWSVaVnBomAKoI3E9IQEEipX3jap9Q1hxmBElBocp/AmIYcQaRQSQQ36r/E8odvepOxoa5khfRT1pf+8q0/wUYAFU/P0XyeZQPAAAAAElFTkSuQmCC'); | ||||
|     margin: 10rpx 0; | ||||
|   } | ||||
| 
 | ||||
|   .num { | ||||
|  | @ -436,7 +435,7 @@ | |||
|   .item { | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|     flex-direction: column; | ||||
|     border-bottom: 1px solid #eee; | ||||
|     height: 130rpx; | ||||
|   } | ||||
| 
 | ||||
|  | @ -447,6 +446,6 @@ | |||
|   } | ||||
| 
 | ||||
|   .on { | ||||
|     color: #f4b409; | ||||
|     background-color: #999 !important; | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,15 +1,23 @@ | |||
| <template> | ||||
|   <s-goods-item | ||||
|     :title="goodsData.spuName" | ||||
|     :img="goodsData.picUrl" | ||||
|     :price="goodsData.price" | ||||
|     :skuText="goodsData.introduction" | ||||
|     priceColor="#FF3000" | ||||
|     :titleWidth="400" | ||||
|   /> | ||||
|   <view class="goods ss-flex"> | ||||
|     <image class="image" :src="sheep.$url.cdn(goodsData.image)" mode="aspectFill"> </image> | ||||
|     <view class="ss-flex-1"> | ||||
|       <view class="title ss-line-2"> | ||||
|         {{ goodsData.title }} | ||||
|       </view> | ||||
|       <view v-if="goodsData.subtitle" class="subtitle ss-line-1"> | ||||
|         {{ goodsData.subtitle }} | ||||
|       </view> | ||||
|       <view class="price ss-m-t-8"> | ||||
|         ¥{{ isArray(goodsData.price) ? goodsData.price[0] : goodsData.price }} | ||||
|       </view> | ||||
|     </view> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isArray } from 'lodash'; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     goodsData: { | ||||
|  | @ -19,3 +27,37 @@ | |||
|   }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .goods { | ||||
|     background: #fff; | ||||
|     padding: 20rpx; | ||||
|     border-radius: 12rpx; | ||||
| 
 | ||||
|     .image { | ||||
|       width: 116rpx; | ||||
|       height: 116rpx; | ||||
|       flex-shrink: 0; | ||||
|       margin-right: 20rpx; | ||||
|     } | ||||
| 
 | ||||
|     .title { | ||||
|       height: 64rpx; | ||||
|       line-height: 32rpx; | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: #333; | ||||
|     } | ||||
| 
 | ||||
|     .subtitle { | ||||
|       font-size: 24rpx; | ||||
|       font-weight: 400; | ||||
|       color: #999; | ||||
|     } | ||||
| 
 | ||||
|     .price { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: #ff3000; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,110 +0,0 @@ | |||
| <template> | ||||
|   <view class="send-wrap ss-flex"> | ||||
|     <view class="left ss-flex ss-flex-1"> | ||||
|       <optimize-input | ||||
|         class="ss-flex-1 ss-p-l-22" | ||||
|         :inputBorder="false" | ||||
|         :clearable="false" | ||||
|         v-model="message" | ||||
|         placeholder="请输入你要咨询的问题" | ||||
|       ></optimize-input> | ||||
|     </view> | ||||
|     <text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text> | ||||
|     <text | ||||
|       v-if="!message" | ||||
|       class="sicon-edit" | ||||
|       :class="{ 'is-active': toolsMode === 'tools' }" | ||||
|       @tap.stop="onTools('tools')" | ||||
|     /> | ||||
|     <button | ||||
|       v-if="message" | ||||
|       class="ss-reset-button send-btn" | ||||
|       @tap="sendMessage" | ||||
|       :disabled="isDisabled || sending" | ||||
|       :class="{ disabled: isDisabled || sending }" | ||||
|     > | ||||
|       <text v-if="sending">发送中</text> | ||||
|       <text v-else>发送</text> | ||||
|     </button> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed, ref, onUnmounted } from 'vue'; | ||||
|   import OptimizeInput from '@/pages/chat/components/optimize-input.vue'; | ||||
|   /** | ||||
|    * 消息发送组件 | ||||
|    */ | ||||
|   const props = defineProps({ | ||||
|     // 消息 | ||||
|     modelValue: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|     // 工具模式 | ||||
|     toolsMode: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|   }); | ||||
|   const emits = defineEmits(['update:modelValue', 'onTools', 'sendMessage']); | ||||
|   const message = computed({ | ||||
|     get() { | ||||
|       return props.modelValue; | ||||
|     }, | ||||
|     set(newValue) { | ||||
|       emits(`update:modelValue`, newValue); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
| 
 | ||||
|   // 打开工具菜单 | ||||
|   function onTools(mode) { | ||||
|     emits('onTools', mode); | ||||
|   } | ||||
| 
 | ||||
|   // 发送消息 | ||||
|   function sendMessage() { | ||||
|     emits('sendMessage'); | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
|   .send-wrap { | ||||
|     padding: 18rpx 20rpx; | ||||
|     background: #fff; | ||||
| 
 | ||||
|     .left { | ||||
|       height: 64rpx; | ||||
|       border-radius: 32rpx; | ||||
|       background: var(--ui-BG-1); | ||||
|     } | ||||
| 
 | ||||
|     .bq { | ||||
|       font-size: 50rpx; | ||||
|       margin-left: 10rpx; | ||||
|     } | ||||
| 
 | ||||
|     .sicon-edit { | ||||
|       font-size: 50rpx; | ||||
|       margin-left: 10rpx; | ||||
|       transform: rotate(0deg); | ||||
|       transition: all linear 0.2s; | ||||
| 
 | ||||
|       &.is-active { | ||||
|         transform: rotate(45deg); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .send-btn { | ||||
|       width: 100rpx; | ||||
|       height: 60rpx; | ||||
|       line-height: 60rpx; | ||||
|       border-radius: 30rpx; | ||||
|       background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|       font-size: 26rpx; | ||||
|       color: #fff; | ||||
|       margin-left: 11rpx; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  | @ -1,293 +0,0 @@ | |||
| <template> | ||||
|   <!--  聊天列表使用scroll-view原生组件,整体倒置  --> | ||||
|   <scroll-view | ||||
|     :scroll-top="scroll.top" | ||||
|     class="chat-scroll-view" | ||||
|     scroll-y | ||||
|     :refresher-enabled="false" | ||||
|     @scroll="onScroll" | ||||
|     @scrolltolower="loadMoreHistory" | ||||
|     style="transform: scaleY(-1)" | ||||
|   > | ||||
|     <!-- 消息列表容器 --> | ||||
|     <view class="message-container"> | ||||
|       <!-- 加载更多提示 --> | ||||
|       <view v-if="isLoading" class="loading-more" style="transform: scaleY(-1)"> | ||||
|         <text>加载中...</text> | ||||
|       </view> | ||||
|       <!-- 消息列表 --> | ||||
|       <view class="message-list"> | ||||
|         <view | ||||
|           v-for="(item, index) in messageList" | ||||
|           :key="item.id" | ||||
|           class="message-item" | ||||
|           style="transform: scaleY(-1)" | ||||
|         > | ||||
|           <!--  消息渲染  --> | ||||
|           <MessageListItem | ||||
|             :message="item" | ||||
|             :message-index="index" | ||||
|             :message-list="messageList" | ||||
|           ></MessageListItem> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|   </scroll-view> | ||||
| 
 | ||||
|   <!-- 底部聊天输入框 --> | ||||
|   <su-fixed bottom> | ||||
|     <view v-if="showTip" class="back-top ss-flex ss-row-center ss-m-b-10" @tap="scrollToTop"> | ||||
|       <text class="back-top-item ss-flex ss-row-center"> | ||||
|         {{ showNewMessageTip ? '有新消息' : '回到底部' }} | ||||
|       </text> | ||||
|     </view> | ||||
|     <slot name="bottom"></slot> | ||||
|   </su-fixed> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import MessageListItem from '@/pages/chat/components/messageListItem.vue'; | ||||
|   import { onMounted, reactive, ref, computed } from 'vue'; | ||||
|   import KeFuApi from '@/sheep/api/promotion/kefu'; | ||||
|   import { isEmpty } from '@/sheep/helper/utils'; | ||||
|   import { formatDate } from '@/sheep/helper/utils'; | ||||
|   import sheep from '@/sheep'; | ||||
| 
 | ||||
|   const { safeAreaInsets } = sheep.$platform.device; | ||||
|   const safeAreaInsetsBottom = safeAreaInsets.bottom + 'px'; // 底部安全区域 | ||||
|   const messageList = ref([]); // 消息列表 | ||||
|   const showTip = ref(false); // 显示提示 | ||||
|   const showNewMessageTip = ref(false); // 显示有新消息提示 | ||||
|   const refreshMessage = ref(false); // 更新消息列表 | ||||
|   const isLoading = ref(false); // 是否正在加载更多 | ||||
|   const hasMore = ref(true); // 是否还有更多数据 | ||||
|   const keyboardHeight = ref(0); // 键盘高度 | ||||
|   const scroll = ref({ | ||||
|     top: 0, | ||||
|     oldTop: 0, | ||||
|   }); // 滚动位置记录 | ||||
|   const queryParams = reactive({ | ||||
|     no: 1, | ||||
|     limit: 20, | ||||
|     createTime: undefined, | ||||
|   }); // 查询参数 | ||||
| 
 | ||||
|   // 计算聊天窗口高度 | ||||
|   const chatScrollHeight = computed(() => { | ||||
|     const baseHeight = 'calc(100vh - 150px - ' + safeAreaInsetsBottom + ')'; | ||||
|     if (keyboardHeight.value > 0) { | ||||
|       // 键盘弹起状态,减去键盘高度 | ||||
|       return `calc(${baseHeight} - ${keyboardHeight.value}px)`; | ||||
|     } | ||||
|     return baseHeight; | ||||
|   }); | ||||
| 
 | ||||
|   // 获得消息分页列表 | ||||
|   const getMessageList = async () => { | ||||
|     isLoading.value = true; | ||||
|     try { | ||||
|       const { data } = await KeFuApi.getKefuMessageList(queryParams); | ||||
|       if (isEmpty(data)) { | ||||
|         hasMore.value = false; | ||||
|         return; | ||||
|       } | ||||
|       if (queryParams.no > 1 && refreshMessage.value) { | ||||
|         const newMessageList = []; | ||||
|         for (const message of data) { | ||||
|           if (messageList.value.some((val) => val.id === message.id)) { | ||||
|             continue; | ||||
|           } | ||||
|           newMessageList.push(message); | ||||
|         } | ||||
|         // 新消息追加到开头 | ||||
|         messageList.value = [...newMessageList, ...messageList.value]; | ||||
|         refreshMessage.value = false; // 更新好后重置状态 | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if (queryParams.no > 1) { | ||||
|         // 加载更多历史消息,追加到现有列表末尾(因为是倒置的,所以旧消息在底部/列表末尾) | ||||
|         if (data.length < queryParams.limit) { | ||||
|           hasMore.value = false; // 如果返回的数据少于请求的数量,说明没有更多数据了 | ||||
|         } | ||||
| 
 | ||||
|         // 过滤掉已存在的消息 | ||||
|         const historyMessages = data.filter( | ||||
|           (msg) => !messageList.value.some((existing) => existing.id === msg.id), | ||||
|         ); | ||||
| 
 | ||||
|         if (historyMessages.length > 0) { | ||||
|           messageList.value = [...messageList.value, ...historyMessages]; | ||||
|         } | ||||
|       } else { | ||||
|         // 首次加载 | ||||
|         messageList.value = data; | ||||
| 
 | ||||
|         if (data.length < queryParams.limit) { | ||||
|           hasMore.value = false; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (data.slice(-1).length > 0) { | ||||
|         // 设置最后一次历史查询的最后一条消息的 createTime | ||||
|         queryParams.createTime = formatDate(data.slice(-1)[0].createTime); | ||||
|       } | ||||
|     } finally { | ||||
|       isLoading.value = false; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** 加载更多历史数据 */ | ||||
|   const loadMoreHistory = async () => { | ||||
|     if (isLoading.value || !hasMore.value) return; | ||||
| 
 | ||||
|     // 增加页码 | ||||
|     queryParams.no += 1; | ||||
|     await getMessageList(); | ||||
|   }; | ||||
| 
 | ||||
|   /** 刷新消息列表 */ | ||||
|   const refreshMessageList = async (message = undefined) => { | ||||
|     if (typeof message !== 'undefined') { | ||||
|       // 追加数据到列表开头(因为是倒置的,所以新消息在顶部/列表开头) | ||||
|       messageList.value.unshift(message); | ||||
|       showNewMessageTip.value = true; | ||||
|     } else { | ||||
|       queryParams.createTime = undefined; | ||||
|       refreshMessage.value = true; | ||||
|       await getMessageList(); | ||||
|     } | ||||
| 
 | ||||
|     // 若已是第一页则不做处理 | ||||
|     if (queryParams.no > 1) { | ||||
|       showTip.value = true; | ||||
|     } else { | ||||
|       scrollToTop(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** 滚动到顶部(倒置后相当于滚动到最新消息) */ | ||||
|   const scrollToTop = () => { | ||||
|     scroll.value.top = scroll.value.oldTop; | ||||
|     setTimeout(() => { | ||||
|       scroll.value.top = 0; | ||||
|     }, 200); // 等待 view 层同步 | ||||
|     showTip.value = false; | ||||
|   }; | ||||
| 
 | ||||
|   /** 设置键盘高度 */ | ||||
|   const setKeyboardHeight = (height) => { | ||||
|     keyboardHeight.value = height; | ||||
|     // 键盘弹起时,滚动到最新消息 | ||||
|     if (height > 0) { | ||||
|       scrollToTop(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   defineExpose({ getMessageList, refreshMessageList }); | ||||
| 
 | ||||
|   /** 监听消息列表滚动 */ | ||||
|   const onScroll = (e) => { | ||||
|     const { scrollTop } = e.detail; | ||||
|     scroll.value.oldTop = scrollTop; | ||||
|     // 当滚动位置超过一定值时,显示"新消息"提示 | ||||
|     if (scrollTop > 100) { | ||||
|       showTip.value = true; | ||||
|     } else { | ||||
|       showTip.value = false; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // 监听键盘弹起和收起事件 | ||||
|   const setupKeyboardListeners = () => { | ||||
|     // #ifdef H5 | ||||
|     // H5环境 | ||||
|     window.addEventListener('resize', () => { | ||||
|       // 窗口大小变化可能是由键盘引起的 | ||||
|       if ( | ||||
|         document.activeElement && | ||||
|         (document.activeElement.tagName === 'INPUT' || | ||||
|           document.activeElement.tagName === 'TEXTAREA') | ||||
|       ) { | ||||
|         // 估算键盘高度,实际上是窗口高度变化 | ||||
|         const currentHeight = window.innerHeight; | ||||
|         const viewportHeight = window.visualViewport | ||||
|           ? window.visualViewport.height | ||||
|           : window.innerHeight; | ||||
|         const keyboardHeight = currentHeight - viewportHeight; | ||||
|         setKeyboardHeight(keyboardHeight > 0 ? keyboardHeight : 0); | ||||
|       } else { | ||||
|         setKeyboardHeight(0); | ||||
|       } | ||||
|     }); | ||||
|     // #endif | ||||
| 
 | ||||
|     // #ifdef MP-WEIXIN | ||||
|     // TODO puhui999: 小程序键盘弹起还有点问题,看看怎么适配 | ||||
|     // 微信小程序环境 | ||||
|     uni.onKeyboardHeightChange((res) => { | ||||
|       setKeyboardHeight(res.height); | ||||
|     }); | ||||
|     // #endif | ||||
|   }; | ||||
| 
 | ||||
|   onMounted(() => { | ||||
|     queryParams.no = 1; // 确保首次加载是第一页 | ||||
|     scroll.value = { | ||||
|       top: 0, | ||||
|       oldTop: 0, | ||||
|     }; | ||||
|     getMessageList(); | ||||
|     setupKeyboardListeners(); | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .chat-scroll-view { | ||||
|     height: v-bind(chatScrollHeight); | ||||
|     width: 100%; | ||||
|     position: relative; | ||||
|     background-color: #f8f8f8; | ||||
|     z-index: 1; | ||||
|   } | ||||
| 
 | ||||
|   .message-container { | ||||
|     width: 100%; | ||||
|     /* 确保容器至少有一屏高度 */ | ||||
|     min-height: 100vh; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: flex-end; | ||||
|   } | ||||
| 
 | ||||
|   .message-list { | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     padding-bottom: 20px; | ||||
|   } | ||||
| 
 | ||||
|   .message-item { | ||||
|     margin-bottom: 10px; | ||||
|   } | ||||
| 
 | ||||
|   .loading-more { | ||||
|     width: 100%; | ||||
|     height: 40px; | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|     color: #999; | ||||
|     font-size: 14px; | ||||
|   } | ||||
| 
 | ||||
|   .back-top { | ||||
|     .back-top-item { | ||||
|       height: 30px; | ||||
|       width: 100px; | ||||
|       background-color: #fff; | ||||
|       border-radius: 30px; | ||||
|       box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  | @ -1,297 +0,0 @@ | |||
| <template> | ||||
|   <view class="chat-box"> | ||||
|     <!--  消息渲染  --> | ||||
|     <view class="message-item ss-flex-col scroll-item"> | ||||
|       <view class="ss-flex ss-row-center ss-col-center"> | ||||
|         <!-- 系统消息 --> | ||||
|         <view | ||||
|           v-if="message.contentType === KeFuMessageContentTypeEnum.SYSTEM" | ||||
|           class="system-message" | ||||
|         > | ||||
|           {{ message.content }} | ||||
|         </view> | ||||
|         <!-- 日期 - 移到消息内容上方显示 --> | ||||
|         <view | ||||
|           v-if=" | ||||
|             message.contentType !== KeFuMessageContentTypeEnum.SYSTEM && | ||||
|             showTime(message, messageIndex) | ||||
|           " | ||||
|           class="date-message" | ||||
|         > | ||||
|           {{ formatDate(message.createTime) }} | ||||
|         </view> | ||||
|       </view> | ||||
| 
 | ||||
|       <!-- 消息体渲染管理员消息和用户消息并左右展示  --> | ||||
|       <view | ||||
|         v-if="message.contentType !== KeFuMessageContentTypeEnum.SYSTEM" | ||||
|         class="ss-flex ss-col-top" | ||||
|         :class="[ | ||||
|           message.senderType === UserTypeEnum.ADMIN | ||||
|             ? `ss-row-left` | ||||
|             : message.senderType === UserTypeEnum.MEMBER | ||||
|             ? `ss-row-right` | ||||
|             : '', | ||||
|         ]" | ||||
|       > | ||||
|         <!-- 客服头像 --> | ||||
|         <image | ||||
|           v-show="message.senderType === UserTypeEnum.ADMIN" | ||||
|           class="chat-avatar ss-m-r-24" | ||||
|           :src="sheep.$url.cdn(message.senderAvatar) || sheep.$url.static('')" | ||||
|           mode="aspectFill" | ||||
|           lazy-load | ||||
|         /> | ||||
|         <!-- 内容 --> | ||||
|         <template v-if="message.contentType === KeFuMessageContentTypeEnum.TEXT"> | ||||
|           <view class="message-box" :class="{ admin: message.senderType === UserTypeEnum.ADMIN }"> | ||||
|             <mp-html :content="processedContent" :domain="sheep.$url.cdn('')" lazy-load /> | ||||
|           </view> | ||||
|         </template> | ||||
|         <template v-if="message.contentType === KeFuMessageContentTypeEnum.IMAGE"> | ||||
|           <view | ||||
|             class="message-box" | ||||
|             :class="{ admin: message.senderType === UserTypeEnum.ADMIN }" | ||||
|             :style="{ width: '200rpx' }" | ||||
|           > | ||||
|             <su-image | ||||
|               class="message-img" | ||||
|               isPreview | ||||
|               :previewList="[sheep.$url.cdn(getMessageContent(message).picUrl || message.content)]" | ||||
|               :current="0" | ||||
|               :src="sheep.$url.cdn(getMessageContent(message).picUrl || message.content)" | ||||
|               :height="200" | ||||
|               :width="200" | ||||
|               mode="aspectFill" | ||||
|             /> | ||||
|           </view> | ||||
|         </template> | ||||
|         <template v-if="message.contentType === KeFuMessageContentTypeEnum.PRODUCT"> | ||||
|           <div class="ss-m-b-10"> | ||||
|             <GoodsItem | ||||
|               :goodsData="getMessageContent(message)" | ||||
|               @tap=" | ||||
|                 sheep.$router.go('/pages/goods/index', { id: getMessageContent(message).spuId }) | ||||
|               " | ||||
|             /> | ||||
|           </div> | ||||
|         </template> | ||||
|         <template v-if="message.contentType === KeFuMessageContentTypeEnum.ORDER"> | ||||
|           <OrderItem | ||||
|             :orderData="getMessageContent(message)" | ||||
|             @tap="sheep.$router.go('/pages/order/detail', { id: getMessageContent(message).id })" | ||||
|           /> | ||||
|         </template> | ||||
|         <!-- user头像 --> | ||||
|         <image | ||||
|           v-if="message.senderType === UserTypeEnum.MEMBER" | ||||
|           class="chat-avatar ss-m-l-24" | ||||
|           :src=" | ||||
|             sheep.$url.cdn(userInfo.avatar) || | ||||
|             sheep.$url.static('/static/img/shop/chat/default.png') | ||||
|           " | ||||
|           mode="aspectFill" | ||||
|         > | ||||
|         </image> | ||||
|       </view> | ||||
|     </view> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed, unref } from 'vue'; | ||||
|   import dayjs from 'dayjs'; | ||||
|   import { KeFuMessageContentTypeEnum, UserTypeEnum } from '@/pages/chat/util/constants'; | ||||
|   import { emojiList } from '@/pages/chat/util/emoji'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { formatDate, jsonParse } from '@/sheep/helper/utils'; | ||||
|   import GoodsItem from '@/pages/chat/components/goods.vue'; | ||||
|   import OrderItem from '@/pages/chat/components/order.vue'; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     // 消息 | ||||
|     message: { | ||||
|       type: Object, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
|     // 消息索引 | ||||
|     messageIndex: { | ||||
|       type: Number, | ||||
|       default: 0, | ||||
|     }, | ||||
|     // 消息列表 | ||||
|     messageList: { | ||||
|       type: Array, | ||||
|       default: () => [], | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const getMessageContent = computed(() => (item) => jsonParse(item.content)); // 解析消息内容 | ||||
|   const userInfo = computed(() => sheep.$store('user').userInfo); | ||||
| 
 | ||||
|   //======================= 工具 ======================= | ||||
| 
 | ||||
|   const showTime = computed(() => (item, index) => { | ||||
|     if (unref(props.messageList)[index + 1]) { | ||||
|       let dateString = dayjs(unref(props.messageList)[index + 1].createTime).fromNow(); | ||||
|       return dateString !== dayjs(unref(item).createTime).fromNow(); | ||||
|     } | ||||
|     return false; | ||||
|   }); | ||||
| 
 | ||||
|   // 缓存表情映射 | ||||
|   const emojiMap = computed(() => { | ||||
|     const map = new Map(); | ||||
|     emojiList.forEach((emoji) => { | ||||
|       map.set(emoji.name, emoji.file); | ||||
|     }); | ||||
|     return map; | ||||
|   }); | ||||
| 
 | ||||
|   // 处理表情 - 进行缓存优化 | ||||
|   function replaceEmoji(data) { | ||||
|     let newData = data; | ||||
|     if (typeof newData !== 'object') { | ||||
|       let reg = /\[(.+?)]/g; // [] 中括号 | ||||
|       let zhEmojiName = newData.match(reg); | ||||
|       if (zhEmojiName) { | ||||
|         zhEmojiName.forEach((item) => { | ||||
|           const emojiFile = emojiMap.value.get(item) || ''; | ||||
|           if (emojiFile) { | ||||
|             newData = newData.replace( | ||||
|               item, | ||||
|               `<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;vertical-align: middle;" src="${sheep.$url.cdn( | ||||
|                 '/static/img/chat/emoji/' + emojiFile, | ||||
|               )}"/>`, | ||||
|             ); | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|     return newData; | ||||
|   } | ||||
| 
 | ||||
|   // 预处理内容,避免重复计算 | ||||
|   const processedContent = computed(() => { | ||||
|     if (props.message.contentType === KeFuMessageContentTypeEnum.TEXT) { | ||||
|       return replaceEmoji(getMessageContent.value(props.message).text || props.message.content); | ||||
|     } | ||||
|     return props.message.content; | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
|   .message-item { | ||||
|     margin-bottom: 10rpx; | ||||
|   } | ||||
| 
 | ||||
|   .date-message, | ||||
|   .system-message { | ||||
|     width: fit-content; | ||||
|     border-radius: 12rpx; | ||||
|     padding: 8rpx 16rpx; | ||||
|     margin-bottom: 16rpx; | ||||
|     background-color: var(--ui-BG-3); | ||||
|     color: #999; | ||||
|     font-size: 24rpx; | ||||
|   } | ||||
| 
 | ||||
|   .chat-avatar { | ||||
|     width: 70rpx; | ||||
|     height: 70rpx; | ||||
|     border-radius: 50%; | ||||
|   } | ||||
| 
 | ||||
|   .send-status { | ||||
|     color: #333; | ||||
|     height: 80rpx; | ||||
|     margin-right: 8rpx; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
| 
 | ||||
|     .loading { | ||||
|       width: 32rpx; | ||||
|       height: 32rpx; | ||||
|       -webkit-animation: rotating 2s linear infinite; | ||||
|       animation: rotating 2s linear infinite; | ||||
| 
 | ||||
|       @-webkit-keyframes rotating { | ||||
|         0% { | ||||
|           transform: rotateZ(0); | ||||
|         } | ||||
| 
 | ||||
|         100% { | ||||
|           transform: rotateZ(360deg); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       @keyframes rotating { | ||||
|         0% { | ||||
|           transform: rotateZ(0); | ||||
|         } | ||||
| 
 | ||||
|         100% { | ||||
|           transform: rotateZ(360deg); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .warning { | ||||
|       width: 32rpx; | ||||
|       height: 32rpx; | ||||
|       color: #ff3000; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .message-box { | ||||
|     max-width: 50%; | ||||
|     font-size: 16px; | ||||
|     white-space: normal; | ||||
|     word-break: break-all; | ||||
|     word-wrap: break-word; | ||||
|     padding: 20rpx; | ||||
|     color: #fff; | ||||
|     background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|     margin-top: 3px; | ||||
|     margin-bottom: 9px; | ||||
|     border-top-left-radius: 10px; | ||||
|     border-bottom-right-radius: 10px; | ||||
|     border-bottom-left-radius: 10px; | ||||
|     &.admin { | ||||
|       background: #fff; | ||||
|       color: #333; | ||||
|       margin-top: 3px; | ||||
|       margin-bottom: 9px; | ||||
|       border-radius: 0 10px 10px 10px; | ||||
|     } | ||||
| 
 | ||||
|     :deep() { | ||||
|       .imgred { | ||||
|         width: 100%; | ||||
|       } | ||||
| 
 | ||||
|       .imgred, | ||||
|       img { | ||||
|         width: 100%; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   :deep() { | ||||
|     .goods, | ||||
|     .order { | ||||
|       max-width: 500rpx; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .message-img { | ||||
|     width: 100px; | ||||
|     height: 100px; | ||||
|     border-radius: 6rpx; | ||||
|   } | ||||
| 
 | ||||
|   .error-img { | ||||
|     width: 400rpx; | ||||
|     height: 400rpx; | ||||
|   } | ||||
| </style> | ||||
|  | @ -1,540 +0,0 @@ | |||
| <template> | ||||
|   <view | ||||
|     class="uni-easyinput" | ||||
|     :class="{ 'uni-easyinput-error': msg }" | ||||
|     :style="{ color: inputBorder && msg ? '#e43d33' : styles.color }" | ||||
|   > | ||||
|     <view | ||||
|       class="uni-easyinput__content" | ||||
|       :class="{ | ||||
| 				'is-input-border': inputBorder, | ||||
| 				'is-input-error-border': inputBorder && msg, | ||||
| 				'is-textarea': type === 'textarea', | ||||
| 				'is-disabled': disabled | ||||
| 			}" | ||||
|       :style="{ | ||||
| 				'border-color': inputBorder && msg ? '#dd524d' : styles.borderColor, | ||||
| 				'background-color': disabled ? styles.disableColor : '' | ||||
| 			}" | ||||
|     > | ||||
|       <uni-icons | ||||
|         v-if="prefixIcon" | ||||
|         class="content-clear-icon" | ||||
|         :type="prefixIcon" | ||||
|         color="#c0c4cc" | ||||
|         @click="onClickIcon('prefix')" | ||||
|       ></uni-icons> | ||||
|       <textarea | ||||
|         v-if="type === 'textarea'" | ||||
|         class="uni-easyinput__content-textarea" | ||||
|         :class="{ 'input-padding': inputBorder }" | ||||
|         :name="name" | ||||
|         :value="val" | ||||
|         :placeholder="placeholder" | ||||
|         :placeholderStyle="placeholderStyle" | ||||
|         :disabled="disabled" | ||||
|         placeholder-class="uni-easyinput__placeholder-class" | ||||
|         :maxlength="inputMaxlength" | ||||
|         :focus="focused" | ||||
|         :autoHeight="autoHeight" | ||||
|         :adjust-position="false" | ||||
|         @input="onInput" | ||||
|         @blur="onBlur" | ||||
|         @focus="onFocus" | ||||
|         @confirm="onConfirm" | ||||
|       ></textarea> | ||||
|       <input | ||||
|         v-else | ||||
|         :type="type === 'password' ? 'text' : type" | ||||
|         class="uni-easyinput__content-input" | ||||
|         :style="{ | ||||
| 					'padding-right': type === 'password' || clearable || prefixIcon ? '' : '10px', | ||||
| 					'padding-left': paddingLeft + 'px' | ||||
| 
 | ||||
| 				}" | ||||
|         :name="name" | ||||
|         :value="val" | ||||
|         :password="!showPassword && type === 'password'" | ||||
|         :placeholder="placeholder" | ||||
|         :placeholderStyle="placeholderStyle" | ||||
|         placeholder-class="uni-easyinput__placeholder-class" | ||||
|         :disabled="disabled" | ||||
|         :maxlength="inputMaxlength" | ||||
|         :focus="focused" | ||||
|         :confirmType="confirmType" | ||||
|         :adjust-position="false" | ||||
|         @focus="onFocus" | ||||
|         @blur="onBlur" | ||||
|         @input="onInput" | ||||
|         @change="onInput" | ||||
|         @confirm="onConfirm" | ||||
|         :cursor-spacing="30" | ||||
|         always-embed | ||||
|       /> | ||||
|       <template v-if="type === 'password' && passwordIcon"> | ||||
|         <uni-icons | ||||
|           v-if="val" | ||||
|           class="content-clear-icon" | ||||
|           :class="{ 'is-textarea-icon': type === 'textarea' }" | ||||
|           :type="showPassword ? 'eye-slash-filled' : 'eye-filled'" | ||||
|           :size="18" | ||||
|           color="#c0c4cc" | ||||
|           @click="onEyes" | ||||
|         ></uni-icons> | ||||
|       </template> | ||||
|       <template v-else-if="suffixIcon"> | ||||
|         <uni-icons | ||||
|           v-if="suffixIcon" | ||||
|           class="content-clear-icon" | ||||
|           :type="suffixIcon" | ||||
|           color="#c0c4cc" | ||||
|           @click="onClickIcon('suffix')" | ||||
|         ></uni-icons> | ||||
|       </template> | ||||
|       <template v-else> | ||||
|         <uni-icons | ||||
|           class="content-clear-icon" | ||||
|           :class="{ 'is-textarea-icon': type === 'textarea' }" | ||||
|           type="clear" | ||||
|           :size="clearSize" | ||||
|           v-if="clearable && val && !disabled" | ||||
|           color="#c0c4cc" | ||||
|           @click="onClear" | ||||
|         ></uni-icons> | ||||
|       </template> | ||||
|       <slot name="right"></slot> | ||||
|     </view> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
|   // import { | ||||
|   // 	debounce, | ||||
|   // 	throttle | ||||
|   // } from './common.js' | ||||
|   /** | ||||
|    * Easyinput 输入框 | ||||
|    * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。 | ||||
|    * @tutorial https://ext.dcloud.net.cn/plugin?id=3455 | ||||
|    * @property {String}	value	输入内容 | ||||
|    * @property {String }	type	输入框的类型(默认text) password/text/textarea/.. | ||||
|    * 	@value text			文本输入键盘 | ||||
|    * 	@value textarea	多行文本输入键盘 | ||||
|    * 	@value password	密码输入键盘 | ||||
|    * 	@value number		数字输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式 | ||||
|    * 	@value idcard		身份证输入键盘,信、支付宝、百度、QQ小程序 | ||||
|    * 	@value digit		带小数点的数字键盘	,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持 | ||||
|    * @property {Boolean}	clearable	是否显示右侧清空内容的图标控件,点击可清空输入框内容(默认true) | ||||
|    * @property {Boolean}	autoHeight	是否自动增高输入区域,type为textarea时有效(默认false) | ||||
|    * @property {String }	placeholder	输入框的提示文字 | ||||
|    * @property {String }	placeholderStyle	placeholder的样式(内联样式,字符串),如"color: #ddd" | ||||
|    * @property {Boolean}	focus	是否自动获得焦点(默认false) | ||||
|    * @property {Boolean}	disabled	是否禁用(默认false) | ||||
|    * @property {Number }	maxlength	最大输入长度,设置为 -1 的时候不限制最大长度(默认140) | ||||
|    * @property {String }	confirmType	设置键盘右下角按钮的文字,仅在type="text"时生效(默认done) | ||||
|    * @property {Number }	clearSize	清除图标的大小,单位px(默认15) | ||||
|    * @property {String}	prefixIcon	输入框头部图标 | ||||
|    * @property {String}	suffixIcon	输入框尾部图标 | ||||
|    * @property {Boolean}	trim	是否自动去除两端的空格 | ||||
|    * @value both	去除两端空格 | ||||
|    * @value left	去除左侧空格 | ||||
|    * @value right	去除右侧空格 | ||||
|    * @value start	去除左侧空格 | ||||
|    * @value end		去除右侧空格 | ||||
|    * @value all		去除全部空格 | ||||
|    * @value none	不去除空格 | ||||
|    * @property {Boolean}	inputBorder	是否显示input输入框的边框(默认true) | ||||
|    * @property {Boolean}	passwordIcon	type=password时是否显示小眼睛图标 | ||||
|    * @property {Object}	styles	自定义颜色 | ||||
|    * @event {Function}	input	输入框内容发生变化时触发 | ||||
|    * @event {Function}	focus	输入框获得焦点时触发 | ||||
|    * @event {Function}	blur	输入框失去焦点时触发 | ||||
|    * @event {Function}	confirm	点击完成按钮时触发 | ||||
|    * @event {Function}	iconClick	点击图标时触发 | ||||
|    * @example <uni-easyinput v-model="mobile"></uni-easyinput> | ||||
|    */ | ||||
| 
 | ||||
|   export default { | ||||
|     name: 'optimize-input', | ||||
|     emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm'], | ||||
|     model: { | ||||
|       prop: 'modelValue', | ||||
|       event: 'update:modelValue' | ||||
|     }, | ||||
|     props: { | ||||
|       name: String, | ||||
|       value: [Number, String], | ||||
|       modelValue: [Number, String], | ||||
|       type: { | ||||
|         type: String, | ||||
|         default: 'text' | ||||
|       }, | ||||
|       clearable: { | ||||
|         type: Boolean, | ||||
|         default: true | ||||
|       }, | ||||
|       autoHeight: { | ||||
|         type: Boolean, | ||||
|         default: false | ||||
|       }, | ||||
|       placeholder: String, | ||||
|       placeholderStyle: String, | ||||
|       focus: { | ||||
|         type: Boolean, | ||||
|         default: false | ||||
|       }, | ||||
|       disabled: { | ||||
|         type: Boolean, | ||||
|         default: false | ||||
|       }, | ||||
|       maxlength: { | ||||
|         type: [Number, String], | ||||
|         default: 140 | ||||
|       }, | ||||
|       confirmType: { | ||||
|         type: String, | ||||
|         default: 'done' | ||||
|       }, | ||||
|       clearSize: { | ||||
|         type: [Number, String], | ||||
|         default: 15 | ||||
|       }, | ||||
|       inputBorder: { | ||||
|         type: Boolean, | ||||
|         default: true | ||||
|       }, | ||||
|       prefixIcon: { | ||||
|         type: String, | ||||
|         default: '' | ||||
|       }, | ||||
|       suffixIcon: { | ||||
|         type: String, | ||||
|         default: '' | ||||
|       }, | ||||
|       trim: { | ||||
|         type: [Boolean, String], | ||||
|         default: true | ||||
|       }, | ||||
|       passwordIcon: { | ||||
|         type: Boolean, | ||||
|         default: true | ||||
|       }, | ||||
|       styles: { | ||||
|         type: Object, | ||||
|         default() { | ||||
|           return { | ||||
|             color: '#333', | ||||
|             disableColor: '#F7F6F6', | ||||
|             borderColor: '#e5e5e5' | ||||
|           }; | ||||
|         } | ||||
|       }, | ||||
|       errorMessage: { | ||||
|         type: [String, Boolean], | ||||
|         default: '' | ||||
|       }, | ||||
|       paddingLeft:{ | ||||
|         type: [Number, String], | ||||
|         default: 0 | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         focused: false, | ||||
|         errMsg: '', | ||||
|         val: '', | ||||
|         showMsg: '', | ||||
|         border: false, | ||||
|         isFirstBorder: false, | ||||
|         showClearIcon: false, | ||||
|         showPassword: false | ||||
|       }; | ||||
|     }, | ||||
|     computed: { | ||||
|       msg() { | ||||
|         return this.errorMessage || this.errMsg; | ||||
|       }, | ||||
|       // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值 | ||||
|       inputMaxlength() { | ||||
|         return Number(this.maxlength); | ||||
|       } | ||||
|     }, | ||||
|     watch: { | ||||
|       value(newVal) { | ||||
|         if (this.errMsg) this.errMsg = ''; | ||||
|         this.val = newVal; | ||||
|         // fix by mehaotian is_reset 在 uni-forms 中定义 | ||||
|         if (this.form && this.formItem && !this.is_reset) { | ||||
|           this.is_reset = false; | ||||
|           this.formItem.setValue(newVal); | ||||
|         } | ||||
|       }, | ||||
|       modelValue(newVal) { | ||||
|         if (this.errMsg) this.errMsg = ''; | ||||
|         this.val = newVal; | ||||
|         if (this.form && this.formItem && !this.is_reset) { | ||||
|           this.is_reset = false; | ||||
|           this.formItem.setValue(newVal); | ||||
|         } | ||||
|       }, | ||||
|       focus(newVal) { | ||||
|         this.$nextTick(() => { | ||||
|           this.focused = this.focus; | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|     created() { | ||||
|       if (!this.value && this.value !== 0) { | ||||
|         this.val = this.modelValue; | ||||
|       } | ||||
|       if (!this.modelValue && this.modelValue !== 0) { | ||||
|         this.val = this.value; | ||||
|       } | ||||
|       this.form = this.getForm('uniForms'); | ||||
|       this.formItem = this.getForm('uniFormsItem'); | ||||
|       if (this.form && this.formItem) { | ||||
|         if (this.formItem.name) { | ||||
|           if (!this.is_reset) { | ||||
|             this.is_reset = false; | ||||
|             this.formItem.setValue(this.val); | ||||
|           } | ||||
|           this.rename = this.formItem.name; | ||||
|           this.form.inputChildrens.push(this); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     mounted() { | ||||
|       this.$nextTick(() => { | ||||
|         this.focused = this.focus; | ||||
|       }); | ||||
|     }, | ||||
|     methods: { | ||||
|       /** | ||||
|        * 初始化变量值 | ||||
|        */ | ||||
|       init() {}, | ||||
|       onClickIcon(type) { | ||||
|         this.$emit('iconClick', type); | ||||
|       }, | ||||
|       /** | ||||
|        * 获取父元素实例 | ||||
|        */ | ||||
|       getForm(name = 'uniForms') { | ||||
|         let parent = this.$parent; | ||||
|         let parentName = parent.$options.name; | ||||
|         while (parentName !== name) { | ||||
|           parent = parent.$parent; | ||||
|           if (!parent) return false; | ||||
|           parentName = parent.$options.name; | ||||
|         } | ||||
|         return parent; | ||||
|       }, | ||||
| 
 | ||||
|       onEyes() { | ||||
|         this.showPassword = !this.showPassword; | ||||
|       }, | ||||
|       onInput(event) { | ||||
|         let value = event.detail.value; | ||||
|         // 判断是否去除空格 | ||||
|         if (this.trim) { | ||||
|           if (typeof this.trim === 'boolean' && this.trim) { | ||||
|             value = this.trimStr(value); | ||||
|           } | ||||
|           if (typeof this.trim === 'string') { | ||||
|             value = this.trimStr(value, this.trim); | ||||
|           } | ||||
|         } | ||||
|         if (this.errMsg) this.errMsg = ''; | ||||
|         this.val = value; | ||||
|         // TODO 兼容 vue2 | ||||
|         this.$emit('input', value); | ||||
|         // TODO 兼容 vue3 | ||||
|         this.$emit('update:modelValue', value); | ||||
|       }, | ||||
|       onFocus(event) { | ||||
|         this.$emit('focus', event); | ||||
|       }, | ||||
|       onBlur(event) { | ||||
|         let value = event.detail.value; | ||||
|         this.$emit('blur', event); | ||||
|       }, | ||||
|       onConfirm(e) { | ||||
|         this.$emit('confirm', e.detail.value); | ||||
|       }, | ||||
|       onClear(event) { | ||||
|         this.val = ''; | ||||
|         // TODO 兼容 vue2 | ||||
|         this.$emit('input', ''); | ||||
|         // TODO 兼容 vue2 | ||||
|         // TODO 兼容 vue3 | ||||
|         this.$emit('update:modelValue', ''); | ||||
|       }, | ||||
|       fieldClick() { | ||||
|         this.$emit('click'); | ||||
|       }, | ||||
|       trimStr(str, pos = 'both') { | ||||
|         if (pos === 'both') { | ||||
|           return str.trim(); | ||||
|         } else if (pos === 'left') { | ||||
|           return str.trimLeft(); | ||||
|         } else if (pos === 'right') { | ||||
|           return str.trimRight(); | ||||
|         } else if (pos === 'start') { | ||||
|           return str.trimStart(); | ||||
|         } else if (pos === 'end') { | ||||
|           return str.trimEnd(); | ||||
|         } else if (pos === 'all') { | ||||
|           return str.replace(/\s+/g, ''); | ||||
|         } else if (pos === 'none') { | ||||
|           return str; | ||||
|         } | ||||
|         return str; | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
|   $uni-error: #e43d33; | ||||
|   $uni-border-1: #dcdfe6 !default; | ||||
|   .uni-easyinput { | ||||
|     /* #ifndef APP-NVUE */ | ||||
|     width: 100%; | ||||
|     /* #endif */ | ||||
|     flex: 1; | ||||
|     position: relative; | ||||
|     text-align: left; | ||||
|     color: #333; | ||||
|     font-size: 14px; | ||||
|   } | ||||
| 
 | ||||
|   .uni-easyinput__content { | ||||
|     flex: 1; | ||||
|     /* #ifndef APP-NVUE */ | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     box-sizing: border-box; | ||||
|     min-height: 72rpx; | ||||
|     /* #endif */ | ||||
|     flex-direction: row; | ||||
|     align-items: center; | ||||
|   } | ||||
| 
 | ||||
|   .uni-easyinput__content-input { | ||||
|     /* #ifndef APP-NVUE */ | ||||
|     width: auto; | ||||
|     /* #endif */ | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|     flex: 1; | ||||
|     line-height: 56rpx; | ||||
|     font-size: 28rpx; | ||||
|     height: 56rpx; | ||||
|   } | ||||
|   .uni-easyinput__placeholder-class { | ||||
|     color: #bbbbbb; | ||||
|     font-size: 28rpx; | ||||
|     font-weight: 400; | ||||
|     line-height: normal; | ||||
|   } | ||||
|   .is-textarea { | ||||
|     align-items: flex-start; | ||||
|   } | ||||
| 
 | ||||
|   .is-textarea-icon { | ||||
|     margin-top: 5px; | ||||
|   } | ||||
| 
 | ||||
|   .uni-easyinput__content-textarea { | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|     flex: 1; | ||||
|     line-height: 1.5; | ||||
|     font-size: 14px; | ||||
|     padding-top: 6px; | ||||
|     padding-bottom: 10px; | ||||
|     height: 80px; | ||||
|     /* #ifndef APP-NVUE */ | ||||
|     min-height: 80px; | ||||
|     width: auto; | ||||
|     /* #endif */ | ||||
|   } | ||||
| 
 | ||||
|   .input-padding { | ||||
|     padding-left: 10px; | ||||
|   } | ||||
| 
 | ||||
|   .content-clear-icon { | ||||
|     padding: 0 5px; | ||||
|   } | ||||
| 
 | ||||
|   .label-icon { | ||||
|     margin-right: 5px; | ||||
|     margin-top: -1px; | ||||
|   } | ||||
| 
 | ||||
|   // 显示边框 | ||||
|   .is-input-border { | ||||
|     /* #ifndef APP-NVUE */ | ||||
|     display: flex; | ||||
|     box-sizing: border-box; | ||||
|     /* #endif */ | ||||
|     flex-direction: row; | ||||
|     align-items: center; | ||||
|     border: 1px solid $uni-border-1; | ||||
|     border-radius: 4px; | ||||
|   } | ||||
| 
 | ||||
|   .uni-error-message { | ||||
|     position: absolute; | ||||
|     bottom: -17px; | ||||
|     left: 0; | ||||
|     line-height: 12px; | ||||
|     color: $uni-error; | ||||
|     font-size: 12px; | ||||
|     text-align: left; | ||||
|   } | ||||
| 
 | ||||
|   .uni-error-msg--boeder { | ||||
|     position: relative; | ||||
|     bottom: 0; | ||||
|     line-height: 22px; | ||||
|   } | ||||
| 
 | ||||
|   .is-input-error-border { | ||||
|     border-color: $uni-error; | ||||
|     .uni-easyinput__placeholder-class { | ||||
|       // color: mix(#fff, $uni-error, 50%); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .uni-easyinput--border { | ||||
|     margin-bottom: 0; | ||||
|     padding: 10px 15px; | ||||
|     // padding-bottom: 0; | ||||
|     border-top: 1px #eee solid; | ||||
|   } | ||||
| 
 | ||||
|   .uni-easyinput-error { | ||||
|     padding-bottom: 0; | ||||
|   } | ||||
| 
 | ||||
|   .is-first-border { | ||||
|     /* #ifndef APP-NVUE */ | ||||
|     border: none; | ||||
|     /* #endif */ | ||||
|     /* #ifdef APP-NVUE */ | ||||
|     border-width: 0; | ||||
|     /* #endif */ | ||||
|   } | ||||
| 
 | ||||
|   .is-disabled { | ||||
|     border-color: red; | ||||
|     background-color: #f7f6f6; | ||||
|     color: #d5d5d5; | ||||
|     .uni-easyinput__placeholder-class { | ||||
|       color: #d5d5d5; | ||||
|       font-size: 12px; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  | @ -1,36 +1,49 @@ | |||
| <template> | ||||
|   <view class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20" | ||||
|         :key="orderData.id"> | ||||
|     <view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20"> | ||||
|       <view class="order-no">订单号:{{ orderData.no }}</view> | ||||
|       <view class="order-state ss-font-26" :class="formatOrderColor(orderData)"> | ||||
|         {{ formatOrderStatus(orderData) }} | ||||
|       </view> | ||||
|   <view class="order"> | ||||
|     <view class="top ss-flex ss-row-between"> | ||||
|       <span>{{ orderData.order_sn }}</span> | ||||
|       <span>{{ orderData.create_time.split(' ')[1] }}</span> | ||||
|     </view> | ||||
|     <view class="border-bottom" v-for="item in orderData.items" :key="item.id"> | ||||
|       <s-goods-item | ||||
|         :img="item.picUrl" | ||||
|         :title="item.spuName" | ||||
|         :skuText="item.properties.map((property) => property.valueName).join(' ')" | ||||
|         :price="item.price" | ||||
|         :num="item.count" | ||||
|       /> | ||||
|     </view> | ||||
|     <view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20"> | ||||
|       <view class="ss-flex ss-col-center"> | ||||
|         <view class="discounts-title pay-color">共 {{ orderData.productCount }} 件商品,总金额:</view> | ||||
|         <view class="discounts-money pay-color"> | ||||
|           ¥{{ fen2yuan(orderData.payPrice) }} | ||||
|     <template v-if="from != 'msg'"> | ||||
|       <view class="bottom ss-flex" v-for="item in orderData.items" :key="item"> | ||||
|         <image class="image" :src="sheep.$url.cdn(item.goods_image)" mode="aspectFill"> </image> | ||||
|         <view class="ss-flex-1"> | ||||
|           <view class="title ss-line-2"> | ||||
|             {{ item.goods_title }} | ||||
|           </view> | ||||
|           <view v-if="item.goods_num" class="num ss-m-b-10"> 数量:{{ item.goods_num }} </view> | ||||
|           <view class="ss-flex ss-row-between ss-m-t-8"> | ||||
|             <span class="price">¥{{ item.goods_price }}</span> | ||||
|             <span class="status">{{ orderData.status_text }}</span> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     </template> | ||||
|     <template v-else> | ||||
|       <view class="bottom ss-flex" v-for="item in [orderData.items[0]]" :key="item"> | ||||
|         <image class="image" :src="sheep.$url.cdn(item.goods_image)" mode="aspectFill"> </image> | ||||
|         <view class="ss-flex-1"> | ||||
|           <view class="title title-1 ss-line-1"> | ||||
|             {{ item.goods_title }} | ||||
|           </view> | ||||
|           <view class="order-total ss-flex ss-row-between ss-m-t-8"> | ||||
|             <span>共{{ orderData.items.length }}件商品</span> | ||||
|             <span>合计 ¥{{ orderData.pay_fee }}</span> | ||||
|           </view> | ||||
|           <view class="ss-flex ss-row-right ss-m-t-8"> | ||||
|             <span class="status">{{ orderData.status_text }}</span> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </template> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { fen2yuan, formatOrderColor, formatOrderStatus } from '@/sheep/hooks/useGoods'; | ||||
|   import sheep from '@/sheep'; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     from: String, | ||||
|     orderData: { | ||||
|       type: Object, | ||||
|       default: {}, | ||||
|  | @ -39,76 +52,71 @@ | |||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .order-list-card-box { | ||||
|     .order-card-header { | ||||
|       height: 80rpx; | ||||
|   .order { | ||||
|     background: #fff; | ||||
|     padding: 20rpx; | ||||
|     border-radius: 12rpx; | ||||
| 
 | ||||
|       .order-no { | ||||
|     .top { | ||||
|       line-height: 40rpx; | ||||
|       font-size: 24rpx; | ||||
|       font-weight: 400; | ||||
|       color: #999; | ||||
|       border-bottom: 1px solid rgba(223, 223, 223, 0.5); | ||||
|       margin-bottom: 20rpx; | ||||
|     } | ||||
| 
 | ||||
|     .bottom { | ||||
|       margin-bottom: 20rpx; | ||||
| 
 | ||||
|       &:last-of-type { | ||||
|         margin-bottom: 0; | ||||
|       } | ||||
| 
 | ||||
|       .image { | ||||
|         flex-shrink: 0; | ||||
|         width: 116rpx; | ||||
|         height: 116rpx; | ||||
|         margin-right: 20rpx; | ||||
|       } | ||||
| 
 | ||||
|       .title { | ||||
|         height: 64rpx; | ||||
|         line-height: 32rpx; | ||||
|         font-size: 26rpx; | ||||
|         font-weight: 500; | ||||
|       } | ||||
| 
 | ||||
|       .order-state {} | ||||
|     } | ||||
|     .pay-box { | ||||
|       .discounts-title { | ||||
|         font-size: 24rpx; | ||||
|         line-height: normal; | ||||
|         color: #999999; | ||||
|       } | ||||
| 
 | ||||
|       .discounts-money { | ||||
|         font-size: 24rpx; | ||||
|         line-height: normal; | ||||
|         color: #999; | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
| 
 | ||||
|       .pay-color { | ||||
|         color: #333; | ||||
|       } | ||||
|     } | ||||
|     .order-card-footer { | ||||
|       height: 100rpx; | ||||
| 
 | ||||
|       .more-item-box { | ||||
|         padding: 20rpx; | ||||
| 
 | ||||
|         .more-item { | ||||
|           height: 60rpx; | ||||
| 
 | ||||
|           .title { | ||||
|             font-size: 26rpx; | ||||
|           } | ||||
|         &.title-1 { | ||||
|           height: 32rpx; | ||||
|           width: 300rpx; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .more-btn { | ||||
|         color: $dark-9; | ||||
|       .num { | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 400; | ||||
|         color: #999; | ||||
|       } | ||||
| 
 | ||||
|       .content { | ||||
|         width: 154rpx; | ||||
|         color: #333333; | ||||
|       .price { | ||||
|         font-size: 26rpx; | ||||
|         font-weight: 500; | ||||
|         color: #ff3000; | ||||
|       } | ||||
| 
 | ||||
|       .status { | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 500; | ||||
|         color: var(--ui-BG-Main); | ||||
|       } | ||||
| 
 | ||||
|       .order-total { | ||||
|         line-height: 28rpx; | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 400; | ||||
|         color: #999; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   .warning-color { | ||||
|     color: #faad14; | ||||
|   } | ||||
| 
 | ||||
|   .danger-color { | ||||
|     color: #ff3000; | ||||
|   } | ||||
| 
 | ||||
|   .success-color { | ||||
|     color: #52c41a; | ||||
|   } | ||||
| 
 | ||||
|   .info-color { | ||||
|     color: #999999; | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -14,11 +14,11 @@ | |||
|         <view | ||||
|           class="item" | ||||
|           v-for="item in state.pagination.data" | ||||
|           :key="item.id" | ||||
|           :key="item" | ||||
|           @tap="emits('select', { type: mode, data: item })" | ||||
|         > | ||||
|           <template v-if="mode == 'goods'"> | ||||
|             <GoodsItem :goodsData="item" /> | ||||
|             <GoodsItem :goodsData="item.goods" /> | ||||
|           </template> | ||||
|           <template v-if="mode == 'order'"> | ||||
|             <OrderItem :orderData="item" /> | ||||
|  | @ -32,7 +32,8 @@ | |||
| 
 | ||||
| <script setup> | ||||
|   import { reactive, watch } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import _ from 'lodash'; | ||||
|   import GoodsItem from './goods.vue'; | ||||
|   import OrderItem from './order.vue'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|  | @ -82,7 +83,7 @@ | |||
|             page, | ||||
|             list_rows, | ||||
|           }); | ||||
|     let orderList = _.concat(state.pagination.data, res.data.list); | ||||
|     let orderList = _.concat(state.pagination.data, res.data.data); | ||||
|     state.pagination = { | ||||
|       ...res.data, | ||||
|       data: orderList, | ||||
|  |  | |||
|  | @ -1,166 +0,0 @@ | |||
| <template> | ||||
|   <su-popup | ||||
|     :show="showTools" | ||||
|     @close="handleClose" | ||||
|   > | ||||
|     <view class="ss-modal-box ss-flex-col"> | ||||
|       <slot></slot> | ||||
|       <view class="content ss-flex ss-flex-1"> | ||||
|         <template v-if="toolsMode === 'emoji'"> | ||||
|           <swiper | ||||
|             class="emoji-swiper" | ||||
|             :indicator-dots="true" | ||||
|             circular | ||||
|             indicator-active-color="#7063D2" | ||||
|             indicator-color="rgba(235, 231, 255, 1)" | ||||
|             :autoplay="false" | ||||
|             :interval="3000" | ||||
|             :duration="1000" | ||||
|           > | ||||
|             <swiper-item v-for="emoji in emojiPage" :key="emoji"> | ||||
|               <view class="ss-flex ss-flex-wrap"> | ||||
|                 <image | ||||
|                   v-for="item in emoji" :key="item" | ||||
|                   class="emoji-img" | ||||
|                   :src="sheep.$url.cdn(`/static/img/chat/emoji/${item.file}`)" | ||||
|                   @tap="onEmoji(item)" | ||||
|                 > | ||||
|                 </image> | ||||
|               </view> | ||||
|             </swiper-item> | ||||
|           </swiper> | ||||
|         </template> | ||||
|         <template v-else> | ||||
|           <view class="image"> | ||||
|             <s-uploader | ||||
|               file-mediatype="image" | ||||
|               :imageStyles="{ width: 50, height: 50, border: false }" | ||||
|               @select="imageSelect({ type: 'image', data: $event })" | ||||
|             > | ||||
|               <image | ||||
|                 class="icon" | ||||
|                 :src="sheep.$url.static('/static/img/shop/chat/image.png')" | ||||
|                 mode="aspectFill" | ||||
|               ></image> | ||||
|             </s-uploader> | ||||
|             <view>图片</view> | ||||
|           </view> | ||||
|           <view class="goods" @tap="onShowSelect('goods')"> | ||||
|             <image | ||||
|               class="icon" | ||||
|               :src="sheep.$url.static('/static/img/shop/chat/goods.png')" | ||||
|               mode="aspectFill" | ||||
|             ></image> | ||||
|             <view>商品</view> | ||||
|           </view> | ||||
|           <view class="order" @tap="onShowSelect('order')"> | ||||
|             <image | ||||
|               class="icon" | ||||
|               :src="sheep.$url.static('/static/img/shop/chat/order.png')" | ||||
|               mode="aspectFill" | ||||
|             ></image> | ||||
|             <view>订单</view> | ||||
|           </view> | ||||
|         </template> | ||||
|       </view> | ||||
|     </view> | ||||
|   </su-popup> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   /** | ||||
|    * 聊天工具 | ||||
|    */ | ||||
|   import { emojiPage } from '@/pages/chat/util/emoji'; | ||||
|   import sheep from '@/sheep'; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     // 工具模式 | ||||
|     toolsMode: { | ||||
|       type: String, | ||||
|       default: '', | ||||
|     }, | ||||
|     // 控制工具菜单弹出 | ||||
|     showTools: { | ||||
|       type: Boolean, | ||||
|       default: () => false, | ||||
|     }, | ||||
|   }); | ||||
|   const emits = defineEmits(['onEmoji', 'imageSelect', 'onShowSelect', 'close']); | ||||
| 
 | ||||
|   // 关闭弹出工具菜单 | ||||
|   function handleClose() { | ||||
|     emits('close'); | ||||
|   } | ||||
| 
 | ||||
|   // 选择表情 | ||||
|   function onEmoji(emoji) { | ||||
|     emits('onEmoji', emoji); | ||||
|   } | ||||
| 
 | ||||
|   // 选择图片 | ||||
|   function imageSelect(val) { | ||||
|     emits('imageSelect', val); | ||||
|   } | ||||
| 
 | ||||
|   // 选择商品或订单 | ||||
|   function onShowSelect(mode) { | ||||
|     emits('onShowSelect', mode); | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
|   .content { | ||||
|     width: 100%; | ||||
|     align-content: space-around; | ||||
|     border-top: 1px solid #dfdfdf; | ||||
|     padding: 20rpx 0 0; | ||||
| 
 | ||||
|     .emoji-swiper { | ||||
|       width: 100%; | ||||
|       height: 280rpx; | ||||
|       padding: 0 20rpx; | ||||
| 
 | ||||
|       .emoji-img { | ||||
|         width: 50rpx; | ||||
|         height: 50rpx; | ||||
|         display: inline-block; | ||||
|         margin: 10rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .image, | ||||
|     .goods, | ||||
|     .order { | ||||
|       width: 33.3%; | ||||
|       height: 280rpx; | ||||
|       text-align: center; | ||||
|       font-size: 24rpx; | ||||
|       color: #333; | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
| 
 | ||||
|       .icon { | ||||
|         width: 50rpx; | ||||
|         height: 50rpx; | ||||
|         margin-bottom: 21rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     :deep() { | ||||
|       .uni-file-picker__container { | ||||
|         justify-content: center; | ||||
|       } | ||||
| 
 | ||||
|       .file-picker__box { | ||||
|         display: none; | ||||
| 
 | ||||
|         &:last-of-type { | ||||
|           display: flex; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  | @ -1,43 +1,278 @@ | |||
| <template> | ||||
|   <s-layout | ||||
|     class="chat-wrap" | ||||
|     :title="!isReconnecting ? '连接客服成功' : '会话重连中'" | ||||
|     navbar="inner" | ||||
|   > | ||||
|     <!--  覆盖头部导航栏背景颜色  --> | ||||
|     <view class="page-bg" :style="{ height: sys_navBar + 'px' }"></view> | ||||
|     <!--  聊天区域  --> | ||||
|     <MessageList ref="messageListRef"> | ||||
|       <template #bottom> | ||||
|         <message-input | ||||
|           v-model="chat.msg" | ||||
|           @on-tools="onTools" | ||||
|           @send-message="onSendMessage" | ||||
|           :auto-focus="false" | ||||
|           :show-char-count="true" | ||||
|           :max-length="500" | ||||
|         ></message-input> | ||||
|       </template> | ||||
|     </MessageList> | ||||
|     <!--  聊天工具  --> | ||||
|     <tools-popup | ||||
|       :show-tools="chat.showTools" | ||||
|       :tools-mode="chat.toolsMode" | ||||
|       @close="handleToolsClose" | ||||
|       @on-emoji="onEmoji" | ||||
|       @image-select="onSelect" | ||||
|       @on-show-select="onShowSelect" | ||||
|   <s-layout class="chat-wrap" title="客服" navbar="inner"> | ||||
|     <div class="status"> | ||||
|       {{ socketState.isConnect ? customerServiceInfo.title : '网络已断开,请检查网络后刷新重试' }} | ||||
|     </div> | ||||
|     <div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div> | ||||
|     <view class="chat-box" :style="{ height: pageHeight + 'px' }"> | ||||
|       <scroll-view | ||||
|         :style="{ height: pageHeight + 'px' }" | ||||
|         scroll-y="true" | ||||
|         :scroll-with-animation="false" | ||||
|         :enable-back-to-top="true" | ||||
|         :scroll-into-view="chat.scrollInto" | ||||
|       > | ||||
|         <button | ||||
|           class="loadmore-btn ss-reset-button" | ||||
|           v-if=" | ||||
|             chatList.length && | ||||
|             chatHistoryPagination.lastPage > 1 && | ||||
|             loadingMap[chatHistoryPagination.loadStatus].title | ||||
|           " | ||||
|           @click="onLoadMore" | ||||
|         > | ||||
|           {{ loadingMap[chatHistoryPagination.loadStatus].title }} | ||||
|           <i | ||||
|             class="loadmore-icon sa-m-l-6" | ||||
|             :class="loadingMap[chatHistoryPagination.loadStatus].icon" | ||||
|           ></i> | ||||
|         </button> | ||||
|         <view class="message-item ss-flex-col" v-for="(item, index) in chatList" :key="index"> | ||||
|           <view class="ss-flex ss-row-center ss-col-center"> | ||||
|             <!-- 日期 --> | ||||
|             <view v-if="item.from !== 'system' && showTime(item, index)" class="date-message"> | ||||
|               {{ formatTime(item.date) }} | ||||
|             </view> | ||||
|             <!-- 系统消息 --> | ||||
|             <view v-if="item.from === 'system'" class="system-message"> | ||||
|               {{ item.content.text }} | ||||
|             </view> | ||||
|           </view> | ||||
|           <!-- 常见问题 --> | ||||
|           <view v-if="item.mode === 'template' && item.content.list.length" class="template-wrap"> | ||||
|             <view class="title">猜你想问</view> | ||||
|             <view | ||||
|               class="item" | ||||
|               v-for="(item, index) in item.content.list" | ||||
|               :key="index" | ||||
|               @click="onTemplateList(item)" | ||||
|             > | ||||
|               * {{ item.title }} | ||||
|             </view> | ||||
|           </view> | ||||
| 
 | ||||
|           <view | ||||
|             v-if=" | ||||
|               (item.from === 'customer_service' && item.mode !== 'template') || | ||||
|               item.from === 'customer' | ||||
|             " | ||||
|             class="ss-flex ss-col-top" | ||||
|             :class="[ | ||||
|               item.from === 'customer_service' | ||||
|                 ? `ss-row-left` | ||||
|                 : item.from === 'customer' | ||||
|                 ? `ss-row-right` | ||||
|                 : '', | ||||
|             ]" | ||||
|           > | ||||
|             <!-- 客服头像 --> | ||||
|             <image | ||||
|               v-show="item.from === 'customer_service'" | ||||
|               class="chat-avatar ss-m-r-24" | ||||
|               :src=" | ||||
|                 sheep.$url.cdn(item?.sender?.avatar) || | ||||
|                 sheep.$url.static('/static/img/shop/chat/default.png') | ||||
|               " | ||||
|               mode="aspectFill" | ||||
|             ></image> | ||||
| 
 | ||||
|             <!-- 发送状态 --> | ||||
|             <span | ||||
|               v-if=" | ||||
|                 item.from === 'customer' && | ||||
|                 index == chatData.chatList.length - 1 && | ||||
|                 chatData.isSendSucces !== 0 | ||||
|               " | ||||
|               class="send-status" | ||||
|             > | ||||
|               <image | ||||
|                 v-if="chatData.isSendSucces == -1" | ||||
|                 class="loading" | ||||
|                 :src="sheep.$url.static('/static/img/shop/chat/loading.png')" | ||||
|                 mode="aspectFill" | ||||
|               ></image> | ||||
|               <!-- <image | ||||
|                 v-if="chatData.isSendSucces == 1" | ||||
|                 class="warning" | ||||
|                 :src="sheep.$url.static('/static/img/shop/chat/warning.png')" | ||||
|                 mode="aspectFill" | ||||
|                 @click="onAgainSendMessage(item)" | ||||
|               ></image> --> | ||||
|             </span> | ||||
| 
 | ||||
|             <!-- 内容 --> | ||||
|             <template v-if="item.mode === 'text'"> | ||||
|               <view class="message-box" :class="[item.from]"> | ||||
|                 <div | ||||
|                   class="message-text ss-flex ss-flex-wrap" | ||||
|                   @click="onRichtext" | ||||
|                   v-html="replaceEmoji(item.content.text)" | ||||
|                 ></div> | ||||
|               </view> | ||||
|             </template> | ||||
|             <template v-if="item.mode === 'image'"> | ||||
|               <view class="message-box" :class="[item.from]" :style="{ width: '200rpx' }"> | ||||
|                 <su-image | ||||
|                   class="message-img" | ||||
|                   isPreview | ||||
|                   :previewList="[sheep.$url.cdn(item.content.url)]" | ||||
|                   :current="0" | ||||
|                   :src="sheep.$url.cdn(item.content.url)" | ||||
|                   :height="200" | ||||
|                   :width="200" | ||||
|                   mode="aspectFill" | ||||
|                 ></su-image> | ||||
|               </view> | ||||
|             </template> | ||||
|             <template v-if="item.mode === 'goods'"> | ||||
|               <GoodsItem | ||||
|                 :goodsData="item.content.item" | ||||
|                 @tap=" | ||||
|                   sheep.$router.go('/pages/goods/index', { | ||||
|                     id: item.content.item.id, | ||||
|                   }) | ||||
|                 " | ||||
|               /> | ||||
|             </template> | ||||
|             <template v-if="item.mode === 'order'"> | ||||
|               <OrderItem | ||||
|                 from="msg" | ||||
|                 :orderData="item.content.item" | ||||
|                 @tap=" | ||||
|                   sheep.$router.go('/pages/order/detail', { | ||||
|                     id: item.content.item.id, | ||||
|                   }) | ||||
|                 " | ||||
|               /> | ||||
|             </template> | ||||
|             <!-- user头像 --> | ||||
|             <image | ||||
|               v-show="item.from === 'customer'" | ||||
|               class="chat-avatar ss-m-l-24" | ||||
|               :src="sheep.$url.cdn(customerUserInfo.avatar)" | ||||
|               mode="aspectFill" | ||||
|             > | ||||
|             </image> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view id="scrollBottom"></view> | ||||
|       </scroll-view> | ||||
|     </view> | ||||
|     <su-fixed bottom> | ||||
|       <view class="send-wrap ss-flex"> | ||||
|         <view class="left ss-flex ss-flex-1"> | ||||
|           <uni-easyinput | ||||
|             class="ss-flex-1 ss-p-l-22" | ||||
|             :inputBorder="false" | ||||
|             :clearable="false" | ||||
|             v-model="chat.msg" | ||||
|             placeholder="请输入你要咨询的问题" | ||||
|           ></uni-easyinput> | ||||
|         </view> | ||||
|         <text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text> | ||||
|         <text | ||||
|           v-if="!chat.msg" | ||||
|           class="sicon-edit" | ||||
|           :class="{ 'is-active': chat.toolsMode == 'tools' }" | ||||
|           @tap.stop="onTools('tools')" | ||||
|         ></text> | ||||
|         <button v-if="chat.msg" class="ss-reset-button send-btn" @tap="onSendMessage"> | ||||
|           发送 | ||||
|         </button> | ||||
|       </view> | ||||
|     </su-fixed> | ||||
|     <su-popup | ||||
|       :show="chat.showTools" | ||||
|       @close=" | ||||
|         chat.showTools = false; | ||||
|         chat.toolsMode = ''; | ||||
|       " | ||||
|     > | ||||
|       <message-input | ||||
|         v-model="chat.msg" | ||||
|         @on-tools="onTools" | ||||
|         @send-message="onSendMessage" | ||||
|         :auto-focus="false" | ||||
|         :show-char-count="true" | ||||
|         :max-length="500" | ||||
|       ></message-input> | ||||
|     </tools-popup> | ||||
|     <!--  商品订单选择  --> | ||||
|       <view class="ss-modal-box ss-flex-col"> | ||||
|         <view class="send-wrap ss-flex"> | ||||
|           <view class="left ss-flex ss-flex-1"> | ||||
|             <uni-easyinput | ||||
|               class="ss-flex-1 ss-p-l-22" | ||||
|               :inputBorder="false" | ||||
|               :clearable="false" | ||||
|               v-model="chat.msg" | ||||
|               placeholder="请输入你要咨询的问题" | ||||
|             ></uni-easyinput> | ||||
|           </view> | ||||
|           <text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text> | ||||
|           <text></text> | ||||
|           <text | ||||
|             v-if="!chat.msg" | ||||
|             class="sicon-edit" | ||||
|             :class="{ 'is-active': chat.toolsMode == 'tools' }" | ||||
|             @tap.stop="onTools('tools')" | ||||
|           ></text> | ||||
|           <button v-if="chat.msg" class="ss-reset-button send-btn" @tap="onSendMessage"> | ||||
|             发送 | ||||
|           </button> | ||||
|         </view> | ||||
|         <view class="content ss-flex ss-flex-1"> | ||||
|           <template v-if="chat.toolsMode == 'emoji'"> | ||||
|             <swiper | ||||
|               class="emoji-swiper" | ||||
|               :indicator-dots="true" | ||||
|               circular | ||||
|               indicator-active-color="#7063D2" | ||||
|               indicator-color="rgba(235, 231, 255, 1)" | ||||
|               :autoplay="false" | ||||
|               :interval="3000" | ||||
|               :duration="1000" | ||||
|             > | ||||
|               <swiper-item v-for="emoji in emojiPage" :key="emoji"> | ||||
|                 <view class="ss-flex ss-flex-wrap"> | ||||
|                   <template v-for="item in emoji" :key="item"> | ||||
|                     <image | ||||
|                       class="emoji-img" | ||||
|                       :src="sheep.$url.cdn(`/static/img/chat/emoji/${item.file}`)" | ||||
|                       @tap="onEmoji(item)" | ||||
|                     > | ||||
|                     </image> | ||||
|                   </template> | ||||
|                 </view> | ||||
|               </swiper-item> | ||||
|             </swiper> | ||||
|           </template> | ||||
|           <template v-else> | ||||
|             <view class="image"> | ||||
|               <s-uploader | ||||
|                 file-mediatype="image" | ||||
|                 :imageStyles="{ width: 50, height: 50, border: false }" | ||||
|                 @select="onSelect({ type: 'image', data: $event })" | ||||
|               > | ||||
|                 <image | ||||
|                   class="icon" | ||||
|                   :src="sheep.$url.static('/static/img/shop/chat/image.png')" | ||||
|                   mode="aspectFill" | ||||
|                 ></image> | ||||
|               </s-uploader> | ||||
|               <view>图片</view> | ||||
|             </view> | ||||
|             <view class="goods" @tap="onShowSelect('goods')"> | ||||
|               <image | ||||
|                 class="icon" | ||||
|                 :src="sheep.$url.static('/static/img/shop/chat/goods.png')" | ||||
|                 mode="aspectFill" | ||||
|               ></image> | ||||
|               <view>商品</view> | ||||
|             </view> | ||||
|             <view class="order" @tap="onShowSelect('order')"> | ||||
|               <image | ||||
|                 class="icon" | ||||
|                 :src="sheep.$url.static('/static/img/shop/chat/order.png')" | ||||
|                 mode="aspectFill" | ||||
|               ></image> | ||||
|               <view>订单</view> | ||||
|             </view> | ||||
|           </template> | ||||
|         </view> | ||||
|       </view> | ||||
|     </su-popup> | ||||
| 
 | ||||
|     <SelectPopup | ||||
|       :mode="chat.selectMode" | ||||
|       :show="chat.showSelect" | ||||
|  | @ -48,82 +283,109 @@ | |||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import MessageList from '@/pages/chat/components/messageList.vue'; | ||||
|   import { reactive, ref, toRefs } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import ToolsPopup from '@/pages/chat/components/toolsPopup.vue'; | ||||
|   import MessageInput from '@/pages/chat/components/messageInput.vue'; | ||||
|   import SelectPopup from '@/pages/chat/components/select-popup.vue'; | ||||
|   import { | ||||
|     KeFuMessageContentTypeEnum, | ||||
|     WebSocketMessageTypeConstants, | ||||
|   } from '@/pages/chat/util/constants'; | ||||
|   import FileApi from '@/sheep/api/infra/file'; | ||||
|   import KeFuApi from '@/sheep/api/promotion/kefu'; | ||||
|   import { useWebSocket } from './util/useWebSocket'; | ||||
|   import { jsonParse } from '@/sheep/helper/utils'; | ||||
|   import { computed, reactive, toRefs } from 'vue'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { emojiList, emojiPage } from './emoji.js'; | ||||
|   import SelectPopup from './components/select-popup.vue'; | ||||
|   import GoodsItem from './components/goods.vue'; | ||||
|   import OrderItem from './components/order.vue'; | ||||
|   import { useChatWebSocket } from './socket'; | ||||
| 
 | ||||
|   const { | ||||
|     socketInit, | ||||
|     state: chatData, | ||||
|     socketSendMsg, | ||||
|     formatChatInput, | ||||
|     socketHistoryList, | ||||
|     onDrop, | ||||
|     onPaste, | ||||
|     getFocus, | ||||
|     // upload, | ||||
|     getUserToken, | ||||
|     // socketTest, | ||||
|     showTime, | ||||
|     formatTime, | ||||
|   } = useChatWebSocket(); | ||||
|   const chatList = toRefs(chatData).chatList; | ||||
|   const customerServiceInfo = toRefs(chatData).customerServerInfo; | ||||
|   const chatHistoryPagination = toRefs(chatData).chatHistoryPagination; | ||||
|   const customerUserInfo = toRefs(chatData).customerUserInfo; | ||||
|   const socketState = toRefs(chatData).socketState; | ||||
| 
 | ||||
|   const sys_navBar = sheep.$platform.navbar; | ||||
|   const chatConfig = computed(() => sheep.$store('app').chat); | ||||
| 
 | ||||
|   const { screenHeight, safeAreaInsets, safeArea, screenWidth } = sheep.$platform.device; | ||||
|   const pageHeight = safeArea.height - 44 - 35 - 50; | ||||
| 
 | ||||
|   const chatStatus = { | ||||
|     online: { | ||||
|       text: '在线', | ||||
|       colorVariate: '#46c55f', | ||||
|     }, | ||||
|     offline: { | ||||
|       text: '离线', | ||||
|       colorVariate: '#b5b5b5', | ||||
|     }, | ||||
|     busy: { | ||||
|       text: '忙碌', | ||||
|       colorVariate: '#ff0e1b', | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   // 加载更多 | ||||
|   const loadingMap = { | ||||
|     loadmore: { | ||||
|       title: '查看更多', | ||||
|       icon: 'el-icon-d-arrow-left', | ||||
|     }, | ||||
|     nomore: { | ||||
|       title: '没有更多了', | ||||
|       icon: '', | ||||
|     }, | ||||
|     loading: { | ||||
|       title: '加载中... ', | ||||
|       icon: 'el-icon-loading', | ||||
|     }, | ||||
|   }; | ||||
|   const onLoadMore = () => { | ||||
|     chatHistoryPagination.value.page < chatHistoryPagination.value.lastPage && socketHistoryList(); | ||||
|   }; | ||||
| 
 | ||||
|   const chat = reactive({ | ||||
|     msg: '', | ||||
|     scrollInto: '', | ||||
| 
 | ||||
|     showTools: false, | ||||
|     toolsMode: '', | ||||
| 
 | ||||
|     showSelect: false, | ||||
|     selectMode: '', | ||||
|     chatStyle: { | ||||
|       mode: 'inner', | ||||
|       color: '#F8270F', | ||||
|       type: 'color', | ||||
|       alwaysShow: 1, | ||||
|       src: '', | ||||
|       list: {}, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   // 发送消息 | ||||
|   async function onSendMessage() { | ||||
|     if (!chat.msg) return; | ||||
|     try { | ||||
|       const data = { | ||||
|         contentType: KeFuMessageContentTypeEnum.TEXT, | ||||
|         content: JSON.stringify({ text: chat.msg }), | ||||
|       }; | ||||
|       await KeFuApi.sendKefuMessage(data); | ||||
|       chat.msg = ''; | ||||
|     } finally { | ||||
|       chat.showTools = false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const messageListRef = ref(); | ||||
| 
 | ||||
|   //======================= 聊天工具相关 start ======================= | ||||
| 
 | ||||
|   function handleToolsClose() { | ||||
|     chat.showTools = false; | ||||
|     chat.toolsMode = ''; | ||||
|   } | ||||
| 
 | ||||
|   function onEmoji(item) { | ||||
|     chat.msg += item.name; | ||||
|   } | ||||
| 
 | ||||
|   // 点击工具栏开关 | ||||
|   function onTools(mode) { | ||||
|     if (isReconnecting.value) { | ||||
|       sheep.$helper.toast('您已掉线!请返回重试'); | ||||
|     if (!socketState.value.isConnect) { | ||||
|       sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试'); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 第二次点击关闭 | ||||
|     if (chat.showTools && chat.toolsMode === mode) { | ||||
|       handleToolsClose(); | ||||
|       return; | ||||
|     if (!chat.toolsMode || chat.toolsMode === mode) { | ||||
|       chat.showTools = !chat.showTools; | ||||
|     } | ||||
|     // 切换工具栏 | ||||
|     if (chat.showTools && chat.toolsMode !== mode) { | ||||
|       chat.showTools = false; | ||||
|     chat.toolsMode = mode; | ||||
|     if (!chat.showTools) { | ||||
|       chat.toolsMode = ''; | ||||
|     } | ||||
|     // 延迟打开等一下过度效果 | ||||
|     setTimeout(() => { | ||||
|       chat.toolsMode = mode; | ||||
|       chat.showTools = true; | ||||
|     }, 200); | ||||
|   } | ||||
| 
 | ||||
|   function onShowSelect(mode) { | ||||
|  | @ -133,77 +395,205 @@ | |||
|   } | ||||
| 
 | ||||
|   async function onSelect({ type, data }) { | ||||
|     let msg; | ||||
|     let msg = ''; | ||||
|     switch (type) { | ||||
|       case 'image': | ||||
|         const res = await FileApi.uploadFile(data.tempFiles[0].path); | ||||
|         const { path, fullurl } = await sheep.$api.app.upload(data.tempFiles[0].path, 'default'); | ||||
|         msg = { | ||||
|           contentType: KeFuMessageContentTypeEnum.IMAGE, | ||||
|           content: JSON.stringify({ picUrl: res.data }), | ||||
|           from: 'customer', | ||||
|           mode: 'image', | ||||
|           date: new Date().getTime(), | ||||
|           content: { | ||||
|             url: fullurl, | ||||
|             path: path, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       case 'goods': | ||||
|         msg = { | ||||
|           contentType: KeFuMessageContentTypeEnum.PRODUCT, | ||||
|           content: JSON.stringify(data), | ||||
|           from: 'customer', | ||||
|           mode: 'goods', | ||||
|           date: new Date().getTime(), | ||||
|           content: { | ||||
|             item: { | ||||
|               id: data.goods.id, | ||||
|               title: data.goods.title, | ||||
|               image: data.goods.image, | ||||
|               price: data.goods.price, | ||||
|               stock: data.goods.stock, | ||||
|             }, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       case 'order': | ||||
|         msg = { | ||||
|           contentType: KeFuMessageContentTypeEnum.ORDER, | ||||
|           content: JSON.stringify(data), | ||||
|           from: 'customer', | ||||
|           mode: 'order', | ||||
|           date: new Date().getTime(), | ||||
|           content: { | ||||
|             item: { | ||||
|               id: data.id, | ||||
|               order_sn: data.order_sn, | ||||
|               create_time: data.create_time, | ||||
|               pay_fee: data.pay_fee, | ||||
|               items: data.items.filter((item) => ({ | ||||
|                 goods_id: item.goods_id, | ||||
|                 goods_title: item.goods_title, | ||||
|                 goods_image: item.goods_image, | ||||
|                 goods_price: item.goods_price, | ||||
|               })), | ||||
|               status_text: data.status_text, | ||||
|             }, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|     } | ||||
|     if (msg) { | ||||
|       // 发送消息 | ||||
|       socketSendMsg(msg, () => { | ||||
|         scrollBottom(); | ||||
|       }); | ||||
|       // scrollBottom(); | ||||
|       await KeFuApi.sendKefuMessage(msg); | ||||
|       await messageListRef.value.refreshMessageList(); | ||||
|       chat.showTools = false; | ||||
|       chat.showSelect = false; | ||||
|       chat.selectMode = ''; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   //======================= 聊天工具相关 end ======================= | ||||
|   const { options } = useWebSocket({ | ||||
|     // 连接成功 | ||||
|     onConnected: async () => {}, | ||||
|     // 收到消息 | ||||
|     onMessage: async (data) => { | ||||
|       const type = data.type; | ||||
|       if (!type) { | ||||
|         console.error('未知的消息类型:' + data); | ||||
|         return; | ||||
|   function onAgainSendMessage(item) { | ||||
|     if (!socketState.value.isConnect) { | ||||
|       sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试'); | ||||
|       return; | ||||
|     } | ||||
|     if (!item) return; | ||||
|     const data = { | ||||
|       from: 'customer', | ||||
|       mode: 'text', | ||||
|       date: new Date().getTime(), | ||||
|       content: item.content, | ||||
|     }; | ||||
|     socketSendMsg(data, () => { | ||||
|       scrollBottom(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   function onSendMessage() { | ||||
|     if (!socketState.value.isConnect) { | ||||
|       sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试'); | ||||
|       return; | ||||
|     } | ||||
|     if (!chat.msg) return; | ||||
|     const data = { | ||||
|       from: 'customer', | ||||
|       mode: 'text', | ||||
|       date: new Date().getTime(), | ||||
|       content: { | ||||
|         text: chat.msg, | ||||
|       }, | ||||
|     }; | ||||
|     socketSendMsg(data, () => { | ||||
|       scrollBottom(); | ||||
|     }); | ||||
|     chat.showTools = false; | ||||
|     // scrollBottom(); | ||||
|     setTimeout(() => { | ||||
|       chat.msg = ''; | ||||
|     }, 100); | ||||
|   } | ||||
| 
 | ||||
|   // 点击猜你想问 | ||||
|   function onTemplateList(e) { | ||||
|     if (!socketState.value.isConnect) { | ||||
|       sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试'); | ||||
|       return; | ||||
|     } | ||||
|     const data = { | ||||
|       from: 'customer', | ||||
|       mode: 'text', | ||||
|       date: new Date().getTime(), | ||||
|       content: { | ||||
|         text: e.title, | ||||
|       }, | ||||
|       customData: { | ||||
|         question_id: e.id, | ||||
|       }, | ||||
|     }; | ||||
|     socketSendMsg(data, () => { | ||||
|       scrollBottom(); | ||||
|     }); | ||||
|     // scrollBottom(); | ||||
|   } | ||||
| 
 | ||||
|   function onEmoji(item) { | ||||
|     chat.msg += item.name; | ||||
|   } | ||||
| 
 | ||||
|   function selEmojiFile(name) { | ||||
|     for (let index in emojiList) { | ||||
|       if (emojiList[index].name === name) { | ||||
|         return emojiList[index].file; | ||||
|       } | ||||
|       // 2.2 消息类型:KEFU_MESSAGE_TYPE | ||||
|       if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) { | ||||
|         // 刷新消息列表 | ||||
|         await messageListRef.value.refreshMessageList(jsonParse(data.content)); | ||||
|         return; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   function replaceEmoji(data) { | ||||
|     let newData = data; | ||||
|     if (typeof newData !== 'object') { | ||||
|       let reg = /\[(.+?)\]/g; // [] 中括号 | ||||
|       let zhEmojiName = newData.match(reg); | ||||
|       if (zhEmojiName) { | ||||
|         zhEmojiName.forEach((item) => { | ||||
|           let emojiFile = selEmojiFile(item); | ||||
|           newData = newData.replace( | ||||
|             item, | ||||
|             `<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;" src="${sheep.$url.cdn( | ||||
|               '/static/img/chat/emoji/' + emojiFile, | ||||
|             )}"/>`, | ||||
|           ); | ||||
|         }); | ||||
|       } | ||||
|       // 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ | ||||
|       if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) { | ||||
|         console.log('管理员已读消息'); | ||||
|         // 更新消息已读状态 | ||||
|         sheep.$helper.toast('客服已读您的消息'); | ||||
|       } | ||||
|     }, | ||||
|     } | ||||
|     return newData; | ||||
|   } | ||||
| 
 | ||||
|   function scrollBottom() { | ||||
|     let timeout = null; | ||||
|     chat.scrollInto = ''; | ||||
|     clearTimeout(timeout); | ||||
|     timeout = setTimeout(() => { | ||||
|       chat.scrollInto = 'scrollBottom'; | ||||
|     }, 100); | ||||
|   } | ||||
| 
 | ||||
|   onLoad(async () => { | ||||
|     const { error } = await getUserToken(); | ||||
|     if (error === 0) { | ||||
|       socketInit(chatConfig.value, () => { | ||||
|         scrollBottom(); | ||||
|       }); | ||||
|     } else { | ||||
|       socketState.value.isConnect = false; | ||||
|     } | ||||
|   }); | ||||
|   const isReconnecting = toRefs(options).isReconnecting; // 重连状态 | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
| <style lang="scss" scoped> | ||||
|   .page-bg { | ||||
|     width: 100%; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|     background-size: 750rpx 100%; | ||||
|     z-index: 1; | ||||
|   } | ||||
| 
 | ||||
|   .chat-wrap { | ||||
|     .page-bg { | ||||
|       width: 100%; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       left: 0; | ||||
|       background-color: var(--ui-BG-Main); | ||||
|       z-index: 1; | ||||
|     } | ||||
|     // :deep() { | ||||
|     //   .ui-navbar-box { | ||||
|     //     background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|     //   } | ||||
|     // } | ||||
| 
 | ||||
|     .status { | ||||
|       position: relative; | ||||
|  | @ -218,5 +608,263 @@ | |||
|       font-weight: 400; | ||||
|       color: var(--ui-BG-Main); | ||||
|     } | ||||
| 
 | ||||
|     .chat-box { | ||||
|       padding: 0 20rpx 0; | ||||
| 
 | ||||
|       .loadmore-btn { | ||||
|         width: 98%; | ||||
|         height: 40px; | ||||
|         font-size: 12px; | ||||
|         color: #8c8c8c; | ||||
| 
 | ||||
|         .loadmore-icon { | ||||
|           transform: rotate(90deg); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .message-item { | ||||
|         margin-bottom: 33rpx; | ||||
|       } | ||||
| 
 | ||||
|       .date-message, | ||||
|       .system-message { | ||||
|         width: fit-content; | ||||
|         border-radius: 12rpx; | ||||
|         padding: 8rpx 16rpx; | ||||
|         margin-bottom: 16rpx; | ||||
|         background-color: var(--ui-BG-3); | ||||
|         color: #999; | ||||
|         font-size: 24rpx; | ||||
|       } | ||||
| 
 | ||||
|       .chat-avatar { | ||||
|         width: 70rpx; | ||||
|         height: 70rpx; | ||||
|         border-radius: 50%; | ||||
|       } | ||||
| 
 | ||||
|       .send-status { | ||||
|         color: #333; | ||||
|         height: 80rpx; | ||||
|         margin-right: 8rpx; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
| 
 | ||||
|         .loading { | ||||
|           width: 32rpx; | ||||
|           height: 32rpx; | ||||
|           -webkit-animation: rotating 2s linear infinite; | ||||
|           animation: rotating 2s linear infinite; | ||||
| 
 | ||||
|           @-webkit-keyframes rotating { | ||||
|             0% { | ||||
|               transform: rotateZ(0); | ||||
|             } | ||||
| 
 | ||||
|             100% { | ||||
|               transform: rotateZ(360deg); | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           @keyframes rotating { | ||||
|             0% { | ||||
|               transform: rotateZ(0); | ||||
|             } | ||||
| 
 | ||||
|             100% { | ||||
|               transform: rotateZ(360deg); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         .warning { | ||||
|           width: 32rpx; | ||||
|           height: 32rpx; | ||||
|           color: #ff3000; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .message-box { | ||||
|         max-width: 50%; | ||||
|         font-size: 16px; | ||||
|         line-height: 20px; | ||||
|         // max-width: 500rpx; | ||||
|         white-space: normal; | ||||
|         word-break: break-all; | ||||
|         word-wrap: break-word; | ||||
|         padding: 20rpx; | ||||
|         border-radius: 10rpx; | ||||
|         color: #fff; | ||||
|         background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
| 
 | ||||
|         &.customer_service { | ||||
|           background: #fff; | ||||
|           color: #333; | ||||
|         } | ||||
| 
 | ||||
|         :deep() { | ||||
|           .imgred { | ||||
|             width: 100%; | ||||
|           } | ||||
| 
 | ||||
|           .imgred, | ||||
|           img { | ||||
|             width: 100%; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       :deep() { | ||||
|         .goods, | ||||
|         .order { | ||||
|           max-width: 500rpx; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .message-img { | ||||
|         width: 100px; | ||||
|         height: 100px; | ||||
|         border-radius: 6rpx; | ||||
|       } | ||||
| 
 | ||||
|       .template-wrap { | ||||
|         // width: 100%; | ||||
|         padding: 20rpx 24rpx; | ||||
|         background: #fff; | ||||
|         border-radius: 10rpx; | ||||
| 
 | ||||
|         .title { | ||||
|           font-size: 26rpx; | ||||
|           font-weight: 500; | ||||
|           color: #333; | ||||
|           margin-bottom: 29rpx; | ||||
|         } | ||||
| 
 | ||||
|         .item { | ||||
|           font-size: 24rpx; | ||||
|           color: var(--ui-BG-Main); | ||||
|           margin-bottom: 16rpx; | ||||
| 
 | ||||
|           &:last-of-type { | ||||
|             margin-bottom: 0; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .error-img { | ||||
|         width: 400rpx; | ||||
|         height: 400rpx; | ||||
|       } | ||||
| 
 | ||||
|       #scrollBottom { | ||||
|         height: 120rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .send-wrap { | ||||
|       padding: 18rpx 20rpx; | ||||
|       background: #fff; | ||||
| 
 | ||||
|       .left { | ||||
|         height: 64rpx; | ||||
|         border-radius: 32rpx; | ||||
|         background: var(--ui-BG-1); | ||||
|       } | ||||
| 
 | ||||
|       .bq { | ||||
|         font-size: 50rpx; | ||||
|         margin-left: 10rpx; | ||||
|       } | ||||
| 
 | ||||
|       .sicon-edit { | ||||
|         font-size: 50rpx; | ||||
|         margin-left: 10rpx; | ||||
|         transform: rotate(0deg); | ||||
|         transition: all linear 0.2s; | ||||
| 
 | ||||
|         &.is-active { | ||||
|           transform: rotate(45deg); | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       .send-btn { | ||||
|         width: 100rpx; | ||||
|         height: 60rpx; | ||||
|         line-height: 60rpx; | ||||
|         border-radius: 30rpx; | ||||
|         background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|         font-size: 26rpx; | ||||
|         color: #fff; | ||||
|         margin-left: 11rpx; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .content { | ||||
|     width: 100%; | ||||
|     align-content: space-around; | ||||
|     border-top: 1px solid #dfdfdf; | ||||
|     padding: 20rpx 0 0; | ||||
| 
 | ||||
|     .emoji-swiper { | ||||
|       width: 100%; | ||||
|       height: 280rpx; | ||||
|       padding: 0 20rpx; | ||||
| 
 | ||||
|       .emoji-img { | ||||
|         width: 50rpx; | ||||
|         height: 50rpx; | ||||
|         display: inline-block; | ||||
|         margin: 10rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .image, | ||||
|     .goods, | ||||
|     .order { | ||||
|       width: 33.3%; | ||||
|       height: 280rpx; | ||||
|       text-align: center; | ||||
|       font-size: 24rpx; | ||||
|       color: #333; | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       justify-content: center; | ||||
| 
 | ||||
|       .icon { | ||||
|         width: 50rpx; | ||||
|         height: 50rpx; | ||||
|         margin-bottom: 21rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     :deep() { | ||||
|       .uni-file-picker__container { | ||||
|         justify-content: center; | ||||
|       } | ||||
| 
 | ||||
|       .file-picker__box { | ||||
|         display: none; | ||||
| 
 | ||||
|         &:last-of-type { | ||||
|           display: flex; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| <style> | ||||
|   .chat-img { | ||||
|     width: 24px; | ||||
|     height: 24px; | ||||
|     margin: 0 3px; | ||||
|   } | ||||
|   .full-img { | ||||
|     object-fit: cover; | ||||
|     width: 100px; | ||||
|     height: 100px; | ||||
|     border-radius: 6px; | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -0,0 +1,821 @@ | |||
| import { reactive, ref, unref } from 'vue'; | ||||
| import sheep from '@/sheep'; | ||||
| // import chat from '@/sheep/api/chat';
 | ||||
| import dayjs from 'dayjs'; | ||||
| import io from '@hyoga/uni-socket.io'; | ||||
| 
 | ||||
| export function useChatWebSocket(socketConfig) { | ||||
|   let SocketIo = null; | ||||
| 
 | ||||
|   // chat状态数据
 | ||||
|   const state = reactive({ | ||||
|     chatDotNum: 0, //总状态红点
 | ||||
|     chatList: [], //会话信息
 | ||||
|     customerUserInfo: {}, //用户信息
 | ||||
|     customerServerInfo: { | ||||
|       //客服信息
 | ||||
|       title: '连接中...', | ||||
|       state: 'connecting', | ||||
|       avatar: null, | ||||
|       nickname: '', | ||||
|     }, | ||||
|     socketState: { | ||||
|       isConnect: true, //是否连接成功
 | ||||
|       isConnecting: false, //重连中,不允许新的socket开启。
 | ||||
|       tip: '', | ||||
|     }, | ||||
|     chatHistoryPagination: { | ||||
|       page: 0, //当前页
 | ||||
|       list_rows: 10, //每页条数
 | ||||
|       last_id: 0, //最后条ID
 | ||||
|       lastPage: 0, //总共多少页
 | ||||
|       loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
 | ||||
|     }, | ||||
|     templateChatList: [], //猜你想问
 | ||||
| 
 | ||||
|     chatConfig: {}, // 配置信息
 | ||||
| 
 | ||||
|     isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败
 | ||||
|   }); | ||||
| 
 | ||||
|   /** | ||||
|    * 连接初始化 | ||||
|    * @param {Object} config  - 配置信息 | ||||
|    * @param {Function} callBack -回调函数,有新消息接入,保持底部 | ||||
|    */ | ||||
|   const socketInit = (config, callBack) => { | ||||
|     state.chatConfig = config; | ||||
|     if (SocketIo && SocketIo.connected) return; // 如果socket已经连接,返回false
 | ||||
|     if (state.socketState.isConnecting) return; // 重连中,返回false
 | ||||
| 
 | ||||
|     // 启动初始化
 | ||||
|     SocketIo = io(config.chat_domain, { | ||||
|       reconnection: true, // 默认 true    是否断线重连
 | ||||
|       reconnectionAttempts: 5, // 默认无限次   断线尝试次数
 | ||||
|       reconnectionDelay: 1000, // 默认 1000,进行下一次重连的间隔。
 | ||||
|       reconnectionDelayMax: 5000, // 默认 5000, 重新连接等待的最长时间 默认 5000
 | ||||
|       randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
 | ||||
|       timeout: 20000, // 默认 20s
 | ||||
|       transports: ['websocket', 'polling'], // websocket | polling,
 | ||||
|       ...config, | ||||
|     }); | ||||
| 
 | ||||
|     // 监听连接
 | ||||
|     SocketIo.on('connect', async (res) => { | ||||
|       socketReset(callBack); | ||||
|       // socket连接
 | ||||
|       // 用户登录
 | ||||
|       // 顾客登录
 | ||||
|       console.log('socket:connect'); | ||||
|     }); | ||||
|     // 监听消息
 | ||||
|     SocketIo.on('message', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         const { message, sender } = res.data; | ||||
|         state.chatList.push(formatMessage(res.data.message)); | ||||
| 
 | ||||
|         // 告诉父级页面
 | ||||
|         // window.parent.postMessage({
 | ||||
|         // 	chatDotNum: ++state.chatDotNum
 | ||||
|         // })
 | ||||
|         callBack && callBack(); | ||||
|       } | ||||
|     }); | ||||
|     // 监听客服接入成功
 | ||||
|     SocketIo.on('customer_service_access', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: res.data.customer_service.name, | ||||
|           state: 'online', | ||||
|           avatar: res.data.customer_service.avatar, | ||||
|         }); | ||||
|         state.chatList.push(formatMessage(res.data.message)); | ||||
|         // callBack && callBack()
 | ||||
|       } | ||||
|     }); | ||||
|     // 监听排队等待
 | ||||
|     SocketIo.on('waiting_queue', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: res.data.title, | ||||
|           state: 'waiting', | ||||
|           avatar: '', | ||||
|         }); | ||||
|         // callBack && callBack()
 | ||||
|       } | ||||
|     }); | ||||
|     // 监听没有客服在线
 | ||||
|     SocketIo.on('no_customer_service', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: '暂无客服在线...', | ||||
|           state: 'waiting', | ||||
|           avatar: '', | ||||
|         }); | ||||
|       } | ||||
|       state.chatList.push(formatMessage(res.data.message)); | ||||
|       // callBack && callBack()
 | ||||
|     }); | ||||
|     // 监听客服上线
 | ||||
|     SocketIo.on('customer_service_online', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: res.data.customer_service.name, | ||||
|           state: 'online', | ||||
|           avatar: res.data.customer_service.avatar, | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|     // 监听客服下线
 | ||||
|     SocketIo.on('customer_service_offline', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: res.data.customer_service.name, | ||||
|           state: 'offline', | ||||
|           avatar: res.data.customer_service.avatar, | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|     // 监听客服忙碌
 | ||||
|     SocketIo.on('customer_service_busy', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: res.data.customer_service.name, | ||||
|           state: 'busy', | ||||
|           avatar: res.data.customer_service.avatar, | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|     // 监听客服断开链接
 | ||||
|     SocketIo.on('customer_service_break', (res) => { | ||||
|       if (res.error === 0) { | ||||
|         editCustomerServerInfo({ | ||||
|           title: '客服服务结束', | ||||
|           state: 'offline', | ||||
|           avatar: '', | ||||
|         }); | ||||
|         state.socketState.isConnect = false; | ||||
|         state.socketState.tip = '当前服务已结束'; | ||||
|       } | ||||
|       state.chatList.push(formatMessage(res.data.message)); | ||||
|       // callBack && callBack()
 | ||||
|     }); | ||||
|     // 监听自定义错误 custom_error
 | ||||
|     SocketIo.on('custom_error', (error) => { | ||||
|       editCustomerServerInfo({ | ||||
|         title: error.msg, | ||||
|         state: 'offline', | ||||
|         avatar: '', | ||||
|       }); | ||||
|       console.log('custom_error:', error); | ||||
|     }); | ||||
|     // 监听错误 error
 | ||||
|     SocketIo.on('error', (error) => { | ||||
|       console.log('error:', error); | ||||
|     }); | ||||
|     // 重连失败 connect_error
 | ||||
|     SocketIo.on('connect_error', (error) => { | ||||
|       console.log('connect_error'); | ||||
|     }); | ||||
|     // 连接上,但无反应 connect_timeout
 | ||||
|     SocketIo.on('connect_timeout', (error) => { | ||||
|       console.log(error, 'connect_timeout'); | ||||
|     }); | ||||
|     // 服务进程销毁 disconnect
 | ||||
|     SocketIo.on('disconnect', (error) => { | ||||
|       console.log(error, 'disconnect'); | ||||
|     }); | ||||
|     // 服务重启重连上reconnect
 | ||||
|     SocketIo.on('reconnect', (error) => { | ||||
|       console.log(error, 'reconnect'); | ||||
|     }); | ||||
|     // 开始重连reconnect_attempt
 | ||||
|     SocketIo.on('reconnect_attempt', (error) => { | ||||
|       state.socketState.isConnect = false; | ||||
|       state.socketState.isConnecting = true; | ||||
|       editCustomerServerInfo({ | ||||
|         title: `重连中,第${error}次尝试...`, | ||||
|         state: 'waiting', | ||||
|         avatar: '', | ||||
|       }); | ||||
|       console.log(error, 'reconnect_attempt'); | ||||
|     }); | ||||
|     // 重新连接中reconnecting
 | ||||
|     SocketIo.on('reconnecting', (error) => { | ||||
|       console.log(error, 'reconnecting'); | ||||
|     }); | ||||
|     // 重新连接错误reconnect_error
 | ||||
|     SocketIo.on('reconnect_error', (error) => { | ||||
|       console.log('reconnect_error'); | ||||
|     }); | ||||
|     // 重新连接失败reconnect_failed
 | ||||
|     SocketIo.on('reconnect_failed', (error) => { | ||||
|       state.socketState.isConnecting = false; | ||||
|       editCustomerServerInfo({ | ||||
|         title: `重连失败,请刷新重试~`, | ||||
|         state: 'waiting', | ||||
|         avatar: '', | ||||
|       }); | ||||
|       console.log(error, 'reconnect_failed'); | ||||
| 
 | ||||
|       // setTimeout(() => {
 | ||||
|       state.isSendSucces = 1; | ||||
|       // }, 500)
 | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // 重置socket
 | ||||
|   const socketReset = (callBack) => { | ||||
|     state.chatList = []; | ||||
|     state.chatHistoryList = []; | ||||
|     state.chatHistoryPagination = { | ||||
|       page: 0, | ||||
|       per_page: 10, | ||||
|       last_id: 0, | ||||
|       totalPage: 0, | ||||
|     }; | ||||
|     socketConnection(callBack); // 连接
 | ||||
|   }; | ||||
| 
 | ||||
|   // 退出连接
 | ||||
|   const socketClose = () => { | ||||
|     SocketIo.emit('customer_logout', {}, (res) => { | ||||
|       console.log('socket:退出', res); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // 测试事件
 | ||||
|   const socketTest = () => { | ||||
|     SocketIo.emit('test', {}, (res) => { | ||||
|       console.log('test:test', res); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // 发送消息
 | ||||
|   const socketSendMsg = (data, sendMsgCallBack) => { | ||||
|     state.isSendSucces = -1; | ||||
|     state.chatList.push(data); | ||||
|     sendMsgCallBack && sendMsgCallBack(); | ||||
|     SocketIo.emit( | ||||
|       'message', | ||||
|       { | ||||
|         message: formatInput(data), | ||||
|         ...data.customData, | ||||
|       }, | ||||
|       (res) => { | ||||
|         // setTimeout(() => {
 | ||||
|         state.isSendSucces = res.error; | ||||
|         // }, 500)
 | ||||
| 
 | ||||
|         // console.log(res, 'socket:send');
 | ||||
|         // sendMsgCallBack && sendMsgCallBack()
 | ||||
|       }, | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   // 连接socket,存入sessionId
 | ||||
|   const socketConnection = (callBack) => { | ||||
|     SocketIo.emit( | ||||
|       'connection', | ||||
|       { | ||||
|         auth: 'user', | ||||
|         token: uni.getStorageSync('socketUserToken') || '', | ||||
|         session_id: uni.getStorageSync('socketSessionId') || '', | ||||
|       }, | ||||
|       (res) => { | ||||
|         if (res.error === 0) { | ||||
|           socketCustomerLogin(callBack); | ||||
|           uni.setStorageSync('socketSessionId', res.data.session_id); | ||||
|           // uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync(
 | ||||
|           // 	'socketUserToken')) // 如果有用户token,绑定
 | ||||
|           state.customerUserInfo = res.data.chat_user; | ||||
|           state.socketState.isConnect = true; | ||||
|         } else { | ||||
|           editCustomerServerInfo({ | ||||
|             title: `服务器异常!`, | ||||
|             state: 'waiting', | ||||
|             avatar: '', | ||||
|           }); | ||||
|           state.socketState.isConnect = false; | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   // 用户id,获取token
 | ||||
|   const getUserToken = async (id) => { | ||||
|     const res = await chat.unifiedToken(); | ||||
|     if (res.error === 0) { | ||||
|       uni.setStorageSync('socketUserToken', res.data.token); | ||||
|       // SocketIo && SocketIo.connected && socketLogin(res.data.token)
 | ||||
|     } | ||||
|     return res; | ||||
|   }; | ||||
| 
 | ||||
|   // 用户登录
 | ||||
|   const socketLogin = (token) => { | ||||
|     SocketIo.emit( | ||||
|       'login', | ||||
|       { | ||||
|         token: token, | ||||
|       }, | ||||
|       (res) => { | ||||
|         console.log(res, 'socket:login'); | ||||
|         state.customerUserInfo = res.data.chat_user; | ||||
|       }, | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   // 顾客登录
 | ||||
|   const socketCustomerLogin = (callBack) => { | ||||
|     SocketIo.emit( | ||||
|       'customer_login', | ||||
|       { | ||||
|         room_id: state.chatConfig.room_id, | ||||
|       }, | ||||
|       (res) => { | ||||
|         state.templateChatList = res.data.questions.length ? res.data.questions : []; | ||||
|         state.chatList.push({ | ||||
|           from: 'customer_service', // 用户customer右 |  顾客customer_service左 | 系统system中间
 | ||||
|           mode: 'template', // goods,order,image,text,system
 | ||||
|           date: new Date().getTime(), //时间
 | ||||
|           content: { | ||||
|             //内容
 | ||||
|             list: state.templateChatList, | ||||
|           }, | ||||
|         }); | ||||
|         res.error === 0 && socketHistoryList(callBack); | ||||
|       }, | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   // 获取历史消息
 | ||||
|   const socketHistoryList = (historyCallBack) => { | ||||
|     state.chatHistoryPagination.loadStatus = 'loading'; | ||||
|     state.chatHistoryPagination.page += 1; | ||||
|     SocketIo.emit('messages', state.chatHistoryPagination, (res) => { | ||||
|       if (res.error === 0) { | ||||
|         state.chatHistoryPagination.total = res.data.messages.total; | ||||
|         state.chatHistoryPagination.lastPage = res.data.messages.last_page; | ||||
|         state.chatHistoryPagination.page = res.data.messages.current_page; | ||||
|         res.data.messages.data.forEach((item) => { | ||||
|           item.message_type && state.chatList.unshift(formatMessage(item)); | ||||
|         }); | ||||
|         state.chatHistoryPagination.loadStatus = | ||||
|           state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage | ||||
|             ? 'loadmore' | ||||
|             : 'nomore'; | ||||
|         if (state.chatHistoryPagination.last_id == 0) { | ||||
|           state.chatHistoryPagination.last_id = res.data.messages.data.length | ||||
|             ? res.data.messages.data[0].id | ||||
|             : 0; | ||||
|         } | ||||
|         state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack(); | ||||
|       } | ||||
| 
 | ||||
|       // 历史记录之后,猜你想问
 | ||||
|       // state.chatList.push({
 | ||||
|       // 	from: 'customer_service', // 用户customer右 |  顾客customer_service左 | 系统system中间
 | ||||
|       // 	mode: 'template', // goods,order,image,text,system
 | ||||
|       // 	date: new Date().getTime(), //时间
 | ||||
|       // 	content: { //内容
 | ||||
|       // 		list: state.templateChatList
 | ||||
|       // 	}
 | ||||
|       // })
 | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // 修改客服信息
 | ||||
|   const editCustomerServerInfo = (data) => { | ||||
|     state.customerServerInfo = { | ||||
|       ...state.customerServerInfo, | ||||
|       ...data, | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * ================ | ||||
|    * 工具函数 ↓ | ||||
|    * =============== | ||||
|    */ | ||||
| 
 | ||||
|   /** | ||||
|    * 是否显示时间 | ||||
|    * @param {*} item - 数据 | ||||
|    * @param {*} index - 索引 | ||||
|    */ | ||||
|   const showTime = (item, index) => { | ||||
|     if (unref(state.chatList)[index + 1]) { | ||||
|       let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow(); | ||||
|       if (dateString === dayjs(unref(item).date).fromNow()) { | ||||
|         return false; | ||||
|       } else { | ||||
|         dateString = dayjs(unref(item).date).fromNow(); | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 格式化时间 | ||||
|    * @param {*} time - 时间戳 | ||||
|    */ | ||||
|   const formatTime = (time) => { | ||||
|     let diffTime = new Date().getTime() - time; | ||||
|     if (diffTime > 28 * 24 * 60 * 1000) { | ||||
|       return dayjs(time).format('MM/DD HH:mm'); | ||||
|     } | ||||
|     if (diffTime > 360 * 28 * 24 * 60 * 1000) { | ||||
|       return dayjs(time).format('YYYY/MM/DD HH:mm'); | ||||
|     } | ||||
|     return dayjs(time).fromNow(); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 获取焦点 | ||||
|    * @param {*} virtualNode - 节点信息 ref | ||||
|    */ | ||||
|   const getFocus = (virtualNode) => { | ||||
|     if (window.getSelection) { | ||||
|       let chatInput = unref(virtualNode); | ||||
|       chatInput.focus(); | ||||
|       let range = window.getSelection(); | ||||
|       range.selectAllChildren(chatInput); | ||||
|       range.collapseToEnd(); | ||||
|     } else if (document.selection) { | ||||
|       let range = document.selection.createRange(); | ||||
|       range.moveToElementText(chatInput); | ||||
|       range.collapse(false); | ||||
|       range.select(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 文件上传 | ||||
|    * @param {Blob} file -文件数据流 | ||||
|    * @return {path,fullPath} | ||||
|    */ | ||||
| 
 | ||||
|   const upload = (name, file) => { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       let data = new FormData(); | ||||
|       data.append('file', file, name); | ||||
|       data.append('group', 'chat'); | ||||
|       ajax({ | ||||
|         url: '/upload', | ||||
|         method: 'post', | ||||
|         headers: { | ||||
|           'Content-Type': 'multipart/form-data', | ||||
|         }, | ||||
|         data, | ||||
|         success: function (res) { | ||||
|           resolve(res); | ||||
|         }, | ||||
|         error: function (err) { | ||||
|           reject(err); | ||||
|         }, | ||||
|       }); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 粘贴到输入框 | ||||
|    * @param {*} e  - 粘贴内容 | ||||
|    * @param {*} uploadHttp - 上传图片地址 | ||||
|    */ | ||||
|   const onPaste = async (e) => { | ||||
|     let paste = e.clipboardData || window.clipboardData; | ||||
|     let filesArr = Array.from(paste.files); | ||||
|     filesArr.forEach(async (child) => { | ||||
|       if (child && child.type.includes('image')) { | ||||
|         e.preventDefault(); //阻止默认
 | ||||
|         let file = child; | ||||
|         const img = await readImg(file); | ||||
|         const blob = await compressImg(img, file.type); | ||||
|         const { data } = await upload(file.name, blob); | ||||
|         let image = `<img class="full-url" src='${data.fullurl}'>`; | ||||
|         document.execCommand('insertHTML', false, image); | ||||
|       } else { | ||||
|         document.execCommand('insertHTML', false, paste.getData('text')); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 拖拽到输入框 | ||||
|    * @param {*} e  - 粘贴内容 | ||||
|    * @param {*} uploadHttp - 上传图片地址 | ||||
|    */ | ||||
|   const onDrop = async (e) => { | ||||
|     e.preventDefault(); //阻止默认
 | ||||
|     let filesArr = Array.from(e.dataTransfer.files); | ||||
|     filesArr.forEach(async (child) => { | ||||
|       if (child && child.type.includes('image')) { | ||||
|         let file = child; | ||||
|         const img = await readImg(file); | ||||
|         const blob = await compressImg(img, file.type); | ||||
|         const { data } = await upload(file.name, blob); | ||||
|         let image = `<img class="full-url" src='${data.fullurl}' >`; | ||||
|         document.execCommand('insertHTML', false, image); | ||||
|       } else { | ||||
|         ElMessage({ | ||||
|           message: '禁止拖拽非图片资源', | ||||
|           type: 'warning', | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 解析富文本输入框内容 | ||||
|    * @param {*}  virtualNode -节点信息 | ||||
|    * @param {Function} formatInputCallBack - cb 回调 | ||||
|    */ | ||||
|   const formatChatInput = (virtualNode, formatInputCallBack) => { | ||||
|     let res = ''; | ||||
|     let elemArr = Array.from(virtualNode.childNodes); | ||||
|     elemArr.forEach((child, index) => { | ||||
|       if (child.nodeName === '#text') { | ||||
|         //如果为文本节点
 | ||||
|         res += child.nodeValue; | ||||
|         if ( | ||||
|           //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。
 | ||||
|           elemArr[index + 1] && | ||||
|           elemArr[index + 1].nodeName === 'IMG' && | ||||
|           elemArr[index + 1] && | ||||
|           elemArr[index + 1].name !== 'emoji' | ||||
|         ) { | ||||
|           const data = { | ||||
|             from: 'customer', | ||||
|             mode: 'text', | ||||
|             date: new Date().getTime(), | ||||
|             content: { | ||||
|               text: filterXSS(res), | ||||
|             }, | ||||
|           }; | ||||
|           formatInputCallBack && formatInputCallBack(data); | ||||
|           res = ''; | ||||
|         } | ||||
|       } else if (child.nodeName === 'BR') { | ||||
|         res += '<br/>'; | ||||
|       } else if (child.nodeName === 'IMG') { | ||||
|         // 有emjio 和 一般图片
 | ||||
|         // 图片解析后直接发送,不跟文字表情一组
 | ||||
|         if (child.name !== 'emoji') { | ||||
|           let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i; | ||||
|           let src = child.outerHTML.match(srcReg); | ||||
|           const data = { | ||||
|             from: 'customer', | ||||
|             mode: 'image', | ||||
|             date: new Date().getTime(), | ||||
|             content: { | ||||
|               url: src[1], | ||||
|               path: src[1].replace(/http:\/\/[^\/]*/, ''), | ||||
|             }, | ||||
|           }; | ||||
|           formatInputCallBack && formatInputCallBack(data); | ||||
|         } else { | ||||
|           // 非表情图片跟文字一起发送
 | ||||
|           res += child.outerHTML; | ||||
|         } | ||||
|       } else if (child.nodeName === 'DIV') { | ||||
|         res += `<div style='width:200px; white-space: nowrap;'>${child.outerHTML}</div>`; | ||||
|       } | ||||
|     }); | ||||
|     if (res) { | ||||
|       const data = { | ||||
|         from: 'customer', | ||||
|         mode: 'text', | ||||
|         date: new Date().getTime(), | ||||
|         content: { | ||||
|           text: filterXSS(res), | ||||
|         }, | ||||
|       }; | ||||
|       formatInputCallBack && formatInputCallBack(data); | ||||
|     } | ||||
|     unref(virtualNode).innerHTML = ''; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 状态回调 | ||||
|    * @param {*} res -接口返回数据 | ||||
|    */ | ||||
|   const callBackNotice = (res) => { | ||||
|     ElNotification({ | ||||
|       title: 'socket', | ||||
|       message: res.msg, | ||||
|       showClose: true, | ||||
|       type: res.error === 0 ? 'success' : 'warning', | ||||
|       duration: 1200, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 格式化发送信息 | ||||
|    * @param {Object} message | ||||
|    * @returns  obj - 消息对象 | ||||
|    */ | ||||
|   const formatInput = (message) => { | ||||
|     let obj = {}; | ||||
|     switch (message.mode) { | ||||
|       case 'text': | ||||
|         obj = { | ||||
|           message_type: 'text', | ||||
|           message: message.content.text, | ||||
|         }; | ||||
|         break; | ||||
|       case 'image': | ||||
|         obj = { | ||||
|           message_type: 'image', | ||||
|           message: message.content.path, | ||||
|         }; | ||||
|         break; | ||||
|       case 'goods': | ||||
|         obj = { | ||||
|           message_type: 'goods', | ||||
|           message: message.content.item, | ||||
|         }; | ||||
|         break; | ||||
|       case 'order': | ||||
|         obj = { | ||||
|           message_type: 'order', | ||||
|           message: message.content.item, | ||||
|         }; | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|     return obj; | ||||
|   }; | ||||
|   /** | ||||
|    * 格式化接收信息 | ||||
|    * @param {*} message | ||||
|    * @returns obj - 消息对象 | ||||
|    */ | ||||
|   const formatMessage = (message) => { | ||||
|     let obj = {}; | ||||
|     switch (message.message_type) { | ||||
|       case 'system': | ||||
|         obj = { | ||||
|           from: 'system', // 用户customer左 |  顾客customer_service右 | 系统system中间
 | ||||
|           mode: 'system', // goods,order,image,text,system
 | ||||
|           date: message.create_time * 1000, //时间
 | ||||
|           content: { | ||||
|             //内容
 | ||||
|             text: message.message, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       case 'text': | ||||
|         obj = { | ||||
|           from: message.sender_identify, | ||||
|           mode: message.message_type, | ||||
|           date: message.create_time * 1000, //时间
 | ||||
|           sender: message.sender, | ||||
|           content: { | ||||
|             text: message.message, | ||||
|             messageId: message.id, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       case 'image': | ||||
|         obj = { | ||||
|           from: message.sender_identify, | ||||
|           mode: message.message_type, | ||||
|           date: message.create_time * 1000, //时间
 | ||||
|           sender: message.sender, | ||||
|           content: { | ||||
|             url: sheep.$url.cdn(message.message), | ||||
|             messageId: message.id, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       case 'goods': | ||||
|         obj = { | ||||
|           from: message.sender_identify, | ||||
|           mode: message.message_type, | ||||
|           date: message.create_time * 1000, //时间
 | ||||
|           sender: message.sender, | ||||
|           content: { | ||||
|             item: message.message, | ||||
|             messageId: message.id, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       case 'order': | ||||
|         obj = { | ||||
|           from: message.sender_identify, | ||||
|           mode: message.message_type, | ||||
|           date: message.create_time * 1000, //时间
 | ||||
|           sender: message.sender, | ||||
|           content: { | ||||
|             item: message.message, | ||||
|             messageId: message.id, | ||||
|           }, | ||||
|         }; | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|     return obj; | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * file 转换为 img | ||||
|    * @param {*} file  - file 文件 | ||||
|    * @returns  img   - img标签 | ||||
|    */ | ||||
|   const readImg = (file) => { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       const img = new Image(); | ||||
|       const reader = new FileReader(); | ||||
|       reader.onload = function (e) { | ||||
|         img.src = e.target.result; | ||||
|       }; | ||||
|       reader.onerror = function (e) { | ||||
|         reject(e); | ||||
|       }; | ||||
|       reader.readAsDataURL(file); | ||||
|       img.onload = function () { | ||||
|         resolve(img); | ||||
|       }; | ||||
|       img.onerror = function (e) { | ||||
|         reject(e); | ||||
|       }; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 压缩图片 | ||||
|    *@param img -被压缩的img对象 | ||||
|    * @param type -压缩后转换的文件类型 | ||||
|    * @param mx -触发压缩的图片最大宽度限制 | ||||
|    * @param mh -触发压缩的图片最大高度限制 | ||||
|    * @returns blob - 文件流 | ||||
|    */ | ||||
|   const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       const canvas = document.createElement('canvas'); | ||||
|       const context = canvas.getContext('2d'); | ||||
|       const { width: originWidth, height: originHeight } = img; | ||||
|       // 最大尺寸限制
 | ||||
|       const maxWidth = mx; | ||||
|       const maxHeight = mh; | ||||
|       // 目标尺寸
 | ||||
|       let targetWidth = originWidth; | ||||
|       let targetHeight = originHeight; | ||||
|       if (originWidth > maxWidth || originHeight > maxHeight) { | ||||
|         if (originWidth / originHeight > 1) { | ||||
|           // 宽图片
 | ||||
|           targetWidth = maxWidth; | ||||
|           targetHeight = Math.round(maxWidth * (originHeight / originWidth)); | ||||
|         } else { | ||||
|           // 高图片
 | ||||
|           targetHeight = maxHeight; | ||||
|           targetWidth = Math.round(maxHeight * (originWidth / originHeight)); | ||||
|         } | ||||
|       } | ||||
|       canvas.width = targetWidth; | ||||
|       canvas.height = targetHeight; | ||||
|       context.clearRect(0, 0, targetWidth, targetHeight); | ||||
|       // 图片绘制
 | ||||
|       context.drawImage(img, 0, 0, targetWidth, targetHeight); | ||||
|       canvas.toBlob( | ||||
|         function (blob) { | ||||
|           resolve(blob); | ||||
|         }, | ||||
|         type, | ||||
|         quality, | ||||
|       ); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   return { | ||||
|     compressImg, | ||||
|     readImg, | ||||
|     formatMessage, | ||||
|     formatInput, | ||||
|     callBackNotice, | ||||
| 
 | ||||
|     socketInit, | ||||
|     socketSendMsg, | ||||
|     socketClose, | ||||
|     socketHistoryList, | ||||
| 
 | ||||
|     getFocus, | ||||
|     formatChatInput, | ||||
|     onDrop, | ||||
|     onPaste, | ||||
|     upload, | ||||
| 
 | ||||
|     getUserToken, | ||||
| 
 | ||||
|     state, | ||||
| 
 | ||||
|     socketTest, | ||||
| 
 | ||||
|     showTime, | ||||
|     formatTime, | ||||
|   }; | ||||
| } | ||||
|  | @ -1,19 +0,0 @@ | |||
| export const KeFuMessageContentTypeEnum = { | ||||
|   TEXT: 1, // 文本消息
 | ||||
|   IMAGE: 2, // 图片消息
 | ||||
|   VOICE: 3, // 语音消息
 | ||||
|   VIDEO: 4, // 视频消息
 | ||||
|   SYSTEM: 5, // 系统消息
 | ||||
|   // ========== 商城特殊消息 ==========
 | ||||
|   PRODUCT: 10,//  商品消息
 | ||||
|   ORDER: 11,//  订单消息"
 | ||||
| }; | ||||
| export const UserTypeEnum = { | ||||
|   MEMBER: 1, // 会员 面向 c 端,普通用户
 | ||||
|   ADMIN: 2, // 管理员 面向 b 端,管理后台
 | ||||
| }; | ||||
| // Promotion 的 WebSocket 消息类型枚举类
 | ||||
| export const WebSocketMessageTypeConstants = { | ||||
|   KEFU_MESSAGE_TYPE: 'kefu_message_type', // 客服消息类型
 | ||||
|   KEFU_MESSAGE_ADMIN_READ: 'kefu_message_read_status_change' // 客服消息管理员已读
 | ||||
| } | ||||
|  | @ -1,150 +0,0 @@ | |||
| import { onBeforeUnmount, reactive, ref } from 'vue'; | ||||
| import { baseUrl, websocketPath } from '@/sheep/config'; | ||||
| import { copyValueToTarget } from '@/sheep/helper/utils'; | ||||
| import { getRefreshToken } from '@/sheep/request'; | ||||
| 
 | ||||
| /** | ||||
|  * WebSocket 创建 hook | ||||
|  * @param opt 连接配置 | ||||
|  * @return {{options: *}} | ||||
|  */ | ||||
| export function useWebSocket(opt) { | ||||
|   const options = reactive({ | ||||
|     url: (baseUrl + websocketPath).replace('http', 'ws') + '?token=' + getRefreshToken(), // ws 地址
 | ||||
|     isReconnecting: false, // 正在重新连接
 | ||||
|     reconnectInterval: 3000, // 重连间隔,单位毫秒
 | ||||
|     heartBeatInterval: 5000, // 心跳间隔,单位毫秒
 | ||||
|     pingTimeoutDuration: 1000, // 超过这个时间,后端没有返回pong,则判定后端断线了。
 | ||||
|     heartBeatTimer: null, // 心跳计时器
 | ||||
|     destroy: false, // 是否销毁
 | ||||
|     pingTimeout: null, // 心跳检测定时器
 | ||||
|     reconnectTimeout: null, // 重连定时器ID的属性
 | ||||
|     onConnected: () => {}, // 连接成功时触发
 | ||||
|     onClosed: () => {}, // 连接关闭时触发
 | ||||
|     onMessage: (data) => {}, // 收到消息
 | ||||
|   }); | ||||
|   const SocketTask = ref(null); // SocketTask 由 uni.connectSocket() 接口创建
 | ||||
| 
 | ||||
|   const initEventListeners = () => { | ||||
|     // 监听 WebSocket 连接打开事件
 | ||||
|     SocketTask.value.onOpen(() => { | ||||
|       console.log('WebSocket 连接成功'); | ||||
|       // 连接成功时触发
 | ||||
|       options.onConnected(); | ||||
|       // 开启心跳检查
 | ||||
|       startHeartBeat(); | ||||
|     }); | ||||
|     // 监听 WebSocket 接受到服务器的消息事件
 | ||||
|     SocketTask.value.onMessage((res) => { | ||||
|       try { | ||||
|         if (res.data === 'pong') { | ||||
|           // 收到心跳重置心跳超时检查
 | ||||
|           resetPingTimeout(); | ||||
|         } else { | ||||
|           options.onMessage(JSON.parse(res.data)); | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.error(error); | ||||
|       } | ||||
|     }); | ||||
|     // 监听 WebSocket 连接关闭事件
 | ||||
|     SocketTask.value.onClose((event) => { | ||||
|       // 情况一:实例销毁
 | ||||
|       if (options.destroy) { | ||||
|         options.onClosed(); | ||||
|       } else { | ||||
|         // 情况二:连接失败重连
 | ||||
|         // 停止心跳检查
 | ||||
|         stopHeartBeat(); | ||||
|         // 重连
 | ||||
|         reconnect(); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   // 发送消息
 | ||||
|   const sendMessage = (message) => { | ||||
|     if (SocketTask.value && !options.destroy) { | ||||
|       SocketTask.value.send({ data: message }); | ||||
|     } | ||||
|   }; | ||||
|   // 开始心跳检查
 | ||||
|   const startHeartBeat = () => { | ||||
|     options.heartBeatTimer = setInterval(() => { | ||||
|       sendMessage('ping'); | ||||
|       options.pingTimeout = setTimeout(() => { | ||||
|         // 如果在超时时间内没有收到 pong,则认为连接断开
 | ||||
|         reconnect(); | ||||
|       }, options.pingTimeoutDuration); | ||||
|     }, options.heartBeatInterval); | ||||
|   }; | ||||
|   // 停止心跳检查
 | ||||
|   const stopHeartBeat = () => { | ||||
|     clearInterval(options.heartBeatTimer); | ||||
|     resetPingTimeout(); | ||||
|   }; | ||||
| 
 | ||||
|   // WebSocket 重连
 | ||||
|   const reconnect = () => { | ||||
|     if (options.destroy || !SocketTask.value) { | ||||
|       // 如果WebSocket已被销毁或尚未完全关闭,不进行重连
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 重连中
 | ||||
|     options.isReconnecting = true; | ||||
| 
 | ||||
|     // 清除现有的重连标志,以避免多次重连
 | ||||
|     if (options.reconnectTimeout) { | ||||
|       clearTimeout(options.reconnectTimeout); | ||||
|     } | ||||
| 
 | ||||
|     // 设置重连延迟
 | ||||
|     options.reconnectTimeout = setTimeout(() => { | ||||
|       // 检查组件是否仍在运行和WebSocket是否关闭
 | ||||
|       if (!options.destroy) { | ||||
|         // 重置重连标志
 | ||||
|         options.isReconnecting = false; | ||||
|         // 初始化新的WebSocket连接
 | ||||
|         initSocket(); | ||||
|       } | ||||
|     }, options.reconnectInterval); | ||||
|   }; | ||||
| 
 | ||||
|   const resetPingTimeout = () => { | ||||
|     if (options.pingTimeout) { | ||||
|       clearTimeout(options.pingTimeout); | ||||
|       options.pingTimeout = null; // 清除超时ID
 | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const close = () => { | ||||
|     options.destroy = true; | ||||
|     stopHeartBeat(); | ||||
|     if (options.reconnectTimeout) { | ||||
|       clearTimeout(options.reconnectTimeout); | ||||
|     } | ||||
|     if (SocketTask.value) { | ||||
|       SocketTask.value.close(); | ||||
|       SocketTask.value = null; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const initSocket = () => { | ||||
|     options.destroy = false; | ||||
|     copyValueToTarget(options, opt); | ||||
|     SocketTask.value = uni.connectSocket({ | ||||
|       url: options.url, | ||||
|       complete: () => {}, | ||||
|       success: () => {}, | ||||
|     }); | ||||
|     initEventListeners(); | ||||
|   }; | ||||
| 
 | ||||
|   initSocket(); | ||||
| 
 | ||||
|   onBeforeUnmount(() => { | ||||
|     close(); | ||||
|   }); | ||||
|   return { options }; | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -16,7 +16,7 @@ | |||
|             /> | ||||
|           </button> | ||||
|         </view> | ||||
|         <view class="ss-flex" @tap="sheep.$router.go('/pages/commission/wallet')"> | ||||
|         <view class="ss-flex" @tap="sheep.$router.go('/pages/user/wallet/commission')"> | ||||
|           <view class="header-title ss-m-r-4">查看明细</view> | ||||
|           <text class="cicon-play-arrow" /> | ||||
|         </view> | ||||
|  |  | |||
|  | @ -45,8 +45,7 @@ | |||
|       type: Boolean, | ||||
|       default: false, | ||||
|     }, | ||||
|     methods: { | ||||
|       // 开启的提现方式 | ||||
|     methods: { // 开启的提现方式 | ||||
|       type: Array, | ||||
|       default: [], | ||||
|     }, | ||||
|  | @ -58,35 +57,25 @@ | |||
| 
 | ||||
|   const typeList = [ | ||||
|     { | ||||
|       icon: '/static/img/shop/pay/wallet.png', | ||||
|       // icon: '/static/img/shop/pay/wechat.png', // TODO 芋艿:后续给个 icon | ||||
|       title: '钱包余额', | ||||
|       value: '1', | ||||
|     }, | ||||
|     { | ||||
|       icon: '/static/img/shop/pay/bank.png', | ||||
|       title: '银行卡转账', | ||||
|       icon: '/static/img/shop/pay/wechat.png', | ||||
|       title: '微信零钱', | ||||
|       value: '2', | ||||
|     }, | ||||
|     { | ||||
|       icon: '/static/img/shop/pay/wechat.png', | ||||
|       title: '微信收款码', // 微信手动转账 | ||||
|       icon: '/static/img/shop/pay/alipay.png', | ||||
|       title: '支付宝账户', | ||||
|       value: '3', | ||||
|     }, | ||||
|     { | ||||
|       icon: '/static/img/shop/pay/alipay.png', | ||||
|       title: '支付宝收款码', // 支付宝手动转账 | ||||
|       icon: '/static/img/shop/pay/bank.png', | ||||
|       title: '银行卡转账', | ||||
|       value: '4', | ||||
|     }, | ||||
|     { | ||||
|       icon: '/static/img/shop/pay/wechat_api.png', | ||||
|       title: '微信零钱', // 微信 API 转账 | ||||
|       value: '5', | ||||
|     }, | ||||
|     { | ||||
|       icon: '/static/img/shop/pay/alipay_api.png', | ||||
|       title: '支付宝余额', // 支付宝 API 转账 | ||||
|       value: '6', | ||||
|     }, | ||||
|   ]; | ||||
| 
 | ||||
|   function onChange(e) { | ||||
|  | @ -100,7 +89,7 @@ | |||
|     } | ||||
|     // 赋值 | ||||
|     emits('update:modelValue', { | ||||
|       type: state.currentValue, | ||||
|       type: state.currentValue | ||||
|     }); | ||||
|     // 关闭弹窗 | ||||
|     emits('close'); | ||||
|  |  | |||
|  | @ -31,10 +31,9 @@ | |||
| <style lang="scss" scoped> | ||||
| 	// 用户资料卡片 | ||||
| 	.user-card { | ||||
| 		width: 700rpx; | ||||
| 		width: 690rpx; | ||||
| 		height: 192rpx; | ||||
| 		margin: 0 auto; | ||||
| 		margin-top: -88rpx; | ||||
| 		margin: -88rpx 20rpx 0 20rpx; | ||||
| 		padding-top: 88rpx; | ||||
| 		background: v-bind(headerBg) no-repeat; | ||||
| 		background-size: 100% 100%; | ||||
|  | @ -111,4 +110,4 @@ | |||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
| </style> | ||||
|  | @ -1,90 +1,74 @@ | |||
| <!-- 分销首页:明细列表  --> | ||||
| <template> | ||||
|   <view class="distribution-log-wrap"> | ||||
|     <view class="header-box"> | ||||
|       <image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title2.png')" /> | ||||
|       <view class="ss-flex header-title"> | ||||
|         <view class="title">实时动态</view> | ||||
|         <text class="cicon-forward" /> | ||||
|       </view> | ||||
|     </view> | ||||
|     <scroll-view | ||||
|       scroll-y="true" | ||||
|       @scrolltolower="loadmore" | ||||
|       class="scroll-box log-scroll" | ||||
|       scroll-with-animation="true" | ||||
|     > | ||||
|       <view v-if="state.pagination.list"> | ||||
|         <view | ||||
|           class="log-item-box ss-flex ss-row-between" | ||||
|           v-for="item in state.pagination.list" | ||||
|           :key="item.id" | ||||
|         > | ||||
|           <view class="log-item-wrap"> | ||||
|             <view class="log-item ss-flex ss-ellipsis-1 ss-col-center"> | ||||
|               <view class="ss-flex ss-col-center"> | ||||
|                 <image | ||||
|                   class="log-img" | ||||
|                   :src="sheep.$url.static('/static/img/shop/avatar/notice.png')" | ||||
|                   mode="aspectFill" | ||||
|                 /> | ||||
|               </view> | ||||
|               <view class="log-text ss-ellipsis-1"> | ||||
| 	<view class="distribution-log-wrap"> | ||||
| 		<view class="header-box"> | ||||
| 			<image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title2.png')" /> | ||||
| 			<view class="ss-flex header-title"> | ||||
| 				<view class="title">实时动态</view> | ||||
| 				<text class="cicon-forward" /> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 		<scroll-view scroll-y="true" @scrolltolower="loadmore" class="scroll-box log-scroll" | ||||
| 			scroll-with-animation="true"> | ||||
| 			<view v-if="state.pagination.list"> | ||||
| 				<view class="log-item-box ss-flex ss-row-between" v-for="item in state.pagination.list" :key="item.id"> | ||||
| 					<view class="log-item-wrap"> | ||||
| 						<view class="log-item ss-flex ss-ellipsis-1 ss-col-center"> | ||||
| 							<view class="ss-flex ss-col-center"> | ||||
| 								<image class="log-img" :src="sheep.$url.static('/static/img/shop/avatar/notice.png')" mode="aspectFill" /> | ||||
| 							</view> | ||||
| 							<view class="log-text ss-ellipsis-1"> | ||||
|                 {{ item.title }} {{ fen2yuan(item.price) }} 元 | ||||
|               </view> | ||||
|             </view> | ||||
|           </view> | ||||
|           <text class="log-time">{{ dayjs(item.createTime).fromNow() }}</text> | ||||
|         </view> | ||||
|       </view> | ||||
| 						</view> | ||||
| 					</view> | ||||
| 					<text class="log-time">{{ dayjs(item.createTime).fromNow() }}</text> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 
 | ||||
|       <!-- 加载更多 --> | ||||
|       <uni-load-more | ||||
|         v-if="state.pagination.total > 0" | ||||
|         :status="state.loadStatus" | ||||
|         color="#333333" | ||||
|         @tap="loadmore" | ||||
|       /> | ||||
|     </scroll-view> | ||||
|   </view> | ||||
| 			<!-- 加载更多 --> | ||||
| 			<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" color="#333333" | ||||
| 				@tap="loadmore" /> | ||||
| 		</scroll-view> | ||||
| 	</view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import dayjs from 'dayjs'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 	import _ from 'lodash'; | ||||
| 	import dayjs from 'dayjs'; | ||||
|   import BrokerageApi from '@/sheep/api/trade/brokerage'; | ||||
|   import { fen2yuan } from '../../../sheep/hooks/useGoods'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     loadStatus: '', | ||||
|     pagination: { | ||||
| 	const state = reactive({ | ||||
| 		loadStatus: '', | ||||
| 		pagination: { | ||||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 8, | ||||
|     }, | ||||
|   }); | ||||
|       pageSize: 1, | ||||
| 		}, | ||||
| 	}); | ||||
| 
 | ||||
|   async function getLog() { | ||||
| 	async function getLog() { | ||||
|     state.loadStatus = 'loading'; | ||||
|     const { code, data } = await BrokerageApi.getBrokerageRecordPage({ | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|     }); | ||||
|       pageSize: state.pagination.pageSize | ||||
| 		}); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   getLog(); | ||||
| 	getLog(); | ||||
| 
 | ||||
|   // 加载更多 | ||||
|   function loadmore() { | ||||
| 	// 加载更多 | ||||
| 	function loadmore() { | ||||
|     if (state.loadStatus === 'noMore') { | ||||
|       return; | ||||
|     } | ||||
|  | @ -94,88 +78,88 @@ | |||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .distribution-log-wrap { | ||||
|     width: 690rpx; | ||||
|     margin: 0 auto; | ||||
|     margin-bottom: 20rpx; | ||||
|     border-radius: 12rpx; | ||||
|     z-index: 3; | ||||
|     position: relative; | ||||
| 	.distribution-log-wrap { | ||||
| 		width: 690rpx; | ||||
| 		margin: 0 auto; | ||||
| 		margin-bottom: 20rpx; | ||||
| 		border-radius: 12rpx; | ||||
| 		z-index: 3; | ||||
| 		position: relative; | ||||
| 
 | ||||
|     .header-box { | ||||
|       width: 690rpx; | ||||
|       height: 76rpx; | ||||
|       position: relative; | ||||
| 		.header-box { | ||||
| 			width: 690rpx; | ||||
| 			height: 76rpx; | ||||
| 			position: relative; | ||||
| 
 | ||||
|       .header-bg { | ||||
|         width: 690rpx; | ||||
|         height: 76rpx; | ||||
|       } | ||||
| 			.header-bg { | ||||
| 				width: 690rpx; | ||||
| 				height: 76rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .header-title { | ||||
|         position: absolute; | ||||
|         left: 20rpx; | ||||
|         top: 24rpx; | ||||
|       } | ||||
| 			.header-title { | ||||
| 				position: absolute; | ||||
| 				left: 20rpx; | ||||
| 				top: 24rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .title { | ||||
|         font-size: 28rpx; | ||||
|         font-weight: 500; | ||||
|         color: #ffffff; | ||||
|         line-height: 30rpx; | ||||
|       } | ||||
| 			.title { | ||||
| 				font-size: 28rpx; | ||||
| 				font-weight: 500; | ||||
| 				color: #ffffff; | ||||
| 				line-height: 30rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .cicon-forward { | ||||
|         font-size: 30rpx; | ||||
|         font-weight: 400; | ||||
|         color: #ffffff; | ||||
|         line-height: 30rpx; | ||||
|       } | ||||
|     } | ||||
| 			.cicon-forward { | ||||
| 				font-size: 30rpx; | ||||
| 				font-weight: 400; | ||||
| 				color: #ffffff; | ||||
| 				line-height: 30rpx; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|     .log-scroll { | ||||
|       height: 600rpx; | ||||
|       background: #fdfae9; | ||||
|       padding: 10rpx 20rpx 0; | ||||
|       box-sizing: border-box; | ||||
|       border-radius: 0 0 12rpx 12rpx; | ||||
| 		.log-scroll { | ||||
| 			height: 600rpx; | ||||
| 			background: #fdfae9; | ||||
| 			padding: 10rpx 20rpx 0; | ||||
| 			box-sizing: border-box; | ||||
| 			border-radius: 0 0 12rpx 12rpx; | ||||
| 
 | ||||
|       .log-item-box { | ||||
|         margin-bottom: 20rpx; | ||||
| 			.log-item-box { | ||||
| 				margin-bottom: 20rpx; | ||||
| 
 | ||||
|         .log-time { | ||||
|           // margin-left: 30rpx; | ||||
|           text-align: right; | ||||
|           font-size: 24rpx; | ||||
|           font-family: OPPOSANS; | ||||
|           font-weight: 400; | ||||
|           color: #c4c4c4; | ||||
|         } | ||||
|       } | ||||
| 				.log-time { | ||||
| 					// margin-left: 30rpx; | ||||
| 					text-align: right; | ||||
| 					font-size: 24rpx; | ||||
| 					font-family: OPPOSANS; | ||||
| 					font-weight: 400; | ||||
| 					color: #c4c4c4; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|       .loadmore-wrap { | ||||
|         // line-height: 80rpx; | ||||
|       } | ||||
| 			.loadmore-wrap { | ||||
| 				// line-height: 80rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .log-item { | ||||
|         // background: rgba(#ffffff, 0.2); | ||||
|         border-radius: 24rpx; | ||||
|         padding: 6rpx 20rpx 6rpx 12rpx; | ||||
| 			.log-item { | ||||
| 				// background: rgba(#ffffff, 0.2); | ||||
| 				border-radius: 24rpx; | ||||
| 				padding: 6rpx 20rpx 6rpx 12rpx; | ||||
| 
 | ||||
|         .log-img { | ||||
|           width: 40rpx; | ||||
|           height: 40rpx; | ||||
|           border-radius: 50%; | ||||
|           margin-right: 10rpx; | ||||
|         } | ||||
| 				.log-img { | ||||
| 					width: 40rpx; | ||||
| 					height: 40rpx; | ||||
| 					border-radius: 50%; | ||||
| 					margin-right: 10rpx; | ||||
| 				} | ||||
| 
 | ||||
|         .log-text { | ||||
|           max-width: 480rpx; | ||||
|           font-size: 24rpx; | ||||
|           font-weight: 500; | ||||
|           color: #333333; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 				.log-text { | ||||
| 					max-width: 480rpx; | ||||
| 					font-size: 24rpx; | ||||
| 					font-weight: 500; | ||||
| 					color: #333333; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,145 +1,138 @@ | |||
| <!-- 分销:商菜单栏 --> | ||||
| <template> | ||||
|   <view class="menu-box ss-flex-col"> | ||||
|     <view class="header-box"> | ||||
|       <image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title1.png')" /> | ||||
|       <view class="ss-flex header-title"> | ||||
|         <view class="title">功能专区</view> | ||||
|         <text class="cicon-forward"></text> | ||||
|       </view> | ||||
|     </view> | ||||
|     <view class="menu-list ss-flex ss-flex-wrap"> | ||||
|       <view | ||||
|         v-for="(item, index) in state.menuList" | ||||
|         :key="index" | ||||
|         class="item-box ss-flex-col ss-col-center" | ||||
|         @tap="sheep.$router.go(item.path)" | ||||
|       > | ||||
|         <image | ||||
|           class="menu-icon ss-m-b-10" | ||||
|           :src="sheep.$url.static(item.img)" | ||||
|           mode="aspectFill" | ||||
|         ></image> | ||||
|         <view>{{ item.title }}</view> | ||||
|       </view> | ||||
|     </view> | ||||
|   </view> | ||||
| 	<view class="menu-box ss-flex-col"> | ||||
| 		<view class="header-box"> | ||||
| 			<image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title1.png')" /> | ||||
| 			<view class="ss-flex header-title"> | ||||
| 				<view class="title">功能专区</view> | ||||
| 				<text class="cicon-forward"></text> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 		<view class="menu-list ss-flex ss-flex-wrap"> | ||||
| 			<view v-for="(item, index) in state.menuList" :key="index" class="item-box ss-flex-col ss-col-center" | ||||
| 				@tap="sheep.$router.go(item.path)"> | ||||
| 				<image class="menu-icon ss-m-b-10" :src="sheep.$url.static(item.img)" mode="aspectFill"></image> | ||||
| 				<view>{{ item.title }}</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 	</view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { reactive } from 'vue'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     menuList: [ | ||||
| 	const state = reactive({ | ||||
| 		menuList: [{ | ||||
| 				img: '/static/img/shop/commission/commission_icon1.png', | ||||
| 				title: '我的团队', | ||||
| 				path: '/pages/commission/team', | ||||
| 			}, | ||||
| 			{ | ||||
| 				img: '/static/img/shop/commission/commission_icon2.png', | ||||
| 				title: '佣金明细', | ||||
| 				path: '/pages/commission/wallet', | ||||
| 			}, | ||||
| 			{ | ||||
| 				img: '/static/img/shop/commission/commission_icon3.png', | ||||
| 				title: '分销订单', | ||||
| 				path: '/pages/commission/order', | ||||
| 			}, | ||||
| 			{ | ||||
| 				img: '/static/img/shop/commission/commission_icon4.png', | ||||
| 				title: '推广商品', | ||||
| 				path: '/pages/commission/goods', | ||||
| 			}, | ||||
| 			// { | ||||
| 			//   img: '/static/img/shop/commission/commission_icon5.png', | ||||
| 			//   title: '我的资料', | ||||
| 			//   path: '/pages/commission/apply', | ||||
| 			//   isAgentFrom: true, | ||||
| 			// }, | ||||
| 			// todo @芋艿:邀请海报需要登录后的个人数据 | ||||
| 			{ | ||||
| 				img: '/static/img/shop/commission/commission_icon7.png', | ||||
| 				title: '邀请海报', | ||||
| 				path: 'action:showShareModal', | ||||
| 			}, | ||||
|       // TODO @芋艿:缺少 icon | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon1.png', | ||||
|         title: '我的团队', | ||||
|         path: '/pages/commission/team', | ||||
|       }, | ||||
| 				// img: '/static/img/shop/commission/commission_icon7.png', | ||||
| 				title: '推广排行', | ||||
| 				path: '/pages/commission/promoter', | ||||
| 			}, | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon2.png', | ||||
|         title: '佣金明细', | ||||
|         path: '/pages/commission/wallet', | ||||
|       }, | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon3.png', | ||||
|         title: '分销订单', | ||||
|         path: '/pages/commission/order', | ||||
|       }, | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon4.png', | ||||
|         title: '推广商品', | ||||
|         path: '/pages/commission/goods', | ||||
|       }, | ||||
|       // { | ||||
|       //   img: '/static/img/shop/commission/commission_icon5.png', | ||||
|       //   title: '我的资料', | ||||
|       //   path: '/pages/commission/apply', | ||||
|       //   isAgentFrom: true, | ||||
|       // }, | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon7.png', | ||||
|         title: '邀请海报', | ||||
|         path: 'action:showShareModal', | ||||
|       }, | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon8.png', | ||||
|         title: '推广排行', | ||||
|         path: '/pages/commission/promoter', | ||||
|       }, | ||||
|       { | ||||
|         img: '/static/img/shop/commission/commission_icon9.png', | ||||
|         title: '佣金排行', | ||||
|         path: '/pages/commission/commission-ranking', | ||||
|       }, | ||||
|     ], | ||||
|   }); | ||||
| 				// img: '/static/img/shop/commission/commission_icon7.png', | ||||
| 				title: '佣金排行', | ||||
| 				path: '/pages/commission/commission-ranking', | ||||
| 			} | ||||
| 		], | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .menu-box { | ||||
|     margin: 0 auto; | ||||
|     width: 690rpx; | ||||
|     margin-bottom: 20rpx; | ||||
|     margin-top: 20rpx; | ||||
|     border-radius: 12rpx; | ||||
|     z-index: 3; | ||||
|     position: relative; | ||||
|   } | ||||
| 	.menu-box { | ||||
| 		margin: 0 auto; | ||||
| 		width: 690rpx; | ||||
| 		margin-bottom: 20rpx; | ||||
| 		margin-top: 20rpx; | ||||
| 		border-radius: 12rpx; | ||||
| 		z-index: 3; | ||||
| 		position: relative; | ||||
| 	} | ||||
| 
 | ||||
|   .header-box { | ||||
|     width: 690rpx; | ||||
|     height: 76rpx; | ||||
|     position: relative; | ||||
| 	.header-box { | ||||
| 		width: 690rpx; | ||||
| 		height: 76rpx; | ||||
| 		position: relative; | ||||
| 
 | ||||
|     .header-bg { | ||||
|       width: 690rpx; | ||||
|       height: 76rpx; | ||||
|     } | ||||
| 		.header-bg { | ||||
| 			width: 690rpx; | ||||
| 			height: 76rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .header-title { | ||||
|       position: absolute; | ||||
|       left: 20rpx; | ||||
|       top: 24rpx; | ||||
|     } | ||||
| 		.header-title { | ||||
| 			position: absolute; | ||||
| 			left: 20rpx; | ||||
| 			top: 24rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .title { | ||||
|       font-size: 28rpx; | ||||
|       font-weight: 500; | ||||
|       color: #ffffff; | ||||
|       line-height: 30rpx; | ||||
|     } | ||||
| 		.title { | ||||
| 			font-size: 28rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: #ffffff; | ||||
| 			line-height: 30rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .cicon-forward { | ||||
|       font-size: 30rpx; | ||||
|       font-weight: 400; | ||||
|       color: #ffffff; | ||||
|       line-height: 30rpx; | ||||
|     } | ||||
|   } | ||||
| 		.cicon-forward { | ||||
| 			font-size: 30rpx; | ||||
| 			font-weight: 400; | ||||
| 			color: #ffffff; | ||||
| 			line-height: 30rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .menu-list { | ||||
|     padding: 50rpx 0 10rpx 0; | ||||
|     background: #fdfae9; | ||||
|     border-radius: 0 0 12rpx 12rpx; | ||||
|   } | ||||
| 	.menu-list { | ||||
| 		padding: 50rpx 0 10rpx 0; | ||||
| 		background: #fdfae9; | ||||
| 		border-radius: 0 0 12rpx 12rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .item-box { | ||||
|     width: 25%; | ||||
|     margin-bottom: 40rpx; | ||||
|   } | ||||
| 	.item-box { | ||||
| 		width: 25%; | ||||
| 		margin-bottom: 40rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .menu-icon { | ||||
|     width: 68rpx; | ||||
|     height: 68rpx; | ||||
|     background: #ffffff; | ||||
|     border-radius: 50%; | ||||
|   } | ||||
| 	.menu-icon { | ||||
| 		width: 68rpx; | ||||
| 		height: 68rpx; | ||||
| 		background: #ffffff; | ||||
| 		border-radius: 50%; | ||||
| 	} | ||||
| 
 | ||||
|   .menu-title { | ||||
|     font-size: 26rpx; | ||||
|     font-weight: 500; | ||||
|     color: #ffffff; | ||||
|   } | ||||
| </style> | ||||
| 	.menu-title { | ||||
| 		font-size: 26rpx; | ||||
| 		font-weight: 500; | ||||
| 		color: #ffffff; | ||||
| 	} | ||||
| </style> | ||||
|  | @ -14,18 +14,12 @@ | |||
|       > | ||||
|         <template #rightBottom> | ||||
|           <view class="ss-flex ss-row-between"> | ||||
|             <view class="commission-num" v-if="item.brokerageMinPrice === undefined"> | ||||
|               预计佣金:计算中 | ||||
|             </view> | ||||
|             <view | ||||
|               class="commission-num" | ||||
|               v-else-if="item.brokerageMinPrice === item.brokerageMaxPrice" | ||||
|             > | ||||
|             <view class="commission-num" v-if="item.brokerageMinPrice === undefined">预计佣金:计算中</view> | ||||
|             <view class="commission-num" v-else-if="item.brokerageMinPrice === item.brokerageMaxPrice"> | ||||
|               预计佣金:{{ fen2yuan(item.brokerageMinPrice) }} | ||||
|             </view> | ||||
|             <view class="commission-num" v-else> | ||||
|               预计佣金:{{ fen2yuan(item.brokerageMinPrice) }} ~ | ||||
|               {{ fen2yuan(item.brokerageMaxPrice) }} | ||||
|               预计佣金:{{ fen2yuan(item.brokerageMinPrice) }} ~ {{ fen2yuan(item.brokerageMaxPrice) }} | ||||
|             </view> | ||||
|             <button | ||||
|               class="ss-reset-button share-btn ui-BG-Main-Gradient" | ||||
|  | @ -59,29 +53,30 @@ | |||
|   import $share from '@/sheep/platform/share'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import { showShareModal } from '@/sheep/hooks/useModal'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import BrokerageApi from '@/sheep/api/trade/brokerage'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import { fen2yuan } from '../../sheep/hooks/useGoods'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     pagination: { | ||||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 8, | ||||
|       pageSize: 1, | ||||
|     }, | ||||
|     loadStatus: '', | ||||
|     shareInfo: {}, | ||||
|   }); | ||||
| 
 | ||||
|   // TODO 芋艿:分享的接入 | ||||
|   function onShareGoods(goodsInfo) { | ||||
|     state.shareInfo = $share.getShareInfo( | ||||
|       { | ||||
|         title: goodsInfo.name, | ||||
|         image: sheep.$url.cdn(goodsInfo.picUrl), | ||||
|         desc: goodsInfo.introduction, | ||||
|         title: goodsInfo.title, | ||||
|         image: sheep.$url.cdn(goodsInfo.image), | ||||
|         desc: goodsInfo.subtitle, | ||||
|         params: { | ||||
|           page: '2', | ||||
|           query: goodsInfo.id, | ||||
|  | @ -89,10 +84,10 @@ | |||
|       }, | ||||
|       { | ||||
|         type: 'goods', // 商品海报 | ||||
|         title: goodsInfo.name, // 商品名称 | ||||
|         image: sheep.$url.cdn(goodsInfo.picUrl), // 商品主图 | ||||
|         price: fen2yuan(goodsInfo.price), // 商品价格 | ||||
|         original_price: fen2yuan(goodsInfo.marketPrice), // 商品原价 | ||||
|         title: goodsInfo.title, // 商品标题 | ||||
|         image: sheep.$url.cdn(goodsInfo.image), // 商品主图 | ||||
|         price: goodsInfo.price[0], // 商品价格 | ||||
|         original_price: goodsInfo.original_price, // 商品原价 | ||||
|       }, | ||||
|     ); | ||||
|     showShareModal(); | ||||
|  | @ -104,29 +99,19 @@ | |||
|       pageSize: state.pagination.pageSize, | ||||
|       pageNo: state.pagination.pageNo, | ||||
|     }); | ||||
| 
 | ||||
|     if (code !== 0) { | ||||
|       state.loadStatus = 'error'; // 处理错误状态 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 使用 Promise.all 来等待所有佣金请求完成 | ||||
|     await Promise.all( | ||||
|       data.list.map(async (item) => { | ||||
|         try { | ||||
|           const res = await BrokerageApi.getProductBrokeragePrice(item.id); | ||||
|           item.brokerageMinPrice = res.data.brokerageMinPrice; | ||||
|           item.brokerageMaxPrice = res.data.brokerageMaxPrice; | ||||
|         } catch (error) { | ||||
|           console.error(`获取商品【${item.name}】的佣金时出错:`, error); | ||||
|         } | ||||
|       }), | ||||
|     ); | ||||
| 
 | ||||
|     // 在所有请求完成后合并列表和更新状态 | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|     // 补充分佣金额 | ||||
|     data.list.forEach((item) => { | ||||
|       BrokerageApi.getProductBrokeragePrice(item.id).then((res) => { | ||||
|         item.brokerageMinPrice = res.data.brokerageMinPrice; | ||||
|         item.brokerageMaxPrice = res.data.brokerageMaxPrice; | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onLoad(() => { | ||||
|  |  | |||
|  | @ -1,57 +1,37 @@ | |||
| <!-- 分销中心  --> | ||||
| <template> | ||||
|   <s-layout | ||||
|     navbar="inner" | ||||
|     class="index-wrap" | ||||
|     title="分销中心" | ||||
|     :bgStyle="bgStyle" | ||||
|     :onShareAppMessage="shareInfo" | ||||
|   > | ||||
|     <!-- 分销商信息 --> | ||||
|     <commission-info /> | ||||
|     <!-- 账户信息 --> | ||||
|     <account-info /> | ||||
|     <!-- 菜单栏 --> | ||||
|     <commission-menu /> | ||||
|     <!-- 分销记录 --> | ||||
|     <commission-log /> | ||||
| 	<s-layout navbar="inner" class="index-wrap" title="分销中心" :bgStyle="bgStyle" onShareAppMessage> | ||||
| 		<!-- 分销商信息 --> | ||||
| 		<commission-info /> | ||||
| 		<!-- 账户信息 --> | ||||
| 		<account-info /> | ||||
| 		<!-- 菜单栏 --> | ||||
| 		<commission-menu /> | ||||
| 		<!-- 分销记录 --> | ||||
| 		<commission-log /> | ||||
| 
 | ||||
|     <!-- 权限弹窗 --> | ||||
|     <commission-auth /> | ||||
|   </s-layout> | ||||
| 		<!-- 权限弹窗 --> | ||||
| 		<commission-auth /> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed } from 'vue'; | ||||
|   import commissionInfo from './components/commission-info.vue'; | ||||
|   import accountInfo from './components/account-info.vue'; | ||||
|   import commissionLog from './components/commission-log.vue'; | ||||
|   import commissionMenu from './components/commission-menu.vue'; | ||||
|   import commissionAuth from './components/commission-auth.vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { SharePageEnum } from '@/sheep/helper/const'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 	import commissionInfo from './components/commission-info.vue'; | ||||
| 	import accountInfo from './components/account-info.vue'; | ||||
| 	import commissionLog from './components/commission-log.vue'; | ||||
| 	import commissionMenu from './components/commission-menu.vue'; | ||||
| 	import commissionAuth from './components/commission-auth.vue'; | ||||
| 
 | ||||
|   /** 分销邀请 */ | ||||
|   const shareInfo = computed(() => { | ||||
|     return sheep.$platform.share.getShareInfo( | ||||
|       { | ||||
|         params: { | ||||
|           page: SharePageEnum.HOME.value, // 用户通邀请进入到首页 | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         type: 'user', | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| 	const state = reactive({}); | ||||
| 
 | ||||
|   const bgStyle = { | ||||
|     color: '#F7D598', | ||||
|   }; | ||||
| 	const bgStyle = { | ||||
| 		color: '#F7D598', | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   :deep(.page-main) { | ||||
|     background-size: 100% 100% !important; | ||||
|   } | ||||
| </style> | ||||
| 	:deep(.page-main) { | ||||
| 		background-size: 100% 100% !important; | ||||
| 	} | ||||
| </style> | ||||
|  | @ -41,7 +41,10 @@ | |||
|           <view class="no-box ss-flex ss-col-center ss-row-between"> | ||||
|             <text class="order-code">订单编号:{{ item.bizId }}</text> | ||||
|             <text class="order-state"> | ||||
|               {{ item.status === 0 ? '待结算' : item.status === 1 ? '已结算' : '已取消' }} | ||||
|               {{ | ||||
|                 item.status === 0 ? '待结算' | ||||
|                   : item.status === 1 ? '已结算' : '已取消' | ||||
|               }} | ||||
|               ( 佣金 {{ fen2yuan(item.price) }} 元 ) | ||||
|             </text> | ||||
|           </view> | ||||
|  | @ -72,10 +75,11 @@ | |||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onPageScroll, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import _ from 'lodash'; | ||||
|   import { onPageScroll } from '@dcloudio/uni-app'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
|   import BrokerageApi from '@/sheep/api/trade/brokerage'; | ||||
|   import { fen2yuan } from '../../sheep/hooks/useGoods'; | ||||
| 
 | ||||
|  | @ -96,22 +100,22 @@ | |||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 8, | ||||
|       pageSize: 1, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const tabMaps = [ | ||||
|     { | ||||
|       name: '全部', | ||||
|       value: -1, | ||||
|       value: 'all', | ||||
|     }, | ||||
|     { | ||||
|       name: '待结算', | ||||
|       value: 0, // 待结算 | ||||
|       value: '0', // 待结算 | ||||
|     }, | ||||
|     { | ||||
|       name: '已结算', | ||||
|       value: 1, // 已结算 | ||||
|       value: '1', // 已结算 | ||||
|     }, | ||||
|   ]; | ||||
| 
 | ||||
|  | @ -125,17 +129,12 @@ | |||
|   // 获取订单列表 | ||||
|   async function getOrderList() { | ||||
|     state.loadStatus = 'loading'; | ||||
|     const tab = tabMaps[state.currentTab]; | ||||
|     const queryParams = { | ||||
|     let { code, data } = await BrokerageApi.getBrokerageRecordPage({ | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageNo: state.pagination.pageSize, | ||||
|       bizType: 1, // 获得推广佣金 | ||||
|       status: tab.value, | ||||
|     }; | ||||
|     if (tab.value < 0) { | ||||
|       delete queryParams.status; | ||||
|     } | ||||
|     const { code, data } = await BrokerageApi.getBrokerageRecordPage(queryParams); | ||||
|       status: state.currentTab > 0 ? state.currentTab : undefined, | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -1,31 +1,8 @@ | |||
| <!-- 页面 --> | ||||
| <!-- 页面 TODO 芋艿:该页面的实现代码需要优化,包括 js 和 css,以及相关的样式设计 --> | ||||
| <template> | ||||
|   <s-layout title="我的团队" :class="state.scrollTop ? 'team-wrap' : ''" navbar="inner"> | ||||
|     <view | ||||
|       class="header-box" | ||||
|       :style="[ | ||||
|         { | ||||
|           marginTop: '-' + Number(statusBarHeight + 88) + 'rpx', | ||||
|           paddingTop: Number(statusBarHeight + 108) + 'rpx', | ||||
|         }, | ||||
|       ]" | ||||
|     > | ||||
|       <!-- 推广数据总览 --> | ||||
|       <view class="team-data-box ss-flex ss-col-center ss-row-between" style="width: 100%"> | ||||
|         <view class="data-card" style="width: 100%"> | ||||
|           <view class="total-item" style="width: 100%"> | ||||
|             <view class="item-title" style="text-align: center">推广人数</view> | ||||
|             <view class="total-num" style="text-align: center"> | ||||
|               {{ | ||||
|                 state.summary.firstBrokerageUserCount + state.summary.secondBrokerageUserCount || 0 | ||||
|               }} | ||||
|             </view> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <view class="promoter-list"> | ||||
|       <!--<view | ||||
|       <view | ||||
|         class="promoterHeader bg-color" | ||||
|         style="backgroundcolor: #e93323 !important; height: 218rpx; color: #fff" | ||||
|       > | ||||
|  | @ -44,9 +21,9 @@ | |||
|           </view> | ||||
|           <view class="iconfont icon-tuandui" /> | ||||
|         </view> | ||||
|       </view>--> | ||||
|       <view style="padding: 0 20rpx"> | ||||
|         <view class="nav acea-row row-around l1" style="margin-top: 20rpx"> | ||||
|       </view> | ||||
|       <view style="padding: 0 30rpx"> | ||||
|         <view class="nav acea-row row-around l1"> | ||||
|           <view :class="state.level == 1 ? 'item on' : 'item'" @click="setType(1)"> | ||||
|             一级({{ state.summary.firstBrokerageUserCount || 0 }}) | ||||
|           </view> | ||||
|  | @ -68,7 +45,7 @@ | |||
|             /> | ||||
|           </view> | ||||
|           <image | ||||
|             :src="sheep.$url.static('/static/img/shop/search.png')" | ||||
|             src="/static/images/search.png" | ||||
|             mode="" | ||||
|             style="width: 60rpx; height: 64rpx" | ||||
|             @click="submitForm" | ||||
|  | @ -82,7 +59,8 @@ | |||
|               v-if="sort === 'userCountDESC'" | ||||
|             > | ||||
|               团队排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort1.png')" /> | ||||
|               <!-- TODO 芋艿:看看怎么从项目里拿出去 --> | ||||
|               <image src="/static/images/sort1.png" /> | ||||
|             </view> | ||||
|             <view | ||||
|               class="sortItem" | ||||
|  | @ -90,15 +68,15 @@ | |||
|               v-else-if="sort === 'userCountASC'" | ||||
|             > | ||||
|               团队排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort3.png')" /> | ||||
|               <image src="/static/images/sort3.png" /> | ||||
|             </view> | ||||
|             <view class="sortItem" @click="setSort('userCount', 'desc')" v-else> | ||||
|               团队排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort2.png')" /> | ||||
|               <image src="/static/images/sort2.png" /> | ||||
|             </view> | ||||
|             <view class="sortItem" @click="setSort('price', 'asc')" v-if="sort === 'priceDESC'"> | ||||
|               金额排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort1.png')" /> | ||||
|               <image src="/static/images/sort1.png" /> | ||||
|             </view> | ||||
|             <view | ||||
|               class="sortItem" | ||||
|  | @ -106,11 +84,11 @@ | |||
|               v-else-if="sort === 'priceASC'" | ||||
|             > | ||||
|               金额排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort3.png')" /> | ||||
|               <image src="/static/images/sort3.png" /> | ||||
|             </view> | ||||
|             <view class="sortItem" @click="setSort('price', 'desc')" v-else> | ||||
|               金额排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort2.png')" /> | ||||
|               <image src="/static/images/sort2.png" /> | ||||
|             </view> | ||||
|             <view | ||||
|               class="sortItem" | ||||
|  | @ -118,7 +96,7 @@ | |||
|               v-if="sort === 'orderCountDESC'" | ||||
|             > | ||||
|               订单排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort1.png')" /> | ||||
|               <image src="/static/images/sort1.png" /> | ||||
|             </view> | ||||
|             <view | ||||
|               class="sortItem" | ||||
|  | @ -126,11 +104,11 @@ | |||
|               v-else-if="sort === 'orderCountASC'" | ||||
|             > | ||||
|               订单排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort3.png')" /> | ||||
|               <image src="/static/images/sort3.png" /> | ||||
|             </view> | ||||
|             <view class="sortItem" @click="setSort('orderCount', 'desc')" v-else> | ||||
|               订单排序 | ||||
|               <image :src="sheep.$url.static('/static/img/shop/sort2.png')" /> | ||||
|               <image src="/static/images/sort2.png" /> | ||||
|             </view> | ||||
|           </view> | ||||
|           <block v-for="(item, index) in state.pagination.list" :key="index"> | ||||
|  | @ -167,14 +145,14 @@ | |||
|                   >单</view | ||||
|                 > | ||||
|                 <view> | ||||
|                   <text class="num">{{ fen2yuan(item.brokeragePrice) || 0 }}</text | ||||
|                   <text class="num">{{ item.brokeragePrice || 0 }}</text | ||||
|                   >元 | ||||
|                 </view> | ||||
|               </view> | ||||
|             </view> | ||||
|           </block> | ||||
|           <block v-if="state.pagination.list.length === 0"> | ||||
|             <view style="text-align: center; margin-top: 30rpx">暂无推广人数</view> | ||||
|             <view style="text-align: center">暂无推广人数</view> | ||||
|           </block> | ||||
|         </view> | ||||
|       </view> | ||||
|  | @ -257,10 +235,9 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { computed, reactive, ref } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import { onPageScroll } from '@dcloudio/uni-app'; | ||||
|   import BrokerageApi from '@/sheep/api/trade/brokerage'; | ||||
|   import { fen2yuan } from '../../sheep/hooks/useGoods'; | ||||
| 
 | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   // const agentInfo = computed(() => sheep.$store('user').agentInfo); | ||||
|  | @ -479,7 +456,7 @@ | |||
|   .promoter-list .nav .item.on { | ||||
|     border-bottom: 5rpx solid; | ||||
|     // $theme-color | ||||
|     color: var(--ui-BG-Main); | ||||
|     color: red; | ||||
|     // $theme-color | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -15,10 +15,7 @@ | |||
|         <view class="num-title">可提现金额(元)</view> | ||||
|         <view class="wallet-num">{{ fen2yuan(state.brokerageInfo.brokeragePrice) }}</view> | ||||
|       </view> | ||||
|       <button | ||||
|         class="ss-reset-button log-btn" | ||||
|         @tap="sheep.$router.go('/pages/commission/wallet', { type: 2 })" | ||||
|       > | ||||
|       <button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/commission/wallet', { type: 2 })"> | ||||
|         提现记录 | ||||
|       </button> | ||||
|     </view> | ||||
|  | @ -29,10 +26,9 @@ | |||
|         <view class="bank-list ss-flex ss-col-center" @tap="onAccountSelect(true)"> | ||||
|           <view v-if="!state.accountInfo.type" class="empty-text">请选择提现方式</view> | ||||
|           <view v-if="state.accountInfo.type === '1'" class="empty-text">钱包余额</view> | ||||
|           <view v-if="state.accountInfo.type === '2'" class="empty-text">银行卡转账</view> | ||||
|           <view v-if="state.accountInfo.type === '3'" class="empty-text">微信账户</view> | ||||
|           <view v-if="state.accountInfo.type === '4'" class="empty-text">支付宝账户</view> | ||||
|           <view v-if="state.accountInfo.type === '5'" class="empty-text">微信零钱</view> | ||||
|           <view v-if="state.accountInfo.type === '2'" class="empty-text">微信零钱</view> | ||||
|           <view v-if="state.accountInfo.type === '3'" class="empty-text">支付宝账户</view> | ||||
|           <view v-if="state.accountInfo.type === '4'" class="empty-text">银行卡转账</view> | ||||
|           <text class="cicon-forward" /> | ||||
|         </view> | ||||
|       </view> | ||||
|  | @ -49,85 +45,71 @@ | |||
|         /> | ||||
|       </view> | ||||
|       <!-- 提现账号 --> | ||||
|       <view class="card-title" v-show="['2', '6'].includes(state.accountInfo.type)"> | ||||
|       <view class="card-title" v-show="['2', '3', '4'].includes(state.accountInfo.type)"> | ||||
|         提现账号 | ||||
|       </view> | ||||
|       <view | ||||
|         class="input-box ss-flex ss-col-center border-bottom" | ||||
|         v-show="['2', '6'].includes(state.accountInfo.type)" | ||||
|         v-show="['2', '3', '4'].includes(state.accountInfo.type)" | ||||
|       > | ||||
|         <view class="unit" /> | ||||
|         <uni-easyinput | ||||
|           :inputBorder="false" | ||||
|           class="ss-flex-1 ss-p-l-10" | ||||
|           v-model="state.accountInfo.userAccount" | ||||
|           v-model="state.accountInfo.accountNo" | ||||
|           placeholder="请输入提现账号" | ||||
|         /> | ||||
|       </view> | ||||
|       <!-- 收款码 --> | ||||
|       <view class="card-title" v-show="['3', '4'].includes(state.accountInfo.type)">收款码</view> | ||||
|       <view class="card-title" v-show="['2', '3'].includes(state.accountInfo.type)">收款码</view> | ||||
|       <view | ||||
|         class="input-box ss-flex ss-col-center" | ||||
|         v-show="['3', '4'].includes(state.accountInfo.type)" | ||||
|         v-show="['2', '3'].includes(state.accountInfo.type)" | ||||
|       > | ||||
|         <view class="unit" /> | ||||
|         <view class="upload-img"> | ||||
|           <s-uploader | ||||
|             v-model:url="state.accountInfo.qrCodeUrl" | ||||
|             v-model:url="state.accountInfo.accountQrCodeUrl" | ||||
|             fileMediatype="image" | ||||
|             limit="1" | ||||
|             mode="grid" | ||||
|             :imageStyles="{ width: '168rpx', height: '168rpx' }" | ||||
|             @success="(payload) => (state.accountInfo.qrCodeUrl = payload.tempFilePaths[0])" | ||||
|           /> | ||||
|         </view> | ||||
|       </view> | ||||
|       <!-- 持卡人姓名 --> | ||||
|       <view class="card-title" v-show="['2', '5', '6'].includes(state.accountInfo.type)"> | ||||
|         收款真名 | ||||
|       </view> | ||||
|       <view class="card-title" v-show="state.accountInfo.type === '4'">持卡人</view> | ||||
|       <view | ||||
|         class="input-box ss-flex ss-col-center border-bottom" | ||||
|         v-show="['2', '5', '6'].includes(state.accountInfo.type)" | ||||
|         v-show="state.accountInfo.type === '4'" | ||||
|       > | ||||
|         <view class="unit" /> | ||||
|         <uni-easyinput | ||||
|           :inputBorder="false" | ||||
|           class="ss-flex-1 ss-p-l-10" | ||||
|           v-model="state.accountInfo.userName" | ||||
|           placeholder="请输入收款真名" | ||||
|           v-model="state.accountInfo.name" | ||||
|           placeholder="请输入持卡人姓名" | ||||
|         /> | ||||
|       </view> | ||||
|       <!-- 提现银行 --> | ||||
|       <view class="card-title" v-show="state.accountInfo.type === '2'">提现银行</view> | ||||
|       <view class="card-title" v-show="state.accountInfo.type === '4'">提现银行</view> | ||||
|       <view | ||||
|         class="input-box ss-flex ss-col-center border-bottom" | ||||
|         v-show="state.accountInfo.type === '2'" | ||||
|         v-show="state.accountInfo.type === '4'" | ||||
|       > | ||||
|         <view class="unit" /> | ||||
|         <!--银行改为下拉选择--> | ||||
|         <picker | ||||
|           @change="bankChange" | ||||
|           :value="state.bankListSelectedIndex" | ||||
|           :range="state.bankList" | ||||
|           range-key="label" | ||||
|           style="width: 100%" | ||||
|         > | ||||
|           <uni-easyinput | ||||
|             :inputBorder="false" | ||||
|             :value="state.accountInfo.bankName" | ||||
|             placeholder="请选择银行" | ||||
|             suffixIcon="right" | ||||
|             disabled | ||||
|             :styles="{ disableColor: '#fff', borderColor: '#fff', color: '#333!important' }" | ||||
|           /> | ||||
|         </picker> | ||||
|         <uni-easyinput | ||||
|           :inputBorder="false" | ||||
|           class="ss-flex-1 ss-p-l-10" | ||||
|           v-model="state.accountInfo.bankName" | ||||
|           placeholder="请输入提现银行" | ||||
|         /> | ||||
|       </view> | ||||
|       <!-- 开户地址 --> | ||||
|       <view class="card-title" v-show="state.accountInfo.type === '2'">开户地址</view> | ||||
|       <view class="card-title" v-show="state.accountInfo.type === '4'">开户地址</view> | ||||
|       <view | ||||
|         class="input-box ss-flex ss-col-center border-bottom" | ||||
|         v-show="state.accountInfo.type === '2'" | ||||
|         v-show="state.accountInfo.type === '4'" | ||||
|       > | ||||
|         <view class="unit" /> | ||||
|         <uni-easyinput | ||||
|  | @ -164,26 +146,25 @@ | |||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { onBeforeMount, reactive } from 'vue'; | ||||
|   import { computed, reactive, onBeforeMount } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import accountTypeSelect from './components/account-type-select.vue'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import TradeConfigApi from '@/sheep/api/trade/config'; | ||||
|   import BrokerageApi from '@/sheep/api/trade/brokerage'; | ||||
|   import DictApi from '@/sheep/api/system/dict'; | ||||
|   import SLayout from '@/sheep/components/s-layout/s-layout.vue'; | ||||
|   import { getWeixinPayChannelCode, goBindWeixin } from '@/sheep/platform/pay'; | ||||
| 
 | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png'); | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
| 
 | ||||
|   const userStore = sheep.$store('user'); | ||||
|   const userInfo = computed(() => userStore.userInfo); | ||||
|   const state = reactive({ | ||||
|     accountInfo: { | ||||
|       // 提现表单 | ||||
|       type: undefined, | ||||
|       userAccount: undefined, | ||||
|       userName: undefined, | ||||
|       qrCodeUrl: undefined, | ||||
|       accountNo: undefined, | ||||
|       accountQrCodeUrl: undefined, | ||||
|       name: undefined, | ||||
|       bankName: undefined, | ||||
|       bankAddress: undefined, | ||||
|     }, | ||||
|  | @ -195,8 +176,6 @@ | |||
|     frozenDays: 0, // 冻结天数 | ||||
|     minPrice: 0, // 最低提现金额 | ||||
|     withdrawTypes: [], // 提现方式 | ||||
|     bankList: [], // 银行字典数据 | ||||
|     bankListSelectedIndex: '', // 选中银行 bankList 的 index | ||||
|   }); | ||||
| 
 | ||||
|   // 打开提现方式的弹窗 | ||||
|  | @ -207,6 +186,7 @@ | |||
|   // 提交提现 | ||||
|   const onConfirm = async () => { | ||||
|     // 参数校验 | ||||
|     debugger; | ||||
|     if ( | ||||
|       !state.accountInfo.price || | ||||
|       state.accountInfo.price > state.brokerageInfo.price || | ||||
|  | @ -219,31 +199,11 @@ | |||
|       sheep.$helper.toast('请选择提现方式'); | ||||
|       return; | ||||
|     } | ||||
|     let openid; | ||||
|     if (state.accountInfo.type === '5') { | ||||
|       openid = await sheep.$platform.useProvider('wechat').getOpenid(); | ||||
|       // 如果获取不到 openid,微信无法发起支付,此时需要引导 | ||||
|       if (!openid) { | ||||
|         goBindWeixin(); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // 提交请求 | ||||
|     const data = { | ||||
|     let { code } = await BrokerageApi.createBrokerageWithdraw({ | ||||
|       ...state.accountInfo, | ||||
|       price: state.accountInfo.price * 100, | ||||
|     }; | ||||
|     if (state.accountInfo.type === '5') { | ||||
|       data.userAccount = openid; | ||||
|       data.transferChannelCode = getWeixinPayChannelCode(); | ||||
|     } else if (state.accountInfo.type === '6' || state.accountInfo.type === '2') { | ||||
|       delete data.transferChannelCode; | ||||
|     } else { | ||||
|       delete data.userAccount; | ||||
|       delete data.transferChannelCode; | ||||
|     } | ||||
|     let { code } = await BrokerageApi.createBrokerageWithdraw(data); | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|  | @ -255,12 +215,12 @@ | |||
|       confirmText: '查看记录', | ||||
|       success: (res) => { | ||||
|         if (res.confirm) { | ||||
|           sheep.$router.go('/pages/commission/wallet', { type: 2 }); | ||||
|           sheep.$router.go('/pages/commission/wallet', { type: 2 }) | ||||
|           return; | ||||
|         } | ||||
|         getBrokerageUser(); | ||||
|         state.accountInfo = {}; | ||||
|       }, | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -285,29 +245,10 @@ | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 获取提现银行配置字典 | ||||
|   async function getDictDataListByType() { | ||||
|     let { code, data } = await DictApi.getDictDataListByType('brokerage_bank_name'); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     if (data && data.length > 0) { | ||||
|       state.bankList = data; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 银行选择 | ||||
|   function bankChange(e) { | ||||
|     const value = e.detail.value; | ||||
|     state.bankListSelectedIndex = value; | ||||
|     state.accountInfo.bankName = state.bankList[value].label; | ||||
|   } | ||||
| 
 | ||||
|   onBeforeMount(() => { | ||||
|     getWithdrawRules(); | ||||
|     getBrokerageUser(); | ||||
|     getDictDataListByType(); //获取银行字典数据 | ||||
|   }); | ||||
|     getBrokerageUser() | ||||
|   }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  |  | |||
|  | @ -1,81 +0,0 @@ | |||
| <template> | ||||
|   <view class="content-container"> | ||||
|     <span v-for="(part, index) in formattedContent" :key="index" | ||||
|           @click="handleClick(part)" | ||||
|           :class="{'highlight-number': part.isNumber, 'phone-number': part.isPhone}"> | ||||
|       {{ part.text }} | ||||
|     </span> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
|   export default { | ||||
|     name: 'HighlightNumber', | ||||
|     props: { | ||||
|       content: { | ||||
|         type: String, | ||||
|         required: true | ||||
|       } | ||||
|     }, | ||||
|     computed: { | ||||
|       formattedContent() { | ||||
|         const phoneRegex = /(1[3-9]\d{9})/g; | ||||
|         const numberRegex = /(\d+)/g; | ||||
|         let text = this.content; | ||||
|         let result = []; | ||||
|         let match; | ||||
| 
 | ||||
|         // Step 1: 提取手机号 | ||||
|         while ((match = phoneRegex.exec(text)) !== null) { | ||||
|           if (match.index > 0) { | ||||
|             const before = text.slice(0, match.index); | ||||
|             result.push(...this.splitAndPush(before, false, false)); | ||||
|           } | ||||
|           result.push({ text: match[0], isNumber: true, isPhone: true }); | ||||
|           text = text.slice(match.index + match[0].length); | ||||
|         } | ||||
| 
 | ||||
|         // Step 2: 提取普通数字 | ||||
|         while ((match = numberRegex.exec(text)) !== null) { | ||||
|           if (match.index > 0) { | ||||
|             const before = text.slice(0, match.index); | ||||
|             result.push(...this.splitAndPush(before, false, false)); | ||||
|           } | ||||
|           result.push({ text: match[0], isNumber: true }); | ||||
|           text = text.slice(match.index + match[0].length); | ||||
|         } | ||||
| 
 | ||||
|         // Step 3: 添加剩余文本 | ||||
|         if (text.length > 0) { | ||||
|           result.push(...this.splitAndPush(text, false, false)); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|       } | ||||
|     }, | ||||
|     methods: { | ||||
|       splitAndPush(str, isNumber = false, isPhone = false) { | ||||
|         return str.split('').map(char => ({ text: char, isNumber, isPhone })); | ||||
|       }, | ||||
|       handleClick(part) { | ||||
|         if (part.isPhone) { | ||||
|           this.$emit('phone-click', { phoneNumber: part.text }); | ||||
|         } else if (part.isNumber) { | ||||
|           this.$emit('number-click', { number: part.text }); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
|   .highlight-number { | ||||
|     color: #ff5722; | ||||
|     font-weight: bold; | ||||
|   } | ||||
| 
 | ||||
|   .phone-number { | ||||
|     color: #007AFF; | ||||
|     text-decoration: underline; | ||||
|   } | ||||
| </style> | ||||
|  | @ -16,16 +16,13 @@ | |||
|             <view class="title ss-m-t-50 ss-m-b-20 ss-m-x-20">{{ state.coupon.name }}</view> | ||||
|             <view class="subtitle ss-m-b-50"> | ||||
|               满 {{ fen2yuan(state.coupon.usePrice) }} 元, | ||||
|               {{ | ||||
|                 state.coupon.discountType === 1 | ||||
|                   ? '减 ' + fen2yuan(state.coupon.discountPrice) + ' 元' | ||||
|                   : '打 ' + state.coupon.discountPercent / 10.0 + ' 折' | ||||
|               }} | ||||
|               {{ state.coupon.discountType === 1 | ||||
|                 ? '减 ' + fen2yuan(state.coupon.discountPrice) + ' 元' | ||||
|                 : '打 ' + state.coupon.discountPercent / 10.0 + ' 折' }} | ||||
|             </view> | ||||
|             <button | ||||
|               class="ss-reset-button ss-m-b-30" | ||||
|               :class=" | ||||
|                 state.coupon.canTake || state.coupon.status === 1 | ||||
|               :class="state.coupon.canTake || state.coupon.status === 1 | ||||
|                   ? 'use-btn' // 优惠劵模版(可领取)、优惠劵(可使用) | ||||
|                   : 'disable-btn' | ||||
|               " | ||||
|  | @ -34,13 +31,7 @@ | |||
|             > | ||||
|               <text v-if="state.id > 0">{{ state.coupon.canTake ? '立即领取' : '已领取' }}</text> | ||||
|               <text v-else> | ||||
|                 {{ | ||||
|                   state.coupon.status === 1 | ||||
|                     ? '可使用' | ||||
|                     : state.coupon.status === 2 | ||||
|                     ? '已使用' | ||||
|                     : '已过期' | ||||
|                 }} | ||||
|                 {{ state.coupon.status === 1 ? '立即使用' : state.coupon.status === 2 ? '已使用' : '已过期' }} | ||||
|               </text> | ||||
|             </button> | ||||
|             <view class="time ss-m-y-30" v-if="state.coupon.validityType === 2"> | ||||
|  | @ -57,6 +48,7 @@ | |||
|               <view>优惠券类型</view> | ||||
|               <view>{{ state.coupon.discountType === 1 ? '满减券' : '折扣券' }}</view> | ||||
|             </view> | ||||
|             <!-- TODO 芋艿:可优化,增加优惠劵的描述 --> | ||||
|             <uni-collapse> | ||||
|               <uni-collapse-item title="优惠券说明" v-if="state.coupon.description"> | ||||
|                 <view class="content ss-p-b-20"> | ||||
|  | @ -148,12 +140,12 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import CouponApi from '@/sheep/api/promotion/coupon'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import CategoryApi from '@/sheep/api/product/category'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     id: 0, // 优惠劵模版编号 templateId | ||||
|  | @ -164,7 +156,7 @@ | |||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 8, | ||||
|       pageSize: 1, | ||||
|     }, | ||||
|     categoryId: 0, // 选中的商品分类编号 | ||||
|     tabMaps: [], // 指定分类时,每个分类构成一个 tab | ||||
|  | @ -184,7 +176,7 @@ | |||
|     const { code, data } = await SpuApi.getSpuPage({ | ||||
|       categoryId: state.categoryId, | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       pageSize: state.pagination.pageSize | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|  | @ -205,9 +197,7 @@ | |||
| 
 | ||||
|   // 获得分类列表 | ||||
|   async function getCategoryList() { | ||||
|     const { data, code } = await CategoryApi.getCategoryListByIds( | ||||
|       state.coupon.productScopeValues.join(','), | ||||
|     ); | ||||
|     const { data, code } = await CategoryApi.getCategoryListByIds(state.coupon.productScopeValues.join(',')); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|  | @ -235,10 +225,8 @@ | |||
| 
 | ||||
|   // 加载优惠劵信息 | ||||
|   async function getCouponContent() { | ||||
|     const { code, data } = | ||||
|       state.id > 0 | ||||
|         ? await CouponApi.getCouponTemplate(state.id) | ||||
|         : await CouponApi.getCoupon(state.couponId); | ||||
|     const { code, data } = state.id > 0 ? await CouponApi.getCouponTemplate(state.id) | ||||
|       : await CouponApi.getCoupon(state.couponId); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 优惠券中心  --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#f2f2f2' }" title="优惠券"> | ||||
|   <s-layout title="优惠券" :bgStyle="{ color: '#f2f2f2' }"> | ||||
|     <su-sticky bgColor="#fff"> | ||||
|       <su-tabs | ||||
|         :list="tabMaps" | ||||
|  | @ -45,7 +45,7 @@ | |||
|           <template #default> | ||||
|             <button | ||||
|               class="ss-reset-button card-btn ss-flex ss-row-center ss-col-center" | ||||
|               :class="item.status !== 1 ? 'disabled-btn' : ''" | ||||
|               :class=" item.status !== 1 ? 'disabled-btn': ''" | ||||
|               :disabled="item.status !== 1" | ||||
|               @click.stop="sheep.$router.go('/pages/coupon/detail', { couponId: item.id })" | ||||
|             > | ||||
|  | @ -56,14 +56,9 @@ | |||
|       </view> | ||||
|     </template> | ||||
| 
 | ||||
|     <uni-load-more | ||||
|       v-if="state.pagination.total > 0" | ||||
|       :status="state.loadStatus" | ||||
|       :content-text="{ | ||||
|     <uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{ | ||||
|         contentdown: '上拉加载更多', | ||||
|       }" | ||||
|       @tap="loadMore" | ||||
|     /> | ||||
|       }" @tap="loadMore" /> | ||||
|   </s-layout> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -71,8 +66,8 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import _ from 'lodash'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
|   import CouponApi from '@/sheep/api/promotion/coupon'; | ||||
| 
 | ||||
|   // 数据 | ||||
|  | @ -83,7 +78,7 @@ | |||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 5, | ||||
|       pageSize: 5 | ||||
|     }, | ||||
|     loadStatus: '', | ||||
|   }); | ||||
|  | @ -107,12 +102,13 @@ | |||
|     }, | ||||
|   ]; | ||||
| 
 | ||||
|   // TODO yunai: | ||||
|   function onTabsChange(e) { | ||||
|     state.currentTab = e.index; | ||||
|     state.type = e.value; | ||||
|     resetPagination(state.pagination); | ||||
|     resetPagination(state.pagination) | ||||
|     if (state.currentTab === 0) { | ||||
|       getData(); | ||||
|     	getData(); | ||||
|     } else { | ||||
|       getCoupon(); | ||||
|     } | ||||
|  | @ -139,7 +135,7 @@ | |||
|     const { data, code } = await CouponApi.getCouponPage({ | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       status: state.type, | ||||
|       status: state.type | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|  | @ -181,13 +177,13 @@ | |||
|     // 领劵中心 | ||||
|     if (Option.type === 'all' || !Option.type) { | ||||
|       getData(); | ||||
|       // 我的优惠劵 | ||||
|     // 我的优惠劵 | ||||
|     } else { | ||||
|       Option.type === 'geted' | ||||
|         ? (state.currentTab = 1) | ||||
|         : Option.type === 'used' | ||||
|         ? (state.currentTab = 2) | ||||
|         : (state.currentTab = 3); | ||||
|           ? (state.currentTab = 2) | ||||
|           : (state.currentTab = 3); | ||||
|       state.type = state.currentTab; | ||||
|       getCoupon(); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,130 +1,89 @@ | |||
| <!-- 评价  --> | ||||
| <template> | ||||
|   <s-layout title="评价"> | ||||
|     <view> | ||||
|       <view v-for="(item, index) in state.orderInfo.items" :key="item.id"> | ||||
|         <view> | ||||
|           <view class="commont-from-wrap"> | ||||
|             <!-- 评价商品 --> | ||||
|             <s-goods-item | ||||
| 	<s-layout title="评价"> | ||||
| 		<view> | ||||
| 			<view v-for="(item, index) in state.orderInfo.items" :key="item.id"> | ||||
| 				<view> | ||||
| 					<view class="commont-from-wrap"> | ||||
| 						<!-- 评价商品 --> | ||||
| 						<s-goods-item | ||||
|               :img="item.picUrl" | ||||
|               :title="item.spuName" | ||||
|               :skuText="item.properties.map((property) => property.valueName).join(' ')" | ||||
|               :price="item.payPrice" | ||||
| 							:price="item.payPrice" | ||||
|               :num="item.count" | ||||
|             /> | ||||
|           </view> | ||||
| 					</view> | ||||
| 
 | ||||
|           <view class="form-item"> | ||||
|             <!-- 评分 --> | ||||
|             <view class="star-box ss-flex ss-col-center"> | ||||
|               <view class="star-title ss-m-r-40">商品质量</view> | ||||
|               <uni-rate v-model="state.commentList[index].descriptionScores" /> | ||||
|             </view> | ||||
|             <view class="star-box ss-flex ss-col-center"> | ||||
|               <view class="star-title ss-m-r-40">服务态度</view> | ||||
|               <uni-rate v-model="state.commentList[index].benefitScores" /> | ||||
|             </view> | ||||
|             <!-- 评价 --> | ||||
|             <view class="area-box"> | ||||
|               <uni-easyinput | ||||
|                 :inputBorder="false" | ||||
|                 type="textarea" | ||||
|                 maxlength="120" | ||||
|                 autoHeight | ||||
|                 v-model="state.commentList[index].content" | ||||
|                 placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" | ||||
|               /> | ||||
|               <view class="img-box"> | ||||
|                 <s-uploader | ||||
|                   v-model:url="state.commentList[index].images" | ||||
|                   fileMediatype="image" | ||||
|                   limit="9" | ||||
|                   mode="grid" | ||||
|                   :imageStyles="{ width: '168rpx', height: '168rpx' }" | ||||
|                   @success="(payload) => uploadSuccess(payload, index)" | ||||
|                 /> | ||||
|               </view> | ||||
|             </view> | ||||
|             <view class="checkbox-container"> | ||||
|               <checkbox-group @change="(event) => toggleAnonymous(index, event)"> | ||||
|                 <label> | ||||
|                   <checkbox value="anonymousChecked" /> | ||||
|                   匿名评论 | ||||
|                 </label> | ||||
|               </checkbox-group> | ||||
|             </view> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <su-fixed bottom placeholder> | ||||
|       <view class="foot_box ss-flex ss-row-center ss-col-center"> | ||||
|         <button class="ss-reset-button post-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onSubmit"> | ||||
|           发布 | ||||
|         </button> | ||||
|       </view> | ||||
|     </su-fixed> | ||||
|   </s-layout> | ||||
| 					<view class="form-item"> | ||||
| 						<!-- 评分 --> | ||||
| 						<view class="star-box ss-flex ss-col-center"> | ||||
| 							<view class="star-title ss-m-r-40">商品质量</view> | ||||
| 							<uni-rate v-model="state.commentList[index].descriptionScores" /> | ||||
| 						</view> | ||||
| 						<view class="star-box ss-flex ss-col-center"> | ||||
| 							<view class="star-title ss-m-r-40">服务态度</view> | ||||
| 							<uni-rate v-model="state.commentList[index].benefitScores" /> | ||||
| 						</view> | ||||
| 						<!-- 评价 --> | ||||
| 						<view class="area-box"> | ||||
| 							<uni-easyinput :inputBorder="false" type="textarea" maxlength="120" autoHeight | ||||
| 								v-model="state.commentList[index].content" | ||||
| 								placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" /> | ||||
|               <!-- TODO 芋艿:文件上传 --> | ||||
| 							<view class="img-box"> | ||||
| 								<s-uploader v-model:url="state.commentList[index].images" fileMediatype="image" | ||||
| 									limit="9" mode="grid" :imageStyles="{ width: '168rpx', height: '168rpx' }" /> | ||||
| 							</view> | ||||
| 						</view> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
|     <!-- TODO 芋艿:是否匿名 --> | ||||
| 
 | ||||
| 		<su-fixed bottom placeholder> | ||||
| 			<view class="foot_box ss-flex ss-row-center ss-col-center"> | ||||
| 				<button class="ss-reset-button post-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onSubmit"> | ||||
| 					发布 | ||||
| 				</button> | ||||
| 			</view> | ||||
| 		</su-fixed> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { onLoad } from '@dcloudio/uni-app'; | ||||
| 	import { reactive } from 'vue'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     orderInfo: {}, | ||||
|     commentList: [], | ||||
|     id: null, | ||||
|   }); | ||||
| 	const state = reactive({ | ||||
| 		orderInfo: {}, | ||||
| 		commentList: [], | ||||
| 		id: null | ||||
| 	}); | ||||
| 
 | ||||
|   /** | ||||
|    * 切换是否匿名 | ||||
|    * | ||||
|    * @param commentIndex  当前评论下标 | ||||
|    * @param event 复选框事件 | ||||
|    */ | ||||
|   function toggleAnonymous(commentIndex, event) { | ||||
|     state.commentList[commentIndex].anonymous = event.detail.value[0] === 'anonymousChecked'; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 发布评论 | ||||
|    * | ||||
|    * @returns {Promise<void>} | ||||
|    */ | ||||
|   async function onSubmit() { | ||||
| 	async function onSubmit() { | ||||
|     // 顺序提交评论 | ||||
|     for (const comment of state.commentList) { | ||||
|       await OrderApi.createOrderItemComment(comment); | ||||
|     } | ||||
|     // 都评论好,返回 | ||||
|     sheep.$router.back(); | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   /** | ||||
|    * 图片添加到表单 | ||||
|    * | ||||
|    * @param payload 上传成功后的回调数据 | ||||
|    * @param commentIndex  当前评论的下标 | ||||
|    */ | ||||
|   function uploadSuccess(payload, commentIndex) { | ||||
|     state.commentList[commentIndex].picUrls = payload.tempFilePaths; | ||||
|   } | ||||
| 
 | ||||
|   onLoad(async (options) => { | ||||
| 	onLoad(async (options) => { | ||||
|     if (!options.id) { | ||||
|       sheep.$helper.toast(`缺少订单信息,请检查`); | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     state.id = options.id; | ||||
| 		state.id = options.id; | ||||
| 
 | ||||
|     const { code, data } = await OrderApi.getOrderDetail(state.id); | ||||
| 		const { code, data } = await OrderApi.getOrder(state.id); | ||||
|     if (code !== 0) { | ||||
|       sheep.$helper.toast('无待评价订单'); | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     // 处理评论 | ||||
|     data.items.forEach((item) => { | ||||
|  | @ -134,57 +93,53 @@ | |||
|         descriptionScores: 5, | ||||
|         benefitScores: 5, | ||||
|         content: '', | ||||
|         picUrls: [], | ||||
|         picUrls: [] | ||||
|       }); | ||||
|     }); | ||||
|     state.orderInfo = data; | ||||
|   }); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   // 评价商品 | ||||
|   .goods-card { | ||||
|     margin: 10rpx 0; | ||||
|     padding: 20rpx; | ||||
|     background: #fff; | ||||
|   } | ||||
| 	// 评价商品 | ||||
| 	.goods-card { | ||||
| 		margin: 10rpx 0; | ||||
| 		padding: 20rpx; | ||||
| 		background: #fff; | ||||
| 	} | ||||
| 
 | ||||
|   // 评论,选择图片 | ||||
|   .form-item { | ||||
|     background: #fff; | ||||
| 	// 评论,选择图片 | ||||
| 	.form-item { | ||||
| 		background: #fff; | ||||
| 
 | ||||
|     .star-box { | ||||
|       height: 100rpx; | ||||
|       padding: 0 25rpx; | ||||
|     } | ||||
| 		.star-box { | ||||
| 			height: 100rpx; | ||||
| 			padding: 0 25rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .star-title { | ||||
|       font-weight: 600; | ||||
|     } | ||||
|   } | ||||
| 		.star-title { | ||||
| 			font-weight: 600; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .area-box { | ||||
|     width: 690rpx; | ||||
|     min-height: 306rpx; | ||||
|     background: rgba(249, 250, 251, 1); | ||||
|     border-radius: 20rpx; | ||||
|     padding: 28rpx; | ||||
|     margin: auto; | ||||
| 	.area-box { | ||||
| 		width: 690rpx; | ||||
| 		min-height: 306rpx; | ||||
| 		background: rgba(249, 250, 251, 1); | ||||
| 		border-radius: 20rpx; | ||||
| 		padding: 28rpx; | ||||
| 		margin: auto; | ||||
| 
 | ||||
|     .img-box { | ||||
|       margin-top: 20rpx; | ||||
|     } | ||||
|   } | ||||
| 		.img-box { | ||||
| 			margin-top: 20rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .checkbox-container { | ||||
|     padding: 10rpx; | ||||
|   } | ||||
| 
 | ||||
|   .post-btn { | ||||
|     width: 690rpx; | ||||
|     line-height: 80rpx; | ||||
|     border-radius: 40rpx; | ||||
|     color: rgba(#fff, 0.9); | ||||
|     margin-bottom: 20rpx; | ||||
|   } | ||||
| </style> | ||||
| 	.post-btn { | ||||
| 		width: 690rpx; | ||||
| 		line-height: 80rpx; | ||||
| 		border-radius: 40rpx; | ||||
| 		color: rgba(#fff, 0.9); | ||||
| 		margin-bottom: 20rpx; | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 商品评论的分页 --> | ||||
| <template> | ||||
|   <s-layout title="全部评论"> | ||||
|   <s-layout title="全部评价"> | ||||
|     <su-tabs | ||||
|       :list="state.type" | ||||
|       :scrollable="false" | ||||
|  | @ -16,7 +16,6 @@ | |||
|     <s-empty v-if="state.pagination.total === 0" text="暂无数据" icon="/static/data-empty.png" /> | ||||
|     <!-- 下拉 --> | ||||
|     <uni-load-more | ||||
|       icon-type="auto" | ||||
|       v-if="state.pagination.total > 0" | ||||
|       :status="state.loadStatus" | ||||
|       :content-text="{ | ||||
|  | @ -31,7 +30,7 @@ | |||
|   import CommentApi from '@/sheep/api/product/comment'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import commentItem from '../components/detail/comment-item.vue'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|  | @ -48,7 +47,7 @@ | |||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 8, | ||||
|       pageSize: 1, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
|   <su-fixed bottom placeholder :val="44"> | ||||
|     <view> | ||||
|       <view v-for="activity in props.activityList" :key="activity.id"> | ||||
|         <!-- TODO 芋艿:拼团 --> | ||||
|         <view | ||||
|           class="activity-box ss-p-x-38 ss-flex ss-row-between ss-col-center" | ||||
|           :class="activity.type === 1 ? 'seckill-box' : 'groupon-box'" | ||||
|  | @ -13,6 +14,7 @@ | |||
|                 :src="sheep.$url.static('/static/img/shop/goods/seckill-icon.png')" | ||||
|                 class="activity-icon" | ||||
|               /> | ||||
|               <!-- TODO 芋艿:拼团 --> | ||||
|               <image | ||||
|                 v-else-if="activity.type === 3" | ||||
|                 :src="sheep.$url.static('/static/img/shop/goods/groupon-icon.png')" | ||||
|  | @ -31,6 +33,7 @@ | |||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
| 
 | ||||
|   // TODO 芋艿:这里要迁移下; | ||||
|   const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png'); | ||||
|   const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png'); | ||||
| 
 | ||||
|  | @ -39,16 +42,14 @@ | |||
|       type: Array, | ||||
|       default() { | ||||
|         return []; | ||||
|       }, | ||||
|     }, | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   function onActivity(activity) { | ||||
|     const type = activity.type; | ||||
|     const typePath = type === 1 ? 'seckill' : type === 3 ? 'groupon' : undefined; | ||||
|     if (!typePath) { | ||||
|       return; | ||||
|     } | ||||
|     const typePath = type === 1 ? 'seckill' : | ||||
|       type === 2 ? 'TODO 拼团' : 'groupon'; | ||||
|     sheep.$router.go(`/pages/goods/${typePath}`, { | ||||
|       id: activity.id, | ||||
|     }); | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| <!-- 商品详情:描述卡片 --> | ||||
| <template> | ||||
|   <view class="detail-content-card bg-white ss-m-x-20 ss-p-t-20"> | ||||
|     <view class="card-header ss-flex ss-col-center ss-m-b-30 ss-m-l-20"> | ||||
|  | @ -5,12 +6,15 @@ | |||
|       <view class="title ss-m-l-20 ss-m-r-20">详情</view> | ||||
|     </view> | ||||
|     <view class="card-content"> | ||||
|       <mp-html :content="content"></mp-html> | ||||
|       <mp-html :content="content" /> | ||||
|     </view> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   const { safeAreaInsets } = sheep.$platform.device; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     content: { | ||||
|       type: String, | ||||
|  | @ -45,10 +49,4 @@ | |||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   :deep() { | ||||
|     image { | ||||
|       display: block; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ | |||
|   import { onPageScroll } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import throttle from '@/sheep/helper/throttle.js'; | ||||
|   import { showMenuTools } from '@/sheep/hooks/useModal'; | ||||
|   import { showMenuTools, closeMenuTools } from '@/sheep/hooks/useModal'; | ||||
| 
 | ||||
|   const sys_statusBar = sheep.$platform.device.statusBarHeight; | ||||
|   const sys_navBar = sheep.$platform.navbar; | ||||
|  | @ -98,8 +98,7 @@ | |||
| 
 | ||||
|   function getCommentCardNode() { | ||||
|     return new Promise((res, rej) => { | ||||
|       uni | ||||
|         .createSelectorQuery() | ||||
|       uni.createSelectorQuery() | ||||
|         .select('.detail-comment-selector') | ||||
|         .boundingClientRect((data) => { | ||||
|           if (data) { | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| <template> | ||||
|   <view v-if="state.list.length > 0" class="groupon-list detail-card ss-p-x-20"> | ||||
|     <view class="join-activity ss-flex ss-row-between ss-m-t-30"> | ||||
|       <!-- todo: 接口缺少总数 --> | ||||
|       <view class="">已有{{ state.list.length }}人参与活动</view> | ||||
|       <text class="cicon-forward"></text> | ||||
|     </view> | ||||
|  | @ -36,7 +37,7 @@ | |||
|   import { onMounted, reactive } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { useDurationTime } from '@/sheep/hooks/useGoods'; | ||||
|   import CombinationApi from '@/sheep/api/promotion/combination'; | ||||
|   import CombinationApi from "@/sheep/api/promotion/combination"; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     modelValue: { | ||||
|  | @ -73,7 +74,7 @@ | |||
|   onMounted(async () => { | ||||
|     // 查询参团记录 | ||||
|     // status = 0 表示未成团 | ||||
|     const { data } = await CombinationApi.getHeadCombinationRecordList(props.modelValue.id, 0, 10); | ||||
|     const { data } = await CombinationApi.getHeadCombinationRecordList(props.modelValue.id, 0 , 10); | ||||
|     state.list = data; | ||||
|   }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -7,16 +7,12 @@ | |||
|     <detailSkeleton v-if="state.skeletonLoading" /> | ||||
|     <!-- 下架/售罄提醒 --> | ||||
|     <s-empty | ||||
|       v-else-if=" | ||||
|         state.goodsInfo === null || | ||||
|         state.activity.status !== 0 || | ||||
|         state.activity.endTime < new Date().getTime() | ||||
|       " | ||||
|       text="活动不存在或已结束" | ||||
|       icon="/static/soldout-empty.png" | ||||
|       showAction | ||||
|       actionText="返回上一页" | ||||
|       @clickAction="sheep.$router.back()" | ||||
|         v-else-if="state.goodsInfo === null || state.activity.status !== 0 || state.activity.endTime < new Date().getTime()" | ||||
|         text="活动不存在或已结束" | ||||
|         icon="/static/soldout-empty.png" | ||||
|         showAction | ||||
|         actionText="返回上一页" | ||||
|         @clickAction="sheep.$router.back()" | ||||
|     /> | ||||
|     <block v-else> | ||||
|       <view class="detail-swiper-selector"> | ||||
|  | @ -51,10 +47,13 @@ | |||
|                 </view> | ||||
|               </view> | ||||
|               <view class="ss-flex ss-row-between"> | ||||
|                 <view class="origin-price ss-flex ss-col-center" v-if="state.goodsInfo.price"> | ||||
|                 <view | ||||
|                   class="origin-price ss-flex ss-col-center" | ||||
|                   v-if="state.goodsInfo.price" | ||||
|                 > | ||||
|                   单买价: | ||||
|                   <view class="origin-price-text"> | ||||
|                     {{ fen2yuan(state.goodsInfo.marketPrice) }} | ||||
|                     {{ fen2yuan(state.goodsInfo.price) }} | ||||
|                   </view> | ||||
|                 </view> | ||||
|               </view> | ||||
|  | @ -80,7 +79,7 @@ | |||
|         <!-- 功能卡片 --> | ||||
|         <view class="detail-cell-card detail-card ss-flex-col"> | ||||
|           <!-- 规格 --> | ||||
|           <detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" /> | ||||
|           <detail-cell-sku :sku="state.selectedSkuPrice" @tap="state.showSelectSku = true" /> | ||||
|         </view> | ||||
| 
 | ||||
|         <!-- 参团列表 --> | ||||
|  | @ -104,6 +103,7 @@ | |||
|       <detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" /> | ||||
| 
 | ||||
|       <!-- 商品tabbar --> | ||||
|       <!-- TODO: 已售罄、预热 判断 设计--> | ||||
|       <detail-tabbar v-model="state.goodsInfo"> | ||||
|         <view class="buy-box ss-flex ss-col-center ss-p-r-20"> | ||||
|           <button | ||||
|  | @ -123,14 +123,7 @@ | |||
|             " | ||||
|             :disabled="state.goodsInfo.stock === 0 || state.activity.status !== 0" | ||||
|           > | ||||
|             <view class="btn-price">{{ | ||||
|               fen2yuan( | ||||
|                 state.selectedSku.price * state.selectedSku.count || | ||||
|                   state.activity.price * state.selectedSku.count || | ||||
|                   state.goodsInfo.price * state.selectedSku.count || | ||||
|                   state.goodsInfo.price, | ||||
|               ) | ||||
|             }}</view> | ||||
|             <view class="btn-price">{{ fen2yuan(state.activity.price || state.goodsInfo.price) }}</view> | ||||
|             <view v-if="state.activity.startTime > new Date().getTime()">未开始</view> | ||||
|             <view v-else-if="state.activity.endTime <= new Date().getTime()">已结束</view> | ||||
|             <view v-else> | ||||
|  | @ -148,7 +141,7 @@ | |||
|   import { reactive, computed } from 'vue'; | ||||
|   import { onLoad, onPageScroll } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
|   import { isEmpty } from 'lodash'; | ||||
|   import detailNavbar from './components/detail/detail-navbar.vue'; | ||||
|   import detailCellSku from './components/detail/detail-cell-sku.vue'; | ||||
|   import detailTabbar from './components/detail/detail-tabbar.vue'; | ||||
|  | @ -156,28 +149,29 @@ | |||
|   import detailCommentCard from './components/detail/detail-comment-card.vue'; | ||||
|   import detailContentCard from './components/detail/detail-content-card.vue'; | ||||
|   import grouponCardList from './components/groupon/groupon-card-list.vue'; | ||||
|   import { useDurationTime, formatGoodsSwiper, fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import CombinationApi from '@/sheep/api/promotion/combination'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { SharePageEnum } from '@/sheep/helper/const'; | ||||
|   import {useDurationTime, formatGoodsSwiper, fen2yuan} from '@/sheep/hooks/useGoods'; | ||||
|   import CombinationApi from "@/sheep/api/promotion/combination"; | ||||
|   import SpuApi from "@/sheep/api/product/spu"; | ||||
| 
 | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/goods/groupon-bg.png'); | ||||
|   const btnBg = sheep.$url.css('/static/img/shop/goods/groupon-btn.png'); | ||||
|   const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png'); | ||||
|   const disabledBtnBg = sheep.$url.css( | ||||
|     '/static/img/shop/goods/activity-btn-disabled.png', | ||||
|   ); | ||||
|   const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png'); | ||||
| 
 | ||||
|   onPageScroll(() => {}); | ||||
|   const state = reactive({ | ||||
|     skeletonLoading: true, // 骨架屏 | ||||
|     goodsId: 0, // 商品ID | ||||
|     goodsInfo: {}, // 商品信息 | ||||
|     goodsSwiper: [], // 商品轮播图 | ||||
|     showSelectSku: false, // 显示规格弹框 | ||||
|     selectedSku: {}, // 选中的规格属性 | ||||
|     activity: {}, // 团购活动 | ||||
|     grouponId: 0, // 团购ID | ||||
|     grouponNum: 0, // 团购人数 | ||||
|     grouponAction: 'create', // 团购操作 | ||||
|     skeletonLoading: true,  // 骨架屏 | ||||
|     goodsId: 0,             // 商品ID | ||||
|     goodsInfo: {},          // 商品信息 | ||||
|     goodsSwiper: [],        // 商品轮播图 | ||||
|     showSelectSku: false,   // 显示规格弹框 | ||||
|     selectedSkuPrice: {},   // 选中的规格价格 | ||||
|     activity: {},           // 团购活动 | ||||
|     grouponId: 0,           // 团购ID | ||||
|     grouponNum: 0,          // 团购人数 | ||||
|     grouponAction: 'create',  // 团购操作 | ||||
|     combinationHeadId: null, // 拼团团长编号 | ||||
|   }); | ||||
| 
 | ||||
|  | @ -188,7 +182,7 @@ | |||
| 
 | ||||
|   // 规格变更 | ||||
|   function onSkuChange(e) { | ||||
|     state.selectedSku = e; | ||||
|     state.selectedSkuPrice = e; | ||||
|   } | ||||
| 
 | ||||
|   function onSkuClose() { | ||||
|  | @ -204,7 +198,6 @@ | |||
| 
 | ||||
|   /** | ||||
|    * 去参团 | ||||
|    * | ||||
|    * @param record 团长的团购记录 | ||||
|    */ | ||||
|   function onJoinGroupon(record) { | ||||
|  | @ -233,6 +226,7 @@ | |||
|   } | ||||
| 
 | ||||
|   // 分享信息 | ||||
|   // TODO @芋艿:分享的接入 | ||||
|   const shareInfo = computed(() => { | ||||
|     if (isEmpty(state.activity)) return {}; | ||||
|     return sheep.$platform.share.getShareInfo( | ||||
|  | @ -240,7 +234,7 @@ | |||
|         title: state.activity.name, | ||||
|         image: sheep.$url.cdn(state.goodsInfo.picUrl), | ||||
|         params: { | ||||
|           page: SharePageEnum.GROUPON.value, | ||||
|           page: '3', | ||||
|           query: state.activity.id, | ||||
|         }, | ||||
|       }, | ||||
|  | @ -258,43 +252,18 @@ | |||
|     // 非法参数 | ||||
|     if (!options.id) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     state.grouponId = options.id; | ||||
|     // 加载活动信息 | ||||
|     const { code, data: activity } = await CombinationApi.getCombinationActivity(state.grouponId); | ||||
|     if (code !== 0) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     state.activity = activity; | ||||
|     // 加载商品信息 | ||||
|     const { data: spu } = await SpuApi.getSpuDetail(activity.spuId); | ||||
|     if (code !== 0) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     state.goodsId = spu.id; | ||||
| 
 | ||||
|     // 默认显示最低价 | ||||
|     spu.price = activity.products.reduce((min, product) => { | ||||
|       return Math.min(min, product.combinationPrice || Infinity); | ||||
|     }, Infinity); | ||||
| 
 | ||||
|     // 价格、库存使用活动的 | ||||
|     spu.skus.forEach((sku) => { | ||||
|       const product = activity.products.find((product) => product.skuId === sku.id); | ||||
|       if (product) { | ||||
|         sku.price = product.combinationPrice; | ||||
|       } else { | ||||
|         // 找不到可能是没配置,则不能发起秒杀 | ||||
|         sku.stock = 0; | ||||
|       } | ||||
|     activity.products.forEach(product => { | ||||
|       spu.price = Math.min(spu.price, product.combinationPrice); // 设置 SPU 的最低价格 | ||||
|     }); | ||||
| 
 | ||||
|     // 关闭骨架屏 | ||||
|     state.skeletonLoading = false; | ||||
|     if (code === 0) { | ||||
|  | @ -506,7 +475,8 @@ | |||
|   } | ||||
| 
 | ||||
|   .groupon-box { | ||||
|     background: v-bind(grouponBg) no-repeat; | ||||
|     background: v-bind(grouponBg) | ||||
|       no-repeat; | ||||
|     background-size: 100% 100%; | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,407 +1,362 @@ | |||
| <template> | ||||
|   <s-layout | ||||
|     navbar="normal" | ||||
|     :leftWidth="0" | ||||
|     :rightWidth="0" | ||||
|     tools="search" | ||||
|     :defaultSearch="state.keyword" | ||||
|     @search="onSearch" | ||||
|   > | ||||
|     <!-- 筛选 --> | ||||
|     <su-sticky bgColor="#fff"> | ||||
|       <view class="ss-flex"> | ||||
|         <view class="ss-flex-1"> | ||||
|           <su-tabs | ||||
|             :list="state.tabList" | ||||
|             :scrollable="false" | ||||
|             @change="onTabsChange" | ||||
|             :current="state.currentTab" | ||||
|           /> | ||||
|         </view> | ||||
|         <view class="list-icon" @tap="state.iconStatus = !state.iconStatus"> | ||||
|           <text v-if="state.iconStatus" class="sicon-goods-list" /> | ||||
|           <text v-else class="sicon-goods-card" /> | ||||
|         </view> | ||||
|       </view> | ||||
|     </su-sticky> | ||||
| 	<s-layout navbar="normal" :leftWidth="0" :rightWidth="0" tools="search" :defaultSearch="state.keyword" | ||||
| 		@search="onSearch"> | ||||
| 		<!-- 筛选 --> | ||||
| 		<su-sticky bgColor="#fff"> | ||||
| 			<view class="ss-flex"> | ||||
| 				<view class="ss-flex-1"> | ||||
| 					<su-tabs :list="state.tabList" :scrollable="false" @change="onTabsChange" | ||||
|                    :current="state.currentTab" /> | ||||
| 				</view> | ||||
| 				<view class="list-icon" @tap="state.iconStatus = !state.iconStatus"> | ||||
| 					<text v-if="state.iconStatus" class="sicon-goods-list" /> | ||||
| 					<text v-else class="sicon-goods-card" /> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</su-sticky> | ||||
| 
 | ||||
|     <!-- 弹窗 --> | ||||
|     <su-popup | ||||
|       :show="state.showFilter" | ||||
|       type="top" | ||||
|       round="10" | ||||
|       :space="sys_navBar + 38" | ||||
|       backgroundColor="#F6F6F6" | ||||
|       :zIndex="10" | ||||
|       @close="state.showFilter = false" | ||||
|     > | ||||
|       <view class="filter-list-box"> | ||||
|         <view | ||||
|           class="filter-item" | ||||
|           v-for="(item, index) in state.tabList[state.currentTab].list" | ||||
|           :key="item.value" | ||||
|           :class="[{ 'filter-item-active': index === state.curFilter }]" | ||||
|           @tap="onFilterItem(index)" | ||||
|         > | ||||
|           {{ item.label }} | ||||
|         </view> | ||||
|       </view> | ||||
|     </su-popup> | ||||
| 		<!-- 弹窗 --> | ||||
| 		<su-popup :show="state.showFilter" type="top" round="10" :space="sys_navBar + 38" backgroundColor="#F6F6F6" | ||||
| 			:zIndex="10" @close="state.showFilter = false"> | ||||
| 			<view class="filter-list-box"> | ||||
| 				<view class="filter-item" v-for="(item, index) in state.tabList[state.currentTab].list" | ||||
| 					:key="item.value" :class="[{ 'filter-item-active': index === state.curFilter }]" | ||||
| 					@tap="onFilterItem(index)"> | ||||
| 					{{ item.label }} | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</su-popup> | ||||
| 
 | ||||
|     <!-- 情况一:单列布局 --> | ||||
|     <view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20"> | ||||
|       <view | ||||
|         class="ss-p-l-20 ss-p-r-20 ss-m-b-20" | ||||
|         v-for="item in state.pagination.list" | ||||
|         :key="item.id" | ||||
|       > | ||||
|         <s-goods-column | ||||
| 		<view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20"> | ||||
| 			<view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id"> | ||||
| 				<s-goods-column | ||||
|           class="" | ||||
|           size="lg" | ||||
|           :data="item" | ||||
|           :topRadius="10" | ||||
|           :bottomRadius="10" | ||||
|           @click="sheep.$router.go('/pages/goods/index', { id: item.id })" | ||||
| 					@click="sheep.$router.go('/pages/goods/index', { id: item.id })" | ||||
|         /> | ||||
|       </view> | ||||
|     </view> | ||||
| 			</view> | ||||
| 		</view> | ||||
|     <!-- 情况二:双列布局 --> | ||||
|     <view | ||||
|       v-if="!state.iconStatus && state.pagination.total > 0" | ||||
|       class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top" | ||||
|     > | ||||
|       <view class="goods-list-box"> | ||||
|         <view class="left-list" v-for="item in state.leftGoodsList" :key="item.id"> | ||||
|           <s-goods-column | ||||
|     <view v-if="!state.iconStatus && state.pagination.total > 0" | ||||
| 			class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top"> | ||||
| 			<view class="goods-list-box"> | ||||
| 				<view class="left-list" v-for="item in state.leftGoodsList" :key="item.id"> | ||||
| 					<s-goods-column | ||||
|             class="goods-md-box" | ||||
|             size="md" | ||||
|             :data="item" | ||||
|             :topRadius="10" | ||||
|             :bottomRadius="10" | ||||
|             @click="sheep.$router.go('/pages/goods/index', { id: item.id })" | ||||
|             @getHeight="mountMasonry($event, 'left')" | ||||
| 						@click="sheep.$router.go('/pages/goods/index', { id: item.id })" | ||||
| 						@getHeight="mountMasonry($event, 'left')" | ||||
|           > | ||||
|             <template v-slot:cart> | ||||
|               <button class="ss-reset-button cart-btn" /> | ||||
|             </template> | ||||
|           </s-goods-column> | ||||
|         </view> | ||||
|       </view> | ||||
|       <view class="goods-list-box"> | ||||
|         <view class="right-list" v-for="item in state.rightGoodsList" :key="item.id"> | ||||
|           <s-goods-column | ||||
| 						<template v-slot:cart> | ||||
| 							<button class="ss-reset-button cart-btn" /> | ||||
| 						</template> | ||||
| 					</s-goods-column> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 			<view class="goods-list-box"> | ||||
| 				<view class="right-list" v-for="item in state.rightGoodsList" :key="item.id"> | ||||
| 					<s-goods-column | ||||
|             class="goods-md-box" | ||||
|             size="md" | ||||
|             :topRadius="10" | ||||
|             :bottomRadius="10" | ||||
|             :data="item" | ||||
|             @click="sheep.$router.go('/pages/goods/index', { id: item.id })" | ||||
|             @getHeight="mountMasonry($event, 'right')" | ||||
| 						@click="sheep.$router.go('/pages/goods/index', { id: item.id })" | ||||
| 						@getHeight="mountMasonry($event, 'right')" | ||||
|           > | ||||
|             <template v-slot:cart> | ||||
|               <button class="ss-reset-button cart-btn" /> | ||||
|             </template> | ||||
|           </s-goods-column> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <uni-load-more | ||||
|       v-if="state.pagination.total > 0" | ||||
|       :status="state.loadStatus" | ||||
|       :content-text="{ | ||||
| 						<template v-slot:cart> | ||||
| 							<button class="ss-reset-button cart-btn" /> | ||||
| 						</template> | ||||
| 					</s-goods-column> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 		<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{ | ||||
|         contentdown: '上拉加载更多', | ||||
|       }" | ||||
|       @tap="loadMore" | ||||
|     /> | ||||
|     <s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" /> | ||||
|   </s-layout> | ||||
|       }" @tap="loadMore" /> | ||||
| 		<s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" /> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { reactive, ref } from 'vue'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 	import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import _ from 'lodash'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|   import { appendSettlementProduct } from '@/sheep/hooks/useGoods'; | ||||
| 
 | ||||
|   const sys_navBar = sheep.$platform.navbar; | ||||
|   const emits = defineEmits(['close', 'change']); | ||||
| 	const sys_navBar = sheep.$platform.navbar; | ||||
| 	const emits = defineEmits(['close', 'change']); | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     pagination: { | ||||
|       list: [], | ||||
| 	const state = reactive({ | ||||
| 		pagination: { | ||||
| 			list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 6, | ||||
|     }, | ||||
|     currentSort: undefined, | ||||
|     currentOrder: undefined, | ||||
|     currentTab: 0, // 当前选中的 tab | ||||
|     curFilter: 0, // 当前选中的 list 筛选项 | ||||
|     showFilter: false, | ||||
|     iconStatus: false, // true - 单列布局;false - 双列布局 | ||||
| 			pageSize: 6, | ||||
| 		}, | ||||
| 		currentSort: undefined, | ||||
| 		currentOrder: undefined, | ||||
| 		currentTab: 0, // 当前选中的 tab | ||||
| 		curFilter: 0, // 当前选中的 list 筛选项 | ||||
| 		showFilter: false, | ||||
| 		iconStatus: false, // true - 单列布局;false - 双列布局 | ||||
|     keyword: '', | ||||
|     categoryId: 0, | ||||
|     tabList: [ | ||||
|       { | ||||
|         name: '综合推荐', | ||||
|         list: [ | ||||
|           { | ||||
|             label: '综合推荐', | ||||
|           }, | ||||
|           { | ||||
|             label: '价格升序', | ||||
|             sort: 'price', | ||||
|             order: true, | ||||
|           }, | ||||
|           { | ||||
|             label: '价格降序', | ||||
|             sort: 'price', | ||||
|             order: false, | ||||
|           }, | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         name: '销量', | ||||
|         sort: 'salesCount', | ||||
|         order: false, | ||||
|       }, | ||||
|       { | ||||
|         name: '新品优先', | ||||
|         value: 'createTime', | ||||
|         order: false, | ||||
|       }, | ||||
|     ], | ||||
|     loadStatus: '', | ||||
|     leftGoodsList: [], // 双列布局 - 左侧商品 | ||||
|     rightGoodsList: [], // 双列布局 - 右侧商品 | ||||
|   }); | ||||
| 		tabList: [{ | ||||
| 				name: '综合推荐', | ||||
| 				list: [{ | ||||
| 						label: '综合推荐' | ||||
| 					}, | ||||
| 					{ | ||||
| 						label: '价格升序', | ||||
| 						sort: 'price', | ||||
| 						order: true, | ||||
| 					}, | ||||
| 					{ | ||||
| 						label: '价格降序', | ||||
| 						sort: 'price', | ||||
| 						order: false, | ||||
| 					}, | ||||
| 				], | ||||
| 			}, | ||||
| 			{ | ||||
| 				name: '销量', | ||||
| 				sort: 'salesCount', | ||||
| 				order: false | ||||
| 			}, | ||||
| 			{ | ||||
| 				name: '新品优先', | ||||
| 				value: 'createTime', | ||||
|         order: false | ||||
| 			}, | ||||
| 		], | ||||
| 		loadStatus: '', | ||||
| 		leftGoodsList: [], // 双列布局 - 左侧商品 | ||||
| 		rightGoodsList: [], // 双列布局 - 右侧商品 | ||||
| 	}); | ||||
| 
 | ||||
|   // 加载瀑布流 | ||||
|   let count = 0; | ||||
|   let leftHeight = 0; | ||||
|   let rightHeight = 0; | ||||
| 	// 加载瀑布流 | ||||
| 	let count = 0; | ||||
| 	let leftHeight = 0; | ||||
| 	let rightHeight = 0; | ||||
| 
 | ||||
|   // 处理双列布局 leftGoodsList + rightGoodsList | ||||
|   function mountMasonry(height = 0, where = 'left') { | ||||
|     if (where === 'left') { | ||||
|       leftHeight += height; | ||||
|     } else { | ||||
|       rightHeight += height; | ||||
|     } | ||||
|     if (!state.pagination.list[count]) { | ||||
| 	function mountMasonry(height = 0, where = 'left') { | ||||
| 		if (!state.pagination.list[count]) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (leftHeight <= rightHeight) { | ||||
|       state.leftGoodsList.push(state.pagination.list[count]); | ||||
|     } else { | ||||
|       state.rightGoodsList.push(state.pagination.list[count]); | ||||
|     } | ||||
|     count++; | ||||
|   } | ||||
| 		if (where === 'left') { | ||||
| 			leftHeight += height; | ||||
| 		} else { | ||||
| 			rightHeight += height; | ||||
| 		} | ||||
| 		if (leftHeight <= rightHeight) { | ||||
| 			state.leftGoodsList.push(state.pagination.list[count]); | ||||
| 		} else { | ||||
| 			state.rightGoodsList.push(state.pagination.list[count]); | ||||
| 		} | ||||
| 		count++; | ||||
| 	} | ||||
| 
 | ||||
|   // 清空列表 | ||||
|   function emptyList() { | ||||
| 	function emptyList() { | ||||
|     resetPagination(state.pagination); | ||||
|     state.leftGoodsList = []; | ||||
|     state.rightGoodsList = []; | ||||
|     count = 0; | ||||
|     leftHeight = 0; | ||||
|     rightHeight = 0; | ||||
|   } | ||||
| 		state.rightGoodsList = []; | ||||
| 		count = 0; | ||||
| 		leftHeight = 0; | ||||
| 		rightHeight = 0; | ||||
| 	} | ||||
| 
 | ||||
|   // 搜索 | ||||
|   function onSearch(e) { | ||||
|     state.keyword = e; | ||||
|     emptyList(); | ||||
|     getList(state.currentSort, state.currentOrder); | ||||
|   } | ||||
| 	// 搜索 | ||||
| 	function onSearch(e) { | ||||
| 		state.keyword = e; | ||||
| 		emptyList(); | ||||
| 		getList(state.currentSort, state.currentOrder); | ||||
| 	} | ||||
| 
 | ||||
|   // 点击 | ||||
|   function onTabsChange(e) { | ||||
| 	// 点击 | ||||
| 	function onTabsChange(e) { | ||||
|     // 如果点击的是【综合推荐】,则直接展开或者收起筛选项 | ||||
|     if (state.tabList[e.index].list) { | ||||
|       state.currentTab = e.index; | ||||
|       state.showFilter = !state.showFilter; | ||||
|       return; | ||||
|     } | ||||
| 		if (state.tabList[e.index].list) { | ||||
| 			state.currentTab = e.index; | ||||
| 			state.showFilter = !state.showFilter; | ||||
| 			return; | ||||
| 		} | ||||
|     state.showFilter = false; | ||||
| 
 | ||||
|     // 如果点击的是【销量】或者【新品优先】,则直接切换 tab | ||||
|     if (e.index === state.currentTab) { | ||||
|       return; | ||||
|     } | ||||
| 		if (e.index === state.currentTab) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
|     state.currentTab = e.index; | ||||
|     state.currentSort = e.sort; | ||||
|     state.currentOrder = e.order; | ||||
|     emptyList(); | ||||
|     getList(e.sort, e.order); | ||||
|   } | ||||
| 		emptyList(); | ||||
| 		getList(e.sort, e.order); | ||||
| 	} | ||||
| 
 | ||||
|   // 点击 tab 的 list 筛选项 | ||||
|   const onFilterItem = (val) => { | ||||
| 	// 点击 tab 的 list 筛选项 | ||||
| 	const onFilterItem = (val) => { | ||||
|     // 如果点击的是当前的筛选项,则直接收起筛选项,不要加载数据 | ||||
|     // 这里选择 tabList[0] 的原因,是目前只有它有 list | ||||
|     if ( | ||||
|       state.currentSort === state.tabList[0].list[val].sort && | ||||
|       state.currentOrder === state.tabList[0].list[val].order | ||||
|     ) { | ||||
|       state.showFilter = false; | ||||
|       return; | ||||
|     } | ||||
| 		if (state.currentSort === state.tabList[0].list[val].sort | ||||
|       && state.currentOrder === state.tabList[0].list[val].order) { | ||||
| 			state.showFilter = false; | ||||
| 			return; | ||||
| 		} | ||||
|     state.showFilter = false; | ||||
| 
 | ||||
|     // 设置筛选条件 | ||||
|     state.curFilter = val; | ||||
|     state.tabList[0].name = state.tabList[0].list[val].label; | ||||
|     state.currentSort = state.tabList[0].list[val].sort; | ||||
|     state.currentOrder = state.tabList[0].list[val].order; | ||||
|     // 清空 + 加载数据 | ||||
| 		state.curFilter = val; | ||||
| 		state.tabList[0].name = state.tabList[0].list[val].label; | ||||
| 		state.currentSort = state.tabList[0].list[val].sort; | ||||
| 		state.currentOrder = state.tabList[0].list[val].order; | ||||
| 		// 清空 + 加载数据 | ||||
|     emptyList(); | ||||
|     getList(); | ||||
|   }; | ||||
| 		getList(); | ||||
| 	} | ||||
| 
 | ||||
|   async function getList() { | ||||
|     state.loadStatus = 'loading'; | ||||
|     const { code, data } = await SpuApi.getSpuPage({ | ||||
| 	async function getList() { | ||||
| 		state.loadStatus = 'loading'; | ||||
| 		const { code, data } = await SpuApi.getSpuPage({ | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       sortField: state.currentSort, | ||||
|       sortAsc: state.currentOrder, | ||||
|       categoryId: state.categoryId, | ||||
|       keyword: state.keyword, | ||||
|     }); | ||||
| 			sortField: state.currentSort, | ||||
| 			sortAsc: state.currentOrder, | ||||
| 			categoryId: state.categoryId, | ||||
| 			keyword: state.keyword, | ||||
| 		}); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     // 拼接结算信息(营销) | ||||
|     await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => { | ||||
|       if (res.code !== 0) { | ||||
|         return; | ||||
|       } | ||||
|       appendSettlementProduct(data.list, res.data); | ||||
|     }); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list) | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|     mountMasonry(); | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   // 加载更多 | ||||
|   function loadMore() { | ||||
| 	// 加载更多 | ||||
| 	function loadMore() { | ||||
|     if (state.loadStatus === 'noMore') { | ||||
|       return; | ||||
|     } | ||||
|     state.pagination.pageNo++; | ||||
|     getList(state.currentSort, state.currentOrder); | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   onLoad((options) => { | ||||
|     state.categoryId = options.categoryId; | ||||
|     state.keyword = options.keyword; | ||||
|     getList(state.currentSort, state.currentOrder); | ||||
|   }); | ||||
| 	onLoad((options) => { | ||||
| 		state.categoryId = options.categoryId; | ||||
| 		state.keyword = options.keyword; | ||||
| 		getList(state.currentSort, state.currentOrder); | ||||
| 	}); | ||||
| 
 | ||||
|   // 上拉加载更多 | ||||
|   onReachBottom(() => { | ||||
|     loadMore(); | ||||
|   }); | ||||
| 	// 上拉加载更多 | ||||
| 	onReachBottom(() => { | ||||
| 		loadMore(); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .goods-list-box { | ||||
|     width: 50%; | ||||
|     box-sizing: border-box; | ||||
| 	.goods-list-box { | ||||
| 		width: 50%; | ||||
| 		box-sizing: border-box; | ||||
| 
 | ||||
|     .left-list { | ||||
|       margin-right: 10rpx; | ||||
|       margin-bottom: 20rpx; | ||||
|     } | ||||
| 		.left-list { | ||||
| 			margin-right: 10rpx; | ||||
| 			margin-bottom: 20rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .right-list { | ||||
|       margin-left: 10rpx; | ||||
|       margin-bottom: 20rpx; | ||||
|     } | ||||
|   } | ||||
| 		.right-list { | ||||
| 			margin-left: 10rpx; | ||||
| 			margin-bottom: 20rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .goods-box { | ||||
|     &:nth-last-of-type(1) { | ||||
|       margin-bottom: 0 !important; | ||||
|     } | ||||
| 	.goods-box { | ||||
| 		&:nth-last-of-type(1) { | ||||
| 			margin-bottom: 0 !important; | ||||
| 		} | ||||
| 
 | ||||
|     &:nth-child(2n) { | ||||
|       margin-right: 0; | ||||
|     } | ||||
|   } | ||||
| 		&:nth-child(2n) { | ||||
| 			margin-right: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .list-icon { | ||||
|     width: 80rpx; | ||||
| 	.list-icon { | ||||
| 		width: 80rpx; | ||||
| 
 | ||||
|     .sicon-goods-card { | ||||
|       font-size: 40rpx; | ||||
|     } | ||||
| 		.sicon-goods-card { | ||||
| 			font-size: 40rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .sicon-goods-list { | ||||
|       font-size: 40rpx; | ||||
|     } | ||||
|   } | ||||
| 		.sicon-goods-list { | ||||
| 			font-size: 40rpx; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .goods-card { | ||||
|     margin-left: 20rpx; | ||||
|   } | ||||
| 	.goods-card { | ||||
| 		margin-left: 20rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .list-filter-tabs { | ||||
|     background-color: #fff; | ||||
|   } | ||||
| 	.list-filter-tabs { | ||||
| 		background-color: #fff; | ||||
| 	} | ||||
| 
 | ||||
|   .filter-list-box { | ||||
|     padding: 28rpx 52rpx; | ||||
| 	.filter-list-box { | ||||
| 		padding: 28rpx 52rpx; | ||||
| 
 | ||||
|     .filter-item { | ||||
|       font-size: 28rpx; | ||||
|       font-weight: 500; | ||||
|       color: #333333; | ||||
|       line-height: normal; | ||||
|       margin-bottom: 24rpx; | ||||
| 		.filter-item { | ||||
| 			font-size: 28rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: #333333; | ||||
| 			line-height: normal; | ||||
| 			margin-bottom: 24rpx; | ||||
| 
 | ||||
|       &:nth-last-child(1) { | ||||
|         margin-bottom: 0; | ||||
|       } | ||||
|     } | ||||
| 			&:nth-last-child(1) { | ||||
| 				margin-bottom: 0; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|     .filter-item-active { | ||||
|       color: var(--ui-BG-Main); | ||||
|     } | ||||
|   } | ||||
| 		.filter-item-active { | ||||
| 			color: var(--ui-BG-Main); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .tab-item { | ||||
|     height: 50px; | ||||
|     position: relative; | ||||
|     z-index: 11; | ||||
| 	.tab-item { | ||||
| 		height: 50px; | ||||
| 		position: relative; | ||||
| 		z-index: 11; | ||||
| 
 | ||||
|     .tab-title { | ||||
|       font-size: 30rpx; | ||||
|     } | ||||
| 		.tab-title { | ||||
| 			font-size: 30rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .cur-tab-title { | ||||
|       font-weight: $font-weight-bold; | ||||
|     } | ||||
| 		.cur-tab-title { | ||||
| 			font-weight: $font-weight-bold; | ||||
| 		} | ||||
| 
 | ||||
|     .tab-line { | ||||
|       width: 60rpx; | ||||
|       height: 6rpx; | ||||
|       border-radius: 6rpx; | ||||
|       position: absolute; | ||||
|       left: 50%; | ||||
|       transform: translateX(-50%); | ||||
|       bottom: 10rpx; | ||||
|       background-color: var(--ui-BG-Main); | ||||
|       z-index: 12; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 		.tab-line { | ||||
| 			width: 60rpx; | ||||
| 			height: 6rpx; | ||||
| 			border-radius: 6rpx; | ||||
| 			position: absolute; | ||||
| 			left: 50%; | ||||
| 			transform: translateX(-50%); | ||||
| 			bottom: 10rpx; | ||||
| 			background-color: var(--ui-BG-Main); | ||||
| 			z-index: 12; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,483 +0,0 @@ | |||
| <!-- 秒杀商品详情 --> | ||||
| <template> | ||||
|   <s-layout :onShareAppMessage="shareInfo" navbar="goods"> | ||||
|     <!-- 标题栏 --> | ||||
|     <detailNavbar /> | ||||
|     <!-- 骨架屏 --> | ||||
|     <detailSkeleton v-if="state.skeletonLoading" /> | ||||
|     <!-- 下架/售罄提醒 --> | ||||
|     <s-empty | ||||
|       v-else-if=" | ||||
|         state.goodsInfo === null || | ||||
|         state.goodsInfo.activity_type !== PromotionActivityTypeEnum.POINT.type | ||||
|       " | ||||
|       text="活动不存在或已结束" | ||||
|       icon="/static/soldout-empty.png" | ||||
|       showAction | ||||
|       actionText="再逛逛" | ||||
|       actionUrl="/pages/goods/list" | ||||
|     /> | ||||
|     <block v-else> | ||||
|       <view class="detail-swiper-selector"> | ||||
|         <!-- 商品图轮播 --> | ||||
|         <su-swiper | ||||
|           class="ss-m-b-14" | ||||
|           isPreview | ||||
|           :list="state.goodsSwiper" | ||||
|           dotStyle="tag" | ||||
|           imageMode="widthFix" | ||||
|           dotCur="bg-mask-40" | ||||
|           :seizeHeight="750" | ||||
|         /> | ||||
| 
 | ||||
|         <!-- 价格+标题 --> | ||||
|         <view class="title-card detail-card ss-p-y-40 ss-p-x-20"> | ||||
|           <view class="ss-flex ss-row-between ss-col-center ss-m-b-18"> | ||||
|             <view class="price-box ss-flex ss-col-bottom"> | ||||
|               <image | ||||
|                 :src="sheep.$url.static('/static/img/shop/goods/score1.svg')" | ||||
|                 class="point-img" | ||||
|               ></image> | ||||
|               <text class="point-text ss-m-r-16"> | ||||
|                 {{ getShowPrice.point }} | ||||
|                 {{ | ||||
|                   !getShowPrice.price || getShowPrice.price === 0 ? '' : `+¥${getShowPrice.price}` | ||||
|                 }} | ||||
|               </text> | ||||
|             </view> | ||||
|             <view class="sales-text"> | ||||
|               {{ formatExchange(state.goodsInfo.sales_show_type, state.goodsInfo.sales) }} | ||||
|             </view> | ||||
|           </view> | ||||
|           <view class="origin-price-text ss-m-b-60" v-if="state.goodsInfo.marketPrice"> | ||||
|             原价:¥{{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }} | ||||
|           </view> | ||||
|           <view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name || '' }}</view> | ||||
|           <view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view> | ||||
|         </view> | ||||
| 
 | ||||
|         <!-- 功能卡片 --> | ||||
|         <view class="detail-cell-card detail-card ss-flex-col"> | ||||
|           <detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" /> | ||||
|         </view> | ||||
|         <!-- 规格与数量弹框 --> | ||||
|         <s-select-seckill-sku | ||||
|           v-model="state.goodsInfo" | ||||
|           :show="state.showSelectSku" | ||||
|           :single-limit-count="activity.singleLimitCount" | ||||
|           @buy="onBuy" | ||||
|           @change="onSkuChange" | ||||
|           @close="state.showSelectSku = false" | ||||
|         /> | ||||
|       </view> | ||||
| 
 | ||||
|       <!-- 评价 --> | ||||
|       <detail-comment-card class="detail-comment-selector" :goodsId="state.goodsInfo.id" /> | ||||
|       <!-- 详情 --> | ||||
|       <detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" /> | ||||
| 
 | ||||
|       <!-- 详情tabbar --> | ||||
|       <detail-tabbar v-model="state.goodsInfo"> | ||||
|         <view class="buy-box ss-flex ss-col-center ss-p-r-20"> | ||||
|           <button | ||||
|             class="ss-reset-button origin-price-btn ss-flex-col" | ||||
|             v-if="state.goodsInfo.marketPrice" | ||||
|             @tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })" | ||||
|           > | ||||
|             <view> | ||||
|               <view class="btn-price">{{ fen2yuan(state.goodsInfo.marketPrice) }}</view> | ||||
|               <view>原价购买</view> | ||||
|             </view> | ||||
|           </button> | ||||
|           <button | ||||
|             class="ss-reset-button btn-box ss-flex-col" | ||||
|             @tap="state.showSelectSku = true" | ||||
|             :class="state.goodsInfo.stock != 0 ? 'check-btn-box' : 'disabled-btn-box'" | ||||
|             :disabled="state.goodsInfo.stock === 0" | ||||
|           > | ||||
|             <view class="price-box ss-flex"> | ||||
|               <image | ||||
|                 :src="sheep.$url.static('/static/img/shop/goods/score1.svg')" | ||||
|                 style="width: 36rpx; height: 36rpx; margin: 0 4rpx" | ||||
|               ></image> | ||||
|               <text class="point-text ss-m-r-16"> | ||||
|                 {{ getShowPrice.point }} | ||||
|                 {{ | ||||
|                   !getShowPrice.price || getShowPrice.price === 0 ? '' : `+¥${getShowPrice.price}` | ||||
|                 }} | ||||
|               </text> | ||||
|             </view> | ||||
|             <view v-if="state.goodsInfo.stock === 0">已售罄</view> | ||||
|             <view v-else>立即兑换</view> | ||||
|           </button> | ||||
|         </view> | ||||
|       </detail-tabbar> | ||||
|     </block> | ||||
|   </s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed, reactive, ref, unref } from 'vue'; | ||||
|   import { onLoad, onPageScroll } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
|   import { fen2yuan, formatExchange, formatGoodsSwiper } from '@/sheep/hooks/useGoods'; | ||||
|   import detailNavbar from './components/detail/detail-navbar.vue'; | ||||
|   import detailCellSku from './components/detail/detail-cell-sku.vue'; | ||||
|   import detailTabbar from './components/detail/detail-tabbar.vue'; | ||||
|   import detailSkeleton from './components/detail/detail-skeleton.vue'; | ||||
|   import detailCommentCard from './components/detail/detail-comment-card.vue'; | ||||
|   import detailContentCard from './components/detail/detail-content-card.vue'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { PromotionActivityTypeEnum, SharePageEnum } from '@/sheep/helper/const'; | ||||
|   import PointApi from '@/sheep/api/promotion/point'; | ||||
| 
 | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/goods/score-bg.png'); | ||||
|   const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png'); | ||||
|   const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png'); | ||||
|   const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png'); | ||||
|   const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png'); | ||||
| 
 | ||||
|   onPageScroll(() => {}); | ||||
|   const state = reactive({ | ||||
|     skeletonLoading: true, | ||||
|     goodsInfo: {}, | ||||
|     showSelectSku: false, | ||||
|     goodsSwiper: [], | ||||
|     selectedSku: {}, | ||||
|     showModel: false, | ||||
|     total: 0, | ||||
|     price: '', | ||||
|   }); | ||||
| 
 | ||||
|   // 规格变更 | ||||
|   function onSkuChange(e) { | ||||
|     state.selectedSku = e; | ||||
|   } | ||||
| 
 | ||||
|   // 立即购买 | ||||
|   function onBuy(sku) { | ||||
|     sheep.$router.go('/pages/order/confirm', { | ||||
|       data: JSON.stringify({ | ||||
|         order_type: 'goods', | ||||
|         buy_type: 'point', | ||||
|         pointActivityId: activity.value.id, | ||||
|         items: [ | ||||
|           { | ||||
|             skuId: sku.id, | ||||
|             count: sku.count, | ||||
|           }, | ||||
|         ], | ||||
|       }), | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 分享信息 | ||||
|   const shareInfo = computed(() => { | ||||
|     if (isEmpty(unref(activity))) return {}; | ||||
|     return sheep.$platform.share.getShareInfo( | ||||
|       { | ||||
|         title: activity.value.name, | ||||
|         image: sheep.$url.cdn(state.goodsInfo.picUrl), | ||||
|         params: { | ||||
|           page: SharePageEnum.POINT.value, | ||||
|           query: activity.value.id, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         type: 'goods', // 商品海报 | ||||
|         title: activity.value.name, // 商品标题 | ||||
|         image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图 | ||||
|         price: (getShowPrice.value.price || 0) + ` + ${getShowPrice.value.point} 积分`, // 积分价格 | ||||
|         marketPrice: fen2yuan(state.goodsInfo.marketPrice), // 商品原价 | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   const activity = ref(); | ||||
| 
 | ||||
|   const getShowPrice = computed(() => { | ||||
|     if (!isEmpty(state.selectedSku)) { | ||||
|       const sku = state.selectedSku; | ||||
|       return { | ||||
|         point: sku.point, | ||||
|         price: !sku.pointPrice ? '' : fen2yuan(sku.pointPrice), | ||||
|       }; | ||||
|     } | ||||
|     return { | ||||
|       point: activity.value.point, | ||||
|       price: !activity.value.price ? '' : fen2yuan(activity.value.price), | ||||
|     }; | ||||
|   }); | ||||
| 
 | ||||
|   // 查询活动 | ||||
|   const getActivity = async (id) => { | ||||
|     const { data } = await PointApi.getPointActivity(id); | ||||
|     if (!data) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     activity.value = data; | ||||
|     // 查询商品 | ||||
|     await getSpu(data.spuId); | ||||
|   }; | ||||
| 
 | ||||
|   // 查询商品 | ||||
|   const getSpu = async (id) => { | ||||
|     const { data } = await SpuApi.getSpuDetail(id); | ||||
|     if (!data) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     data.activity_type = PromotionActivityTypeEnum.POINT.type; | ||||
|     state.goodsInfo = data; | ||||
|     state.goodsInfo.stock = Math.min(data.stock, activity.value.stock); | ||||
|     // 处理轮播图 | ||||
|     state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls); | ||||
| 
 | ||||
|     // 价格、库存使用活动的 | ||||
|     data.skus.forEach((sku) => { | ||||
|       const product = activity.value.products.find((product) => product.skuId === sku.id); | ||||
|       if (product) { | ||||
|         sku.point = product.point; | ||||
|         sku.pointPrice = product.price; | ||||
|         sku.stock = Math.min(sku.stock, product.stock); | ||||
|         // 设置限购数量 | ||||
|         sku.limitCount = product.count; | ||||
|       } else { | ||||
|         // 找不到可能是没配置 | ||||
|         sku.stock = 0; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     state.skeletonLoading = false; | ||||
|   }; | ||||
| 
 | ||||
|   onLoad((options) => { | ||||
|     // 非法参数 | ||||
|     if (!options.id) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 查询活动 | ||||
|     getActivity(options.id); | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .disabled-btn-box[disabled] { | ||||
|     background-color: transparent; | ||||
|   } | ||||
| 
 | ||||
|   .detail-card { | ||||
|     background-color: $white; | ||||
|     margin: 14rpx 20rpx; | ||||
|     border-radius: 10rpx; | ||||
|     overflow: hidden; | ||||
|   } | ||||
| 
 | ||||
|   // 价格标题卡片 | ||||
|   .title-card { | ||||
|     width: 710rpx; | ||||
|     box-sizing: border-box; | ||||
|     background-size: 100% 100%; | ||||
|     border-radius: 10rpx; | ||||
|     background-image: v-bind(headerBg); | ||||
|     background-repeat: no-repeat; | ||||
| 
 | ||||
|     .price-box { | ||||
|       .point-img { | ||||
|         width: 36rpx; | ||||
|         height: 36rpx; | ||||
|         margin: 0 4rpx; | ||||
|       } | ||||
| 
 | ||||
|       .point-text { | ||||
|         font-size: 42rpx; | ||||
|         font-weight: 500; | ||||
|         color: #ff3000; | ||||
|         line-height: 36rpx; | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
| 
 | ||||
|       .price-text { | ||||
|         font-size: 42rpx; | ||||
|         font-weight: 500; | ||||
|         color: #ff3000; | ||||
|         line-height: 36rpx; | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .origin-price-text { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 400; | ||||
|       text-decoration: line-through; | ||||
|       color: $gray-c; | ||||
|       font-family: OPPOSANS; | ||||
|     } | ||||
| 
 | ||||
|     .sales-text { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: $gray-c; | ||||
|     } | ||||
| 
 | ||||
|     .discounts-box { | ||||
|       .discounts-tag { | ||||
|         padding: 4rpx 10rpx; | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 500; | ||||
|         border-radius: 4rpx; | ||||
|         color: var(--ui-BG-Main); | ||||
|         // background: rgba(#2aae67, 0.05); | ||||
|         background: var(--ui-BG-Main-tag); | ||||
|       } | ||||
| 
 | ||||
|       .discounts-title { | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 500; | ||||
|         color: var(--ui-BG-Main); | ||||
|         line-height: normal; | ||||
|       } | ||||
| 
 | ||||
|       .cicon-forward { | ||||
|         color: var(--ui-BG-Main); | ||||
|         font-size: 24rpx; | ||||
|         line-height: normal; | ||||
|         margin-top: 4rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .title-text { | ||||
|       font-size: 30rpx; | ||||
|       font-weight: bold; | ||||
|       line-height: 42rpx; | ||||
|     } | ||||
| 
 | ||||
|     .subtitle-text { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 400; | ||||
|       color: $dark-9; | ||||
|       line-height: 42rpx; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 购买 | ||||
|   .buy-box { | ||||
|     .check-btn-box { | ||||
|       width: 248rpx; | ||||
|       height: 80rpx; | ||||
|       font-size: 24rpx; | ||||
|       font-weight: 600; | ||||
|       margin-left: -36rpx; | ||||
|       background-image: v-bind(btnBg); | ||||
|       background-repeat: no-repeat; | ||||
|       background-size: 100% 100%; | ||||
|       color: #ffffff; | ||||
|       line-height: normal; | ||||
|       border-radius: 0px 40rpx 40rpx 0px; | ||||
|     } | ||||
| 
 | ||||
|     .disabled-btn-box { | ||||
|       width: 248rpx; | ||||
|       height: 80rpx; | ||||
|       font-size: 24rpx; | ||||
|       font-weight: 600; | ||||
|       margin-left: -36rpx; | ||||
|       background-image: v-bind(disabledBtnBg); | ||||
|       background-repeat: no-repeat; | ||||
|       background-size: 100% 100%; | ||||
|       color: #999999; | ||||
|       line-height: normal; | ||||
|       border-radius: 0px 40rpx 40rpx 0px; | ||||
|     } | ||||
| 
 | ||||
|     .btn-price { | ||||
|       font-family: OPPOSANS; | ||||
| 
 | ||||
|       &::before { | ||||
|         content: '¥'; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .origin-price-btn { | ||||
|       width: 236rpx; | ||||
|       height: 80rpx; | ||||
|       background: rgba(#ff5651, 0.1); | ||||
|       color: #ff6000; | ||||
|       border-radius: 40rpx 0px 0px 40rpx; | ||||
|       line-height: normal; | ||||
|       font-size: 24rpx; | ||||
|       font-weight: 500; | ||||
| 
 | ||||
|       .no-original { | ||||
|         font-size: 28rpx; | ||||
|       } | ||||
| 
 | ||||
|       .btn-title { | ||||
|         font-size: 28rpx; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   //秒杀卡片 | ||||
|   .seckill-box { | ||||
|     background: v-bind(seckillBg) no-repeat; | ||||
|     background-size: 100% 100%; | ||||
|   } | ||||
| 
 | ||||
|   .groupon-box { | ||||
|     background: v-bind(grouponBg) no-repeat; | ||||
|     background-size: 100% 100%; | ||||
|   } | ||||
| 
 | ||||
|   //活动卡片 | ||||
|   .activity-box { | ||||
|     width: 100%; | ||||
|     height: 80rpx; | ||||
|     box-sizing: border-box; | ||||
|     margin-bottom: 10rpx; | ||||
| 
 | ||||
|     .activity-title { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: #ffffff; | ||||
|       line-height: 42rpx; | ||||
| 
 | ||||
|       .activity-icon { | ||||
|         width: 38rpx; | ||||
|         height: 38rpx; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     .activity-go { | ||||
|       width: 70rpx; | ||||
|       height: 32rpx; | ||||
|       background: #ffffff; | ||||
|       border-radius: 16rpx; | ||||
|       font-weight: 500; | ||||
|       color: #ff6000; | ||||
|       font-size: 24rpx; | ||||
|       line-height: normal; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .model-box { | ||||
|     .title { | ||||
|       font-size: 36rpx; | ||||
|       font-weight: bold; | ||||
|       color: #333333; | ||||
|     } | ||||
| 
 | ||||
|     .subtitle { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: #333333; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  | @ -7,9 +7,7 @@ | |||
|     <detailSkeleton v-if="state.skeletonLoading" /> | ||||
|     <!-- 下架/售罄提醒 --> | ||||
|     <s-empty | ||||
|       v-else-if=" | ||||
|         state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill' || endTime.ms <= 0 | ||||
|       " | ||||
|       v-else-if="state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill'" | ||||
|       text="活动不存在或已结束" | ||||
|       icon="/static/soldout-empty.png" | ||||
|       showAction | ||||
|  | @ -65,13 +63,16 @@ | |||
|             <detail-progress :percent="state.percent" /> | ||||
|           </view> | ||||
| 
 | ||||
|           <view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name || '' }}</view> | ||||
|           <view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo?.name }}</view> | ||||
|           <view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view> | ||||
|         </view> | ||||
| 
 | ||||
|         <!-- 功能卡片 --> | ||||
|         <view class="detail-cell-card detail-card ss-flex-col"> | ||||
|           <detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" /> | ||||
|           <detail-cell-sku | ||||
|             :sku="state.selectedSku" | ||||
|             @tap="state.showSelectSku = true" | ||||
|           /> | ||||
|         </view> | ||||
|         <!-- 规格与数量弹框 --> | ||||
|         <s-select-seckill-sku | ||||
|  | @ -106,9 +107,7 @@ | |||
|           <button v-else class="ss-reset-button origin-price-btn ss-flex-col"> | ||||
|             <view | ||||
|               class="no-original" | ||||
|               :class=" | ||||
|                 state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : '' | ||||
|               " | ||||
|               :class="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : ''" | ||||
|             > | ||||
|               秒杀价 | ||||
|             </view> | ||||
|  | @ -137,11 +136,11 @@ | |||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed, reactive, ref, unref } from 'vue'; | ||||
|   import {reactive, computed, ref} from 'vue'; | ||||
|   import { onLoad, onPageScroll } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isEmpty, min } from 'lodash-es'; | ||||
|   import { fen2yuan, formatGoodsSwiper, useDurationTime } from '@/sheep/hooks/useGoods'; | ||||
|   import {isEmpty, min} from 'lodash'; | ||||
|   import {useDurationTime, formatGoodsSwiper, fen2yuan} from '@/sheep/hooks/useGoods'; | ||||
|   import detailNavbar from './components/detail/detail-navbar.vue'; | ||||
|   import detailCellSku from './components/detail/detail-cell-sku.vue'; | ||||
|   import detailTabbar from './components/detail/detail-tabbar.vue'; | ||||
|  | @ -149,13 +148,15 @@ | |||
|   import detailCommentCard from './components/detail/detail-comment-card.vue'; | ||||
|   import detailContentCard from './components/detail/detail-content-card.vue'; | ||||
|   import detailProgress from './components/detail/detail-progress.vue'; | ||||
|   import SeckillApi from '@/sheep/api/promotion/seckill'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { getTimeStatusEnum, SharePageEnum, TimeStatusEnum } from '@/sheep/helper/const'; | ||||
|   import SeckillApi from "@/sheep/api/promotion/seckill"; | ||||
|   import SpuApi from "@/sheep/api/product/spu"; | ||||
|   import {getTimeStatusEnum, TimeStatusEnum} from "@/sheep/util/const"; | ||||
| 
 | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-bg.png'); | ||||
|   const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png'); | ||||
|   const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png'); | ||||
|   const disabledBtnBg = sheep.$url.css( | ||||
|     '/static/img/shop/goods/activity-btn-disabled.png', | ||||
|   ); | ||||
|   const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png'); | ||||
|   const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png'); | ||||
| 
 | ||||
|  | @ -198,15 +199,15 @@ | |||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 分享信息 | ||||
|   // 分享信息 TODO 芋艿:待接入 | ||||
|   const shareInfo = computed(() => { | ||||
|     if (isEmpty(unref(activity))) return {}; | ||||
|     if (isEmpty(activity)) return {}; | ||||
|     return sheep.$platform.share.getShareInfo( | ||||
|       { | ||||
|         title: activity.value.name, | ||||
|         image: sheep.$url.cdn(state.goodsInfo.picUrl), | ||||
|         params: { | ||||
|           page: SharePageEnum.SECKILL.value, | ||||
|           page: '4', | ||||
|           query: activity.value.id, | ||||
|         }, | ||||
|       }, | ||||
|  | @ -214,57 +215,42 @@ | |||
|         type: 'goods', // 商品海报 | ||||
|         title: activity.value.name, // 商品标题 | ||||
|         image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图 | ||||
|         price: fen2yuan(state.goodsInfo.price), // 商品价格 | ||||
|         marketPrice: fen2yuan(state.goodsInfo.marketPrice), // 商品原价 | ||||
|         price: state.goodsInfo.price, // 商品价格 | ||||
|         marketPrice: state.goodsInfo.marketPrice, // 商品原价 | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   const activity = ref(); | ||||
|   const timeStatusEnum = ref(''); | ||||
| 
 | ||||
|   const activity = ref() | ||||
|   const timeStatusEnum = ref('') | ||||
|   // 查询活动 | ||||
|   const getActivity = async (id) => { | ||||
|     const { data } = await SeckillApi.getSeckillActivity(id); | ||||
|     if (!data) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     activity.value = data; | ||||
|     timeStatusEnum.value = getTimeStatusEnum(activity.value.startTime, activity.value.endTime); | ||||
|     state.percent = 100 - (data.stock / data.totalStock) * 100; | ||||
|     // 查询商品 | ||||
|     await getSpu(data.spuId); | ||||
|   }; | ||||
|     const { data } = await SeckillApi.getSeckillActivity(id) | ||||
|     activity.value = data | ||||
|     timeStatusEnum.value = getTimeStatusEnum(activity.startTime, activity.endTime) | ||||
| 
 | ||||
|     // 查询商品 | ||||
|     await getSpu(data.spuId) | ||||
|   } | ||||
| 
 | ||||
|   // 查询商品 | ||||
|   const getSpu = async (id) => { | ||||
|     const { data } = await SpuApi.getSpuDetail(id); | ||||
|     if (!data) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
|     data.activity_type = 'seckill'; | ||||
|     state.goodsInfo = data; | ||||
|     const { data } = await SpuApi.getSpuDetail(id) | ||||
|     // 模拟 | ||||
|     data.activity_type = 'seckill' | ||||
|     state.goodsInfo = data | ||||
|     // 处理轮播图 | ||||
|     state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls); | ||||
| 
 | ||||
|     // 默认显示最低价 | ||||
|     state.goodsInfo.price = min([ | ||||
|       state.goodsInfo.price, | ||||
|       ...activity.value.products.map((spu) => spu.seckillPrice), | ||||
|     ]); | ||||
|     state.goodsInfo.price = min([state.goodsInfo.price, ...activity.value.products.map(spu => spu.seckillPrice)]) | ||||
| 
 | ||||
|     // 价格、库存使用活动的 | ||||
|     data.skus.forEach((sku) => { | ||||
|       const product = activity.value.products.find((product) => product.skuId === sku.id); | ||||
|     data.skus.forEach(sku => { | ||||
|       const product = activity.value.products.find(product => product.skuId === sku.id); | ||||
|       if (product) { | ||||
|         sku.price = product.seckillPrice; | ||||
|         sku.stock = Math.min(sku.stock, product.stock); | ||||
|       } else { | ||||
|         // 找不到可能是没配置,则不能发起秒杀 | ||||
|       } else { // 找不到可能是没配置,则不能发起秒杀 | ||||
|         sku.stock = 0; | ||||
|       } | ||||
|       // 设置限购数量 | ||||
|  | @ -278,18 +264,17 @@ | |||
|     }); | ||||
| 
 | ||||
|     state.skeletonLoading = false; | ||||
|   }; | ||||
|   } | ||||
| 
 | ||||
|   onLoad((options) => { | ||||
|     // 非法参数 | ||||
|     if (!options.id) { | ||||
|       state.goodsInfo = null; | ||||
|       state.skeletonLoading = false; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 查询活动 | ||||
|     getActivity(options.id); | ||||
|     getActivity(options.id) | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
|  | @ -297,7 +282,6 @@ | |||
|   .disabled-btn-box[disabled] { | ||||
|     background-color: transparent; | ||||
|   } | ||||
| 
 | ||||
|   .detail-card { | ||||
|     background-color: $white; | ||||
|     margin: 14rpx 20rpx; | ||||
|  | @ -388,7 +372,6 @@ | |||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: #ffffff; | ||||
| 
 | ||||
|       .countdown-h { | ||||
|         font-size: 24rpx; | ||||
|         font-family: OPPOSANS; | ||||
|  | @ -399,7 +382,6 @@ | |||
|         background: rgba(#000000, 0.1); | ||||
|         border-radius: 6rpx; | ||||
|       } | ||||
| 
 | ||||
|       .countdown-num { | ||||
|         font-size: 24rpx; | ||||
|         font-family: OPPOSANS; | ||||
|  | @ -483,7 +465,6 @@ | |||
|       line-height: normal; | ||||
|       border-radius: 0px 40rpx 40rpx 0px; | ||||
|     } | ||||
| 
 | ||||
|     .btn-price { | ||||
|       font-family: OPPOSANS; | ||||
| 
 | ||||
|  | @ -501,7 +482,6 @@ | |||
|       line-height: normal; | ||||
|       font-size: 24rpx; | ||||
|       font-weight: 500; | ||||
| 
 | ||||
|       .no-original { | ||||
|         font-size: 28rpx; | ||||
|       } | ||||
|  |  | |||
|  | @ -1,315 +1,200 @@ | |||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#fff' }" tabbar="/pages/index/cart" title="购物车"> | ||||
|     <s-empty | ||||
|       v-if="state.list.length === 0" | ||||
|       icon="/static/cart-empty.png" | ||||
|       text="购物车空空如也,快去逛逛吧~" | ||||
|     /> | ||||
| 	<s-layout title="购物车" tabbar="/pages/index/cart" :bgStyle="{ color: '#fff' }"> | ||||
| 		<s-empty v-if="state.list.length === 0" text="购物车空空如也,快去逛逛吧~" icon="/static/cart-empty.png" /> | ||||
| 
 | ||||
|     <!-- 头部 --> | ||||
|     <view v-if="state.list.length" class="cart-box ss-flex ss-flex-col ss-row-between"> | ||||
|       <view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30"> | ||||
|         <view class="header-left ss-flex ss-col-center ss-font-26"> | ||||
|           共 | ||||
|           <text class="goods-number ui-TC-Main ss-flex">{{ state.list.length }}</text> | ||||
|           件商品 | ||||
|         </view> | ||||
|         <view class="header-right"> | ||||
|           <button v-if="state.editMode" class="ss-reset-button" @tap="onChangeEditMode(false)"> | ||||
|             取消 | ||||
|           </button> | ||||
|           <button v-else class="ss-reset-button ui-TC-Main" @tap="onChangeEditMode(true)"> | ||||
|             编辑 | ||||
|           </button> | ||||
|         </view> | ||||
|       </view> | ||||
|       <!-- 内容 --> | ||||
|       <view class="cart-content ss-flex-1 ss-p-x-30 ss-m-b-40"> | ||||
|         <view v-for="item in state.list" :key="item.id" class="goods-box ss-r-10 ss-m-b-14"> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <label class="check-box ss-flex ss-col-center ss-p-l-10" @tap="onSelectSingle(item.id)"> | ||||
|               <radio | ||||
|                 :checked="state.selectedIds.includes(item.id)" | ||||
|                 color="var(--ui-BG-Main)" | ||||
|                 style="transform: scale(0.8)" | ||||
|                 @tap.stop="onSelectSingle(item.id)" | ||||
|               /> | ||||
|             </label> | ||||
|             <view v-if="item.spu?.status !== 1 && !state.editMode" class="down-box"> | ||||
|               该商品已下架 | ||||
|             </view> | ||||
|             <view v-else-if="item.spu?.stock <= 0 && !state.editMode" class="down-box"> | ||||
|               该商品无库存 | ||||
|             </view> | ||||
|             <s-goods-item | ||||
|               :img="item.spu.picUrl || item.goods.image" | ||||
|               :price="item.sku.price" | ||||
|               :skuText=" | ||||
|                 item.sku.properties.length > 1 | ||||
|                   ? item.sku.properties.reduce( | ||||
|                       (items2, items) => items2.valueName + ' ' + items.valueName, | ||||
|                     ) | ||||
|                   : item.sku.properties[0].valueName | ||||
|               " | ||||
|               :title="item.spu.name" | ||||
|               :titleWidth="400" | ||||
|               priceColor="#FF3000" | ||||
|             > | ||||
|               <template v-if="!state.editMode" v-slot:tool> | ||||
|                 <su-number-box | ||||
|                   v-model="item.count" | ||||
|                   :max="item.sku.stock" | ||||
|                   :min="0" | ||||
|                   :step="1" | ||||
|                   @change="onNumberChange($event, item)" | ||||
|                 /> | ||||
|               </template> | ||||
|             </s-goods-item> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|       <!-- 底部 --> | ||||
|       <su-fixed v-if="state.list.length > 0" :isInset="false" :val="48" bottom placeholder> | ||||
|         <view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom"> | ||||
|           <view class="footer-left ss-flex ss-col-center"> | ||||
|             <label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll"> | ||||
|               <radio | ||||
|                 :checked="state.isAllSelected" | ||||
|                 color="var(--ui-BG-Main)" | ||||
|                 style="transform: scale(0.8)" | ||||
|                 @tap.stop="onSelectAll" | ||||
|               /> | ||||
|               <view class="ss-m-l-8"> 全选</view> | ||||
|             </label> | ||||
|             <text>合计:</text> | ||||
|             <view class="text-price price-text"> | ||||
|               {{ fen2yuan(state.totalPriceSelected) }} | ||||
|             </view> | ||||
|           </view> | ||||
|           <view class="footer-right"> | ||||
|             <button | ||||
|               v-if="state.editMode" | ||||
|               class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main" | ||||
|               @tap="onDelete" | ||||
|             > | ||||
|               删除 | ||||
|             </button> | ||||
|             <button | ||||
|               v-else | ||||
|               class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main" | ||||
|               @tap="onConfirm" | ||||
|             > | ||||
|               去结算 | ||||
|               {{ state.selectedIds?.length ? `(${state.selectedIds.length})` : '' }} | ||||
|             </button> | ||||
|           </view> | ||||
|         </view> | ||||
|       </su-fixed> | ||||
|     </view> | ||||
|   </s-layout> | ||||
| 		<!-- 头部 --> | ||||
| 		<view class="cart-box ss-flex ss-flex-col ss-row-between" v-if="state.list.length"> | ||||
| 			<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30"> | ||||
| 				<view class="header-left ss-flex ss-col-center ss-font-26"> | ||||
| 					共 | ||||
| 					<text class="goods-number ui-TC-Main ss-flex">{{ state.list.length }}</text> | ||||
| 					件商品 | ||||
| 				</view> | ||||
| 				<view class="header-right"> | ||||
| 					<button v-if="state.editMode" class="ss-reset-button" @tap="state.editMode = false"> | ||||
| 						取消 | ||||
| 					</button> | ||||
| 					<button v-else class="ss-reset-button ui-TC-Main" @tap="state.editMode = true"> | ||||
| 						编辑 | ||||
| 					</button> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 			<!-- 内容 --> | ||||
| 			<view class="cart-content ss-flex-1 ss-p-x-30 ss-m-b-40"> | ||||
| 				<view class="goods-box ss-r-10 ss-m-b-14" v-for="item in state.list" :key="item.id"> | ||||
| 					<view class="ss-flex ss-col-center"> | ||||
| 						<label class="check-box ss-flex ss-col-center ss-p-l-10" @tap="onSelectSingle(item.id)"> | ||||
| 							<radio :checked="state.selectedIds.includes(item.id)" color="var(--ui-BG-Main)" | ||||
| 								style="transform: scale(0.8)" @tap.stop="onSelectSingle(item.id)" /> | ||||
| 						</label> | ||||
| 						<s-goods-item :title="item.spu.name" :img="item.spu.picUrl || item.goods.image" | ||||
| 							:price="item.sku.price/100" | ||||
| 							:skuText="item.sku.properties.length>1? item.sku.properties.reduce((items2,items)=>items2.valueName+' '+items.valueName):item.sku.properties[0].valueName" | ||||
| 							priceColor="#FF3000" :titleWidth="400"> | ||||
| 							<template v-if="!state.editMode" v-slot:tool> | ||||
| 								<su-number-box :min="0" :max="item.sku.stock" :step="1" v-model="item.count" | ||||
| 									@change="onNumberChange($event, item)"></su-number-box> | ||||
| 							</template> | ||||
| 						</s-goods-item> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 			<!-- 底部 --> | ||||
| 			<su-fixed bottom :val="48" placeholder v-if="state.list.length > 0" :isInset="false"> | ||||
| 				<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom"> | ||||
| 					<view class="footer-left ss-flex ss-col-center"> | ||||
| 						<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll"> | ||||
| 							<radio :checked="state.isAllSelected" color="var(--ui-BG-Main)" | ||||
| 								style="transform: scale(0.8)" @tap.stop="onSelectAll" /> | ||||
| 							<view class="ss-m-l-8"> 全选 </view> | ||||
| 						</label> | ||||
| 						<text>合计:</text> | ||||
| 						<view class="text-price price-text"> | ||||
| 							{{ state.totalPriceSelected }} | ||||
| 						</view> | ||||
| 					</view> | ||||
| 					<view class="footer-right"> | ||||
| 						<button v-if="state.editMode" class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main" | ||||
| 							@tap="onDelete"> | ||||
| 							删除 | ||||
| 						</button> | ||||
| 						<button v-else class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main" | ||||
| 							@tap="onConfirm"> | ||||
| 							去结算 | ||||
| 							{{ state.selectedIds?.length ? `(${state.selectedIds.length})` : '' }} | ||||
| 						</button> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</su-fixed> | ||||
| 		</view> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onShow } from '@dcloudio/uni-app'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { computed, reactive } from 'vue'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import { isEmpty } from '@/sheep/helper/utils'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { | ||||
| 		computed, | ||||
| 		reactive, | ||||
| 		unref | ||||
| 	} from 'vue'; | ||||
| 
 | ||||
|   // 隐藏原生tabBar | ||||
|   uni.hideTabBar({ | ||||
|     fail: () => {}, | ||||
|   }); | ||||
| 	const sys_navBar = sheep.$platform.navbar; | ||||
| 	const cart = sheep.$store('cart'); | ||||
| 
 | ||||
|   const sys_navBar = sheep.$platform.navbar; | ||||
|   const cart = sheep.$store('cart'); | ||||
| 	const state = reactive({ | ||||
| 		editMode: false, | ||||
| 		list: computed(() => cart.list), | ||||
| 		selectedList: [], | ||||
| 		selectedIds: computed(() => cart.selectedIds), | ||||
| 		isAllSelected: computed(() => cart.isAllSelected), | ||||
| 		totalPriceSelected: computed(() => cart.totalPriceSelected), | ||||
| 	}); | ||||
| 	// 单选选中 | ||||
| 	function onSelectSingle(id) { | ||||
| 		console.log('单选') | ||||
| 		cart.selectSingle(id); | ||||
| 	} | ||||
| 	// 全选 | ||||
| 	function onSelectAll() { | ||||
| 		cart.selectAll(!state.isAllSelected); | ||||
| 	} | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     editMode: computed(() => cart.editMode), | ||||
|     list: computed(() => cart.list), | ||||
|     selectedList: [], | ||||
|     selectedIds: computed(() => cart.selectedIds), | ||||
|     isAllSelected: computed(() => cart.isAllSelected), | ||||
|     totalPriceSelected: computed(() => cart.totalPriceSelected), | ||||
|   }); | ||||
| 	// 结算 | ||||
| 	function onConfirm() { | ||||
| 		let items = [] | ||||
| 		let goods_list = []; | ||||
| 		state.selectedList = state.list.filter((item) => state.selectedIds.includes(item.id)); | ||||
| 		state.selectedList.map((item) => { | ||||
| 			console.log(item, '便利'); | ||||
| 			// 此处前端做出修改 | ||||
| 			items.push({ | ||||
| 				skuId: item.sku.id, | ||||
| 				count: item.count, | ||||
| 				cartId: item.id, | ||||
| 			}) | ||||
| 			goods_list.push({ | ||||
| 				// goods_id: item.goods_id, | ||||
| 				goods_id: item.spu.id, | ||||
| 				// goods_num: item.goods_num, | ||||
| 				goods_num: item.count, | ||||
| 				// 商品价格id真没有 | ||||
| 				// goods_sku_price_id: item.goods_sku_price_id, | ||||
| 			}); | ||||
| 		}); | ||||
| 		// return; | ||||
| 		if (goods_list.length === 0) { | ||||
| 			sheep.$helper.toast('请选择商品'); | ||||
| 			return; | ||||
| 		} | ||||
| 		sheep.$router.go('/pages/order/confirm', { | ||||
| 			data: JSON.stringify({ | ||||
| 				// order_type: 'goods', | ||||
| 				// goods_list, | ||||
| 				items, | ||||
| 				// from: 'cart', | ||||
| 				deliveryType: 1, | ||||
| 				pointStatus: false, | ||||
| 			}), | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|   // 单选选中 | ||||
|   function onSelectSingle(id) { | ||||
|     cart.selectSingle(id); | ||||
|   } | ||||
| 
 | ||||
|   // 编辑、取消 | ||||
|   function onChangeEditMode(flag) { | ||||
|     cart.onChangeEditMode(flag); | ||||
|   } | ||||
| 
 | ||||
|   // 全选 | ||||
|   function onSelectAll() { | ||||
|     cart.selectAll(!state.isAllSelected); | ||||
|   } | ||||
| 
 | ||||
|   // 结算 | ||||
|   async function onConfirm() { | ||||
|     const items = []; | ||||
|     state.selectedList = state.list.filter((item) => state.selectedIds.includes(item.id)); | ||||
|     state.selectedList.map((item) => { | ||||
|       // 此处前端做出修改 | ||||
|       items.push({ | ||||
|         skuId: item.sku.id, | ||||
|         count: item.count, | ||||
|         cartId: item.id, | ||||
|         categoryId: item.spu.categoryId, | ||||
|       }); | ||||
|     }); | ||||
|     if (isEmpty(items)) { | ||||
|       sheep.$helper.toast('请先选择商品'); | ||||
|       return; | ||||
|     } | ||||
|     await validateDeliveryType(state.selectedList.map((item) => item.spu).map((spu) => spu.id)); | ||||
|     sheep.$router.go('/pages/order/confirm', { | ||||
|       data: JSON.stringify({ | ||||
|         items, | ||||
|       }), | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 校验配送方式冲突 | ||||
|    * | ||||
|    * @param {string[]} spuIds - 商品ID数组 | ||||
|    * @returns {Promise<void>} | ||||
|    * @throws {Error} 当配送方式冲突或获取商品信息失败时抛出错误 | ||||
|    */ | ||||
|   async function validateDeliveryType(spuIds) { | ||||
|     // 获取商品信息 | ||||
|     const { data: spuList } = await SpuApi.getSpuListByIds(spuIds.join(',')); | ||||
|     if (isEmpty(spuList)) { | ||||
|       sheep.$helper.toast('未找到商品信息'); | ||||
|       throw new Error('未找到商品信息'); | ||||
|     } | ||||
|     // 获取所有商品的配送方式列表 | ||||
|     const deliveryTypesList = spuList.map((item) => item.deliveryTypes); | ||||
|     // 检查配送方式冲突 | ||||
|     const hasConflict = checkDeliveryConflicts(deliveryTypesList); | ||||
|     if (hasConflict) { | ||||
|       sheep.$helper.toast('选中商品支持的配送方式冲突,不允许提交'); | ||||
|       throw new Error('选中商品支持的配送方式冲突,不允许提交'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * 检查配送方式列表中是否存在冲突 | ||||
|    * @description | ||||
|    * 示例场景: | ||||
|    * A 商品支持:[快递, 自提] | ||||
|    * B 商品支持:[快递] | ||||
|    * C 商品支持:[自提] | ||||
|    * | ||||
|    * 对比结果: | ||||
|    * A 和 B:不冲突 (有交集:快递) | ||||
|    * A 和 C:不冲突 (有交集:自提) | ||||
|    * B 和 C:冲突 (无交集) | ||||
|    * @param {Array<Array<number>>} deliveryTypesList - 配送方式列表的数组 | ||||
|    * @returns {boolean} 是否存在冲突 | ||||
|    */ | ||||
|   function checkDeliveryConflicts(deliveryTypesList) { | ||||
|     for (let i = 0; i < deliveryTypesList.length - 1; i++) { | ||||
|       const currentTypes = deliveryTypesList[i]; | ||||
|       for (let j = i + 1; j < deliveryTypesList.length; j++) { | ||||
|         const nextTypes = deliveryTypesList[j]; | ||||
|         // 检查是否没有交集(即冲突) | ||||
|         const hasNoIntersection = !currentTypes.some((type) => nextTypes.includes(type)); | ||||
|         if (hasNoIntersection) { | ||||
|           return true; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   function onNumberChange(e, cartItem) { | ||||
|     if (e === 0) { | ||||
|       cart.delete(cartItem.id); | ||||
|       return; | ||||
|     } | ||||
|     if (cartItem.goods_num === e) return; | ||||
|     cartItem.goods_num = e; | ||||
|     cart.update({ | ||||
|       goods_id: cartItem.id, | ||||
|       goods_num: e, | ||||
|       goods_sku_price_id: cartItem.goods_sku_price_id, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   async function onDelete() { | ||||
|     cart.delete(state.selectedIds); | ||||
|   } | ||||
| 
 | ||||
|   function getCartList() { | ||||
|     cart.getList(); | ||||
|   } | ||||
| 
 | ||||
|   onShow(() => { | ||||
|     getCartList(); | ||||
|   }); | ||||
| 	function onNumberChange(e, cartItem) { | ||||
| 		if (e === 0) { | ||||
| 			cart.delete(cartItem.id); | ||||
| 			return; | ||||
| 		} | ||||
| 		if (cartItem.goods_num === e) return; | ||||
| 		cartItem.goods_num = e; | ||||
| 		cart.update({ | ||||
| 			goods_id: cartItem.id, | ||||
| 			goods_num: e, | ||||
| 			goods_sku_price_id: cartItem.goods_sku_price_id, | ||||
| 		}); | ||||
| 	} | ||||
| 	async function onDelete() { | ||||
| 		cart.delete(state.selectedIds); | ||||
| 	} | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   :deep(.ui-fixed) { | ||||
|     height: 72rpx; | ||||
|   } | ||||
| 	:deep(.ui-fixed) { | ||||
| 		height: 72rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .cart-box { | ||||
|     width: 100%; | ||||
| 	.cart-box { | ||||
| 		width: 100%; | ||||
| 
 | ||||
|     .cart-header { | ||||
|       height: 70rpx; | ||||
|       background-color: #f6f6f6; | ||||
|       width: 100%; | ||||
|       position: fixed; | ||||
|       left: 0; | ||||
|       top: v-bind('sys_navBar') rpx; | ||||
|       z-index: 1000; | ||||
|       box-sizing: border-box; | ||||
|     } | ||||
| 		.cart-header { | ||||
| 			height: 70rpx; | ||||
| 			background-color: #f6f6f6; | ||||
| 			width: 100%; | ||||
| 			position: fixed; | ||||
| 			left: 0; | ||||
| 			top: v-bind('sys_navBar') rpx; | ||||
| 			z-index: 1000; | ||||
| 			box-sizing: border-box; | ||||
| 		} | ||||
| 
 | ||||
|     .cart-footer { | ||||
|       height: 100rpx; | ||||
|       background-color: #fff; | ||||
| 		.cart-footer { | ||||
| 			height: 100rpx; | ||||
| 			background-color: #fff; | ||||
| 
 | ||||
|       .pay-btn { | ||||
|         width: 180rpx; | ||||
|         height: 70rpx; | ||||
|         font-size: 28rpx; | ||||
|         line-height: 28rpx; | ||||
|         font-weight: 500; | ||||
|         border-radius: 40rpx; | ||||
|       } | ||||
|     } | ||||
| 			.pay-btn { | ||||
| 				width: 180rpx; | ||||
| 				height: 70rpx; | ||||
| 				font-size: 28rpx; | ||||
| 				line-height: 28rpx; | ||||
| 				font-weight: 500; | ||||
| 				border-radius: 40rpx; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|     .cart-content { | ||||
|       margin-top: 70rpx; | ||||
| 		.cart-content { | ||||
| 			margin-top: 70rpx; | ||||
| 
 | ||||
|       .goods-box { | ||||
|         background-color: #fff; | ||||
|         position: relative; | ||||
|       } | ||||
|       // 下架商品 | ||||
|       .down-box { | ||||
|         position: absolute; | ||||
|         left: 0; | ||||
|         top: 0; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         background: rgba(#fff, 0.8); | ||||
|         z-index: 2; | ||||
|         display: flex; | ||||
|         justify-content: center; | ||||
|         align-items: center; | ||||
|         color: #999; | ||||
|         font-size: 32rpx; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 			.goods-box { | ||||
| 				background-color: #fff; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,53 +1,54 @@ | |||
| <!-- 商品分类列表 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#fff' }" tabbar="/pages/index/category" title="分类"> | ||||
|   <s-layout title="分类" tabbar="/pages/index/category" :bgStyle="{ color: '#fff' }"> | ||||
|     <view class="s-category"> | ||||
|       <view class="three-level-wrap ss-flex ss-col-top"> | ||||
|       <view class="three-level-wrap ss-flex ss-col-top" :style="[{ height: pageHeight + 'px' }]"> | ||||
|         <!-- 商品分类(左) --> | ||||
|         <view class="side-menu-wrap" :style="[{ top: Number(statusBarHeight + 88) + 'rpx' }]"> | ||||
|           <scroll-view scroll-y :style="[{ height: pageHeight + 'px' }]"> | ||||
|             <view | ||||
|               class="menu-item ss-flex" | ||||
|               v-for="(item, index) in state.categoryList" | ||||
|               :key="item.id" | ||||
|               :class="[{ 'menu-item-active': index === state.activeMenu }]" | ||||
|               @tap="onMenu(index)" | ||||
|             > | ||||
|               <view class="menu-title ss-line-1"> | ||||
|                 {{ item.name }} | ||||
|               </view> | ||||
|         <scroll-view class="side-menu-wrap" scroll-y :style="[{ height: pageHeight + 'px' }]"> | ||||
|           <view | ||||
|             class="menu-item ss-flex" | ||||
|             v-for="(item, index) in state.categoryList" | ||||
|             :key="item.id" | ||||
|             :class="[{ 'menu-item-active': index === state.activeMenu }]" | ||||
|             @tap="onMenu(index)" | ||||
|           > | ||||
|             <view class="menu-title ss-line-1"> | ||||
|               {{ item.name }} | ||||
|             </view> | ||||
|           </scroll-view> | ||||
|         </view> | ||||
|           </view> | ||||
|         </scroll-view> | ||||
|         <!-- 商品分类(右) --> | ||||
|         <view class="goods-list-box" v-if="state.categoryList?.length"> | ||||
|           <scroll-view scroll-y :style="[{ height: pageHeight + 'px' }]"> | ||||
|             <image | ||||
|               v-if="state.categoryList[state.activeMenu].picUrl" | ||||
|               class="banner-img" | ||||
|               :src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)" | ||||
|               mode="widthFix" | ||||
|             /> | ||||
|             <first-one v-if="state.style === 'first_one'" :pagination="state.pagination" /> | ||||
|             <first-two v-if="state.style === 'first_two'" :pagination="state.pagination" /> | ||||
|             <second-one | ||||
|               v-if="state.style === 'second_one'" | ||||
|               :data="state.categoryList" | ||||
|               :activeMenu="state.activeMenu" | ||||
|             /> | ||||
|             <uni-load-more | ||||
|               v-if=" | ||||
|                 (state.style === 'first_one' || state.style === 'first_two') && | ||||
|                 state.pagination.total > 0 | ||||
|               " | ||||
|               :status="state.loadStatus" | ||||
|               :content-text="{ | ||||
|                 contentdown: '点击查看更多', | ||||
|               }" | ||||
|               @tap="loadMore" | ||||
|             /> | ||||
|           </scroll-view> | ||||
|         </view> | ||||
|         <scroll-view | ||||
|           class="goods-list-box" | ||||
|           scroll-y | ||||
|           :style="[{ height: pageHeight + 'px' }]" | ||||
|           v-if="state.categoryList?.length" | ||||
|         > | ||||
|           <image | ||||
|             v-if="state.categoryList[state.activeMenu].picUrl" | ||||
|             class="banner-img" | ||||
|             :src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)" | ||||
|             mode="widthFix" | ||||
|           /> | ||||
|           <first-one v-if="state.style === 'first_one'" :pagination="state.pagination" /> | ||||
|           <first-two v-if="state.style === 'first_two'" :pagination="state.pagination" /> | ||||
|           <second-one | ||||
|             v-if="state.style === 'second_one'" | ||||
|             :data="state.categoryList" | ||||
|             :activeMenu="state.activeMenu" | ||||
|           /> | ||||
|           <uni-load-more | ||||
|             v-if=" | ||||
|               (state.style === 'first_one' || state.style === 'first_two') && | ||||
|               state.pagination.total > 0 | ||||
|             " | ||||
|             :status="state.loadStatus" | ||||
|             :content-text="{ | ||||
|               contentdown: '点击查看更多', | ||||
|             }" | ||||
|             @tap="loadMore" | ||||
|           /> | ||||
|         </scroll-view> | ||||
|       </view> | ||||
|     </view> | ||||
|   </s-layout> | ||||
|  | @ -60,10 +61,10 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import CategoryApi from '@/sheep/api/product/category'; | ||||
|   import SpuApi from '@/sheep/api/product/spu'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { computed, reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { handleTree } from '@/sheep/helper/utils'; | ||||
|   import _ from 'lodash'; | ||||
|   import { handleTree } from '@/sheep/util'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     style: 'second_one', // first_one(一级 - 样式一), first_two(二级 - 样式二), second_one(二级) | ||||
|  | @ -82,7 +83,6 @@ | |||
| 
 | ||||
|   const { safeArea } = sheep.$platform.device; | ||||
|   const pageHeight = computed(() => safeArea.height - 44 - 50); | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
| 
 | ||||
|   // 加载商品分类 | ||||
|   async function getList() { | ||||
|  | @ -131,18 +131,17 @@ | |||
|     getGoodsList(); | ||||
|   } | ||||
| 
 | ||||
|   onLoad(async (params) => { | ||||
|   onLoad(async () => { | ||||
|     await getList(); | ||||
| 
 | ||||
|     // 首页点击分类的处理:查找满足条件的分类 | ||||
|     const foundCategory = state.categoryList.find((category) => category.id === Number(params.id)); | ||||
|     // 如果找到则调用 onMenu 自动勾选相应分类,否则调用 onMenu(0) 勾选第一个分类 | ||||
|     onMenu(foundCategory ? state.categoryList.indexOf(foundCategory) : 0); | ||||
|     // 如果是 first 风格,需要加载商品分页 | ||||
|     if (state.style === 'first_one' || state.style === 'first_two') { | ||||
|       onMenu(0); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   function handleScrollToLower() { | ||||
|   onReachBottom(() => { | ||||
|     loadMore(); | ||||
|   } | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  | @ -153,8 +152,6 @@ | |||
|         height: 100%; | ||||
|         padding-left: 12rpx; | ||||
|         background-color: #f6f6f6; | ||||
|         position: fixed; | ||||
|         left: 0; | ||||
| 
 | ||||
|         .menu-item { | ||||
|           width: 100%; | ||||
|  | @ -225,9 +222,8 @@ | |||
| 
 | ||||
|       .goods-list-box { | ||||
|         background-color: #fff; | ||||
|         width: calc(100vw - 200rpx); | ||||
|         width: calc(100vw - 100px); | ||||
|         padding: 10px; | ||||
|         margin-left: 200rpx; | ||||
|       } | ||||
| 
 | ||||
|       .banner-img { | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|             <image class="goods-img" :src="item.picUrl" mode="aspectFit" /> | ||||
|           </view> | ||||
|           <view class="goods-content"> | ||||
|             <view class="goods-title ss-line-1 ss-m-b-28">{{ item.name }}</view> | ||||
|             <view class="goods-title ss-line-1 ss-m-b-28">{{ item.title }}</view> | ||||
|             <view class="goods-price">¥{{ fen2yuan(item.price) }}</view> | ||||
|           </view> | ||||
|         </view> | ||||
|  |  | |||
|  | @ -1,93 +1,87 @@ | |||
| <!-- 首页,支持店铺装修 --> | ||||
| <template> | ||||
|   <view v-if="template"> | ||||
|     <s-layout | ||||
|       title="首页" | ||||
|       navbar="custom" | ||||
|       tabbar="/pages/index/index" | ||||
|       :bgStyle="template.page" | ||||
|       :navbarStyle="template.navigationBar" | ||||
|       onShareAppMessage | ||||
|     > | ||||
|       <s-block | ||||
|         v-for="(item, index) in template.components" | ||||
|         :key="index" | ||||
|         :styles="item.property.style" | ||||
|       > | ||||
|         <s-block-item :type="item.id" :data="item.property" :styles="item.property.style" /> | ||||
|       </s-block> | ||||
|     </s-layout> | ||||
|   </view> | ||||
| 	<view v-if="template"> | ||||
| 		<s-layout title="首页" navbar="custom" tabbar="/pages/index/index" :bgStyle="template.page" | ||||
| 			:navbarStyle="template.style?.navbar" onShareAppMessage> | ||||
| 			<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style"> | ||||
| 				<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" /> | ||||
| 			</s-block> | ||||
| 		</s-layout> | ||||
| 	</view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed } from 'vue'; | ||||
|   import { onLoad, onPageScroll, onPullDownRefresh } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import $share from '@/sheep/platform/share'; | ||||
|   // 隐藏原生tabBar | ||||
|   uni.hideTabBar({ | ||||
|     fail: () => {}, | ||||
|   }); | ||||
| 	import { | ||||
| 		computed | ||||
| 	} from 'vue'; | ||||
| 	import { | ||||
| 		onLoad, | ||||
| 		onPageScroll, | ||||
| 		onPullDownRefresh | ||||
| 	} from '@dcloudio/uni-app'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import $share from '@/sheep/platform/share'; | ||||
| 	// 隐藏原生tabBar | ||||
| 	uni.hideTabBar(); | ||||
| 
 | ||||
|   const template = computed(() => sheep.$store('app').template?.home); | ||||
|   // 在此处拦截改变一下首页轮播图 此处先写死后期复活 放到启动函数里 | ||||
|   // (async function() { | ||||
|   // console.log('原代码首页定制化数据',template) | ||||
|   // let { | ||||
|   // 	data | ||||
|   // } = await index2Api.decorate(); | ||||
|   // console.log('首页导航配置化过高无法兼容',JSON.parse(data[1].value)) | ||||
|   // 改变首页底部数据 但是没有通过数组id获取商品数据接口 | ||||
|   // let { | ||||
|   // 	data: datas | ||||
|   // } = await index2Api.spids(); | ||||
|   // template.value.data[9].data.goodsIds = datas.list.map(item => item.id); | ||||
|   // template.value.data[0].data.list = JSON.parse(data[0].value).map(item => { | ||||
|   // 	return { | ||||
|   // 		src: item.picUrl, | ||||
|   // 		url: item.url, | ||||
|   // 		title: item.name, | ||||
|   // 		type: "image" | ||||
|   // 	} | ||||
|   // }) | ||||
|   // }()) | ||||
| 	const template = computed(() => sheep.$store('app').template?.home); | ||||
| 	// 在此处拦截改变一下首页轮播图 此处先写死后期复活 放到启动函数里 | ||||
| 	// (async function() { | ||||
| 		// console.log('原代码首页定制化数据',template) | ||||
| 		// let { | ||||
| 		// 	data | ||||
| 		// } = await index2Api.decorate(); | ||||
| 		// console.log('首页导航配置化过高无法兼容',JSON.parse(data[1].value)) | ||||
| 		// 改变首页底部数据 但是没有通过数组id获取商品数据接口 | ||||
| 		// let { | ||||
| 		// 	data: datas | ||||
| 		// } = await index2Api.spids(); | ||||
| 		// template.value.data[9].data.goodsIds = datas.list.map(item => item.id); | ||||
| 		// template.value.data[0].data.list = JSON.parse(data[0].value).map(item => { | ||||
| 		// 	return { | ||||
| 		// 		src: item.picUrl, | ||||
| 		// 		url: item.url, | ||||
| 		// 		title: item.name, | ||||
| 		// 		type: "image" | ||||
| 		// 	} | ||||
| 		// }) | ||||
| 	// }()) | ||||
| 
 | ||||
|   onLoad((options) => { | ||||
|     // #ifdef MP | ||||
|     // 小程序识别二维码 | ||||
|     if (options.scene) { | ||||
|       const sceneParams = decodeURIComponent(options.scene).split('='); | ||||
|       console.log('sceneParams=>', sceneParams); | ||||
|       options[sceneParams[0]] = sceneParams[1]; | ||||
|     } | ||||
|     // #endif | ||||
| 
 | ||||
|     // 预览模板 | ||||
|     if (options.templateId) { | ||||
|       sheep.$store('app').init(options.templateId); | ||||
|     } | ||||
| 	onLoad((options) => { | ||||
| 		// #ifdef MP | ||||
| 		// 小程序识别二维码 | ||||
| 		if (options.scene) { | ||||
| 			const sceneParams = decodeURIComponent(options.scene).split('='); | ||||
| 			options[sceneParams[0]] = sceneParams[1]; | ||||
| 		} | ||||
| 		// #endif | ||||
| 
 | ||||
|     // 解析分享信息 | ||||
|     if (options.spm) { | ||||
|       $share.decryptSpm(options.spm); | ||||
|     } | ||||
| 		// 预览模板 | ||||
| 		if (options.templateId) { | ||||
| 			sheep.$store('app').init(options.templateId); | ||||
| 		} | ||||
| 
 | ||||
|     // 进入指定页面(完整页面路径) | ||||
|     if (options.page) { | ||||
|       sheep.$router.go(decodeURIComponent(options.page)); | ||||
|     } | ||||
|   }); | ||||
| 		// 解析分享信息 | ||||
| 		if (options.spm) { | ||||
| 			$share.decryptSpm(options.spm); | ||||
| 		} | ||||
| 
 | ||||
|   // 下拉刷新 | ||||
|   onPullDownRefresh(() => { | ||||
|     sheep.$store('app').init(); | ||||
|     setTimeout(function () { | ||||
|       uni.stopPullDownRefresh(); | ||||
|     }, 800); | ||||
|   }); | ||||
| 		// 进入指定页面(完整页面路径) | ||||
| 		if (options.page) { | ||||
| 			sheep.$router.go(decodeURIComponent(options.page)); | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
|   onPageScroll(() => {}); | ||||
| 	// 下拉刷新 | ||||
| 	onPullDownRefresh(() => { | ||||
| 		sheep.$store('app').init(); | ||||
| 		setTimeout(function() { | ||||
| 			uni.stopPullDownRefresh(); | ||||
| 		}, 800); | ||||
| 	}); | ||||
| 
 | ||||
| 	onPageScroll(() => {}); | ||||
| </script> | ||||
| 
 | ||||
| <style></style> | ||||
|  |  | |||
|  | @ -14,20 +14,19 @@ | |||
|     new URLSearchParams(location.search).forEach((value, key) => { | ||||
|       options[key] = value; | ||||
|     }); | ||||
|     // 执行登录 or 绑定,注意需要 await 绑定 | ||||
|     const event = options.event; | ||||
|     const code = options.code; | ||||
|     const state = options.state; | ||||
|     if (event === 'login') { // 场景一:登录 | ||||
|       await sheep.$platform.useProvider().login(code, state); | ||||
|       const res = await sheep.$platform.useProvider().login(code, state); | ||||
|     } else if (event === 'bind') { // 场景二:绑定 | ||||
|       await sheep.$platform.useProvider().bind(code, state); | ||||
|       sheep.$platform.useProvider().bind(code, state); | ||||
|     } | ||||
| 
 | ||||
|     // 检测 H5 登录回调 | ||||
|     let returnUrl = uni.getStorageSync('returnUrl'); | ||||
|     if (returnUrl) { | ||||
|       uni.removeStorage({key:'returnUrl'}); | ||||
|       uni.removeStorage('returnUrl'); | ||||
|       location.replace(returnUrl); | ||||
|     } else { | ||||
|       uni.switchTab({ | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 搜索界面 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FFF' }" class="set-wrap" title="搜索"> | ||||
|   <s-layout class="set-wrap" title="搜索" :bgStyle="{ color: '#FFF' }"> | ||||
|     <view class="ss-p-x-24"> | ||||
|       <view class="ss-flex ss-col-center"> | ||||
|         <uni-search-bar | ||||
|  |  | |||
|  | @ -5,14 +5,10 @@ | |||
|     tabbar="/pages/index/user" | ||||
|     navbar="custom" | ||||
|     :bgStyle="template.page" | ||||
|     :navbarStyle="template.navigationBar" | ||||
|     :navbarStyle="template.style?.navbar" | ||||
|     onShareAppMessage | ||||
|   > | ||||
|     <s-block | ||||
|       v-for="(item, index) in template.components" | ||||
|       :key="index" | ||||
|       :styles="item.property.style" | ||||
|     > | ||||
|     <s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style"> | ||||
|       <s-block-item :type="item.id" :data="item.property" :styles="item.property.style" /> | ||||
|     </s-block> | ||||
|   </s-layout> | ||||
|  | @ -24,11 +20,10 @@ | |||
|   import sheep from '@/sheep'; | ||||
| 
 | ||||
|   // 隐藏原生tabBar | ||||
|   uni.hideTabBar({ | ||||
|     fail: () => {}, | ||||
|   }); | ||||
|   uni.hideTabBar(); | ||||
| 
 | ||||
|   const template = computed(() => sheep.$store('app').template.user); | ||||
|   const isLogin = computed(() => sheep.$store('user').isLogin); | ||||
| 
 | ||||
|   onShow(() => { | ||||
|     sheep.$store('user').updateUserData(); | ||||
|  |  | |||
|  | @ -1,282 +0,0 @@ | |||
| <!-- 下单界面,收货地址 or 自提门店的选择组件 --> | ||||
| <template> | ||||
|   <view class="allAddress" :style="state.isPickUp ? '' : 'padding-top:10rpx;'"> | ||||
|     <view class="nav flex flex-wrap"> | ||||
|       <view | ||||
|         class="item font-color" | ||||
|         :class="state.deliveryType === 1 ? 'on' : 'on2'" | ||||
|         @tap="switchDeliveryType(1)" | ||||
|         v-if="state.isPickUp" | ||||
|       /> | ||||
|       <view | ||||
|         class="item font-color" | ||||
|         :class="state.deliveryType === 2 ? 'on' : 'on2'" | ||||
|         @tap="switchDeliveryType(2)" | ||||
|         v-if="state.isPickUp" | ||||
|       /> | ||||
|     </view> | ||||
|     <!-- 情况一:收货地址的选择 --> | ||||
|     <view | ||||
|       class="address flex flex-wrap flex-center ss-row-between" | ||||
|       @tap="onSelectAddress" | ||||
|       v-if="state.deliveryType === 1" | ||||
|       :style="state.isPickUp ? '' : 'border-top-left-radius: 14rpx;border-top-right-radius: 14rpx;'" | ||||
|     > | ||||
|       <view class="addressCon" v-if="state.addressInfo.name"> | ||||
|         <view class="name" | ||||
|           >{{ state.addressInfo.name }} | ||||
|           <text class="phone">{{ state.addressInfo.mobile }}</text> | ||||
|         </view> | ||||
|         <view class="flex flex-wrap"> | ||||
|           <text class="default font-color" v-if="state.addressInfo.defaultStatus">[默认]</text> | ||||
|           <text class="line2"> | ||||
|             {{ state.addressInfo.areaName }} {{ state.addressInfo.detailAddress }} | ||||
|           </text> | ||||
|         </view> | ||||
|       </view> | ||||
|       <view class="addressCon" v-else> | ||||
|         <view class="setaddress">设置收货地址</view> | ||||
|       </view> | ||||
|       <view class="iconfont"> | ||||
|         <view class="ss-rest-button"> | ||||
|           <text class="_icon-forward" /> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <!-- 情况二:门店的选择 --> | ||||
|     <view | ||||
|       class="address flex flex-wrap flex-center ss-row-between" | ||||
|       v-if="state.deliveryType === 2" | ||||
|       @tap="onSelectAddress" | ||||
|     > | ||||
|       <view class="addressCon" v-if="state.pickUpInfo.name"> | ||||
|         <view class="name" | ||||
|           >{{ state.pickUpInfo.name }} | ||||
|           <text class="phone">{{ state.pickUpInfo.phone }}</text> | ||||
|         </view> | ||||
|         <view class="line1"> | ||||
|           {{ state.pickUpInfo.areaName }}{{ ', ' + state.pickUpInfo.detailAddress }} | ||||
|         </view> | ||||
|       </view> | ||||
|       <view class="addressCon" v-else> | ||||
|         <view class="setaddress">选择自提门店</view> | ||||
|       </view> | ||||
|       <view class="iconfont"> | ||||
|         <view class="ss-rest-button"> | ||||
|           <text class="_icon-forward" /> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <view class="line"> | ||||
|       <image :src="sheep.$url.static('/static/img/shop/line.png')" /> | ||||
|     </view> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     modelValue: { | ||||
|       type: Object, | ||||
|       default() {}, | ||||
|     }, | ||||
|   }); | ||||
|   const emits = defineEmits(['update:modelValue']); | ||||
| 
 | ||||
|   // computed 解决父子组件双向数据同步 | ||||
|   const state = computed({ | ||||
|     get() { | ||||
|       return new Proxy(props.modelValue, { | ||||
|         set(obj, name, val) { | ||||
|           emits('update:modelValue', { | ||||
|             ...obj, | ||||
|             [name]: val, | ||||
|           }); | ||||
|           return true; | ||||
|         }, | ||||
|       }); | ||||
|     }, | ||||
|     set(val) { | ||||
|       emits('update:modelValue', val); | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   // 选择地址 | ||||
|   function onSelectAddress() { | ||||
|     let emitName = 'SELECT_ADDRESS'; | ||||
|     let addressPage = '/pages/user/address/list?type=select'; | ||||
|     if (state.value.deliveryType === 2) { | ||||
|       emitName = 'SELECT_PICK_UP_INFO'; | ||||
|       addressPage = '/pages/user/goods_details_store/index'; | ||||
|     } | ||||
|     uni.$once(emitName, (e) => { | ||||
|       changeConsignee(e.addressInfo); | ||||
|     }); | ||||
|     sheep.$router.go(addressPage); | ||||
|   } | ||||
| 
 | ||||
|   // 更改收货人地址&计算订单信息 | ||||
|   async function changeConsignee(addressInfo = {}) { | ||||
|     if (!isEmpty(addressInfo)) { | ||||
|       if (state.value.deliveryType === 1) { | ||||
|         state.value.addressInfo = addressInfo; | ||||
|       } | ||||
|       if (state.value.deliveryType === 2) { | ||||
|         state.value.pickUpInfo = addressInfo; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 收货方式切换 | ||||
|   const switchDeliveryType = (type) => { | ||||
|     state.value.deliveryType = type; | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
|   .allAddress .font-color { | ||||
|     color: #e93323 !important; | ||||
|   } | ||||
|   .line2 { | ||||
|     width: 504rpx; | ||||
|   } | ||||
|   .textR { | ||||
|     text-align: right; | ||||
|   } | ||||
| 
 | ||||
|   .line { | ||||
|     width: 100%; | ||||
|     height: 3rpx; | ||||
|   } | ||||
| 
 | ||||
|   .line image { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     display: block; | ||||
|   } | ||||
| 
 | ||||
|   .address { | ||||
|     padding: 28rpx; | ||||
|     background-color: #fff; | ||||
|     box-sizing: border-box; | ||||
|   } | ||||
| 
 | ||||
|   .address .addressCon { | ||||
|     width: 596rpx; | ||||
|     font-size: 26rpx; | ||||
|     color: #666; | ||||
|   } | ||||
| 
 | ||||
|   .address .addressCon .name { | ||||
|     font-size: 30rpx; | ||||
|     color: #282828; | ||||
|     font-weight: bold; | ||||
|     margin-bottom: 10rpx; | ||||
|   } | ||||
| 
 | ||||
|   .address .addressCon .name .phone { | ||||
|     margin-left: 50rpx; | ||||
|   } | ||||
| 
 | ||||
|   .address .addressCon .default { | ||||
|     margin-right: 12rpx; | ||||
|   } | ||||
| 
 | ||||
|   .address .addressCon .setaddress { | ||||
|     color: #333; | ||||
|     font-size: 28rpx; | ||||
|   } | ||||
| 
 | ||||
|   .address .iconfont { | ||||
|     font-size: 35rpx; | ||||
|     color: #707070; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress { | ||||
|     width: 100%; | ||||
|     background: linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%); | ||||
|     // background-image: linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%); | ||||
|     // background-image: -webkit-linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%); | ||||
|     // background-image: -moz-linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%); | ||||
|     //padding: 100rpx 30rpx 0 30rpx; | ||||
|     padding-top: 100rpx; | ||||
|     padding-bottom: 10rpx; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav { | ||||
|     width: 690rpx; | ||||
|     margin: 0 auto; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item { | ||||
|     width: 334rpx; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item.on { | ||||
|     position: relative; | ||||
|     width: 230rpx; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item.on::before { | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     content: '快递配送'; | ||||
|     font-size: 28rpx; | ||||
|     display: block; | ||||
|     height: 0; | ||||
|     width: 336rpx; | ||||
|     border-width: 0 20rpx 80rpx 0; | ||||
|     border-style: none solid solid; | ||||
|     border-color: transparent transparent #fff; | ||||
|     z-index: 2; | ||||
|     border-radius: 14rpx 36rpx 0 0; | ||||
|     text-align: center; | ||||
|     line-height: 80rpx; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item:nth-of-type(2).on::before { | ||||
|     content: '到店自提'; | ||||
|     border-width: 0 0 80rpx 20rpx; | ||||
|     border-radius: 36rpx 14rpx 0 0; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item.on2 { | ||||
|     position: relative; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item.on2::before { | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     content: '到店自提'; | ||||
|     font-size: 28rpx; | ||||
|     display: block; | ||||
|     height: 0; | ||||
|     width: 401rpx; | ||||
|     border-width: 0 0 60rpx 60rpx; | ||||
|     border-style: none solid solid; | ||||
|     border-color: transparent transparent #f7c1bd; | ||||
|     border-radius: 36rpx 14rpx 0 0; | ||||
|     text-align: center; | ||||
|     line-height: 60rpx; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .nav .item:nth-of-type(1).on2::before { | ||||
|     content: '快递配送'; | ||||
|     border-width: 0 60rpx 60rpx 0; | ||||
|     border-radius: 14rpx 36rpx 0 0; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .address { | ||||
|     width: 690rpx; | ||||
|     max-height: 180rpx; | ||||
|     margin: 0 auto; | ||||
|   } | ||||
| 
 | ||||
|   .allAddress .line { | ||||
|     width: 100%; | ||||
|     margin: 0 auto; | ||||
|   } | ||||
| </style> | ||||
|  | @ -64,9 +64,10 @@ | |||
|             v-model="formData.applyDescription" | ||||
|             placeholder="客官~请描述您遇到的问题,建议上传照片" | ||||
|           /> | ||||
|           <!-- TODO 芋艿:上传的测试 --> | ||||
|           <view class="upload-img"> | ||||
|             <s-uploader | ||||
|               v-model:url="formData.applyPicUrls" | ||||
|               v-model:url="formData.images" | ||||
|               fileMediatype="image" | ||||
|               limit="9" | ||||
|               mode="grid" | ||||
|  | @ -97,7 +98,11 @@ | |||
|         </view> | ||||
|         <view class="modal-content content_box"> | ||||
|           <radio-group @change="onChange"> | ||||
|             <label class="radio ss-flex ss-col-center" v-for="item in state.reasonList" :key="item"> | ||||
|             <label | ||||
|               class="radio ss-flex ss-col-center" | ||||
|               v-for="item in state.reasonList" | ||||
|               :key="item" | ||||
|             > | ||||
|               <view class="ss-flex-1 ss-p-20">{{ item }}</view> | ||||
|               <radio | ||||
|                 :value="item" | ||||
|  | @ -147,18 +152,21 @@ | |||
|     ], | ||||
|     reasonList: [], // 可选的申请原因数组 | ||||
|     showModal: false, // 是否显示申请原因弹窗 | ||||
|     currentValue: '', // 当前选择的售后原因 | ||||
|     currentValue: '' // 当前选择的售后原因 | ||||
|   }); | ||||
|   let formData = reactive({ | ||||
|   const formData = reactive({ | ||||
|     way: '', | ||||
|     applyReason: '', | ||||
|     applyDescription: '', | ||||
|     applyPicUrls: [], | ||||
|     images: [], | ||||
|   }); | ||||
|   const rules = reactive({}); | ||||
| 
 | ||||
|   // 提交表单 | ||||
|   async function submit() { | ||||
|     // #ifdef MP | ||||
|     sheep.$platform.useProvider('wechat').subscribeMessage('order_aftersale_change'); | ||||
|     // #endif | ||||
|     let data = { | ||||
|       orderItemId: state.itemId, | ||||
|       refundPrice: state.item.payPrice, | ||||
|  | @ -169,7 +177,7 @@ | |||
|       uni.showToast({ | ||||
|         title: '申请成功', | ||||
|       }); | ||||
|       sheep.$router.redirect('/pages/order/aftersale/list'); | ||||
|       sheep.$router.go('/pages/order/aftersale/list'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -206,7 +214,7 @@ | |||
|     state.itemId = parseInt(options.itemId); | ||||
| 
 | ||||
|     // 读取订单信息 | ||||
|     const { code, data } = await OrderApi.getOrderDetail(state.orderId); | ||||
|     const { code, data } = await OrderApi.getOrder(state.orderId); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,193 +1,156 @@ | |||
| <!-- 售后详情 --> | ||||
| <template> | ||||
|   <s-layout title="售后详情" :navbar="!isEmpty(state.info) && state.loading ? 'inner' : 'normal'"> | ||||
|     <view class="content_box" v-if="!isEmpty(state.info) && state.loading"> | ||||
|       <!-- 步骤条 --> | ||||
|       <view | ||||
|         class="steps-box ss-flex" | ||||
|         :style="[ | ||||
| 	<s-layout title="售后详情" :navbar="!isEmpty(state.info) && state.loading ? 'inner' : 'normal'"> | ||||
| 		<view class="content_box" v-if="!isEmpty(state.info) && state.loading"> | ||||
| 			<!-- 步骤条 --> | ||||
| 			<view class="steps-box ss-flex" :style="[ | ||||
|           { | ||||
|             marginTop: '-' + Number(statusBarHeight + 88) + 'rpx', | ||||
|             paddingTop: Number(statusBarHeight + 88) + 'rpx', | ||||
|           }, | ||||
|         ]" | ||||
|       > | ||||
|         <view class="ss-flex"> | ||||
|           <view class="steps-item" v-for="(item, index) in state.list" :key="index"> | ||||
|             <view class="ss-flex"> | ||||
|               <text | ||||
|                 class="sicon-circleclose" | ||||
|                 v-if="state.list.length - 1 === index && [61, 62, 63].includes(state.info.status)" | ||||
|               /> | ||||
|               <text | ||||
|                 class="sicon-circlecheck" | ||||
|                 v-else | ||||
|                 :class="state.active >= index ? 'activity-color' : 'info-color'" | ||||
|               /> | ||||
|         ]"> | ||||
| 				<view class="ss-flex"> | ||||
| 					<view class="steps-item" v-for="(item, index) in state.list" :key="index"> | ||||
| 						<view class="ss-flex"> | ||||
| 							<text class="sicon-circleclose" | ||||
|                     v-if="state.list.length - 1 === index && [61, 62, 63].includes(state.info.status)" /> | ||||
| 							<text class="sicon-circlecheck" v-else | ||||
|                     :class="state.active >= index ? 'activity-color' : 'info-color'" /> | ||||
| 
 | ||||
|               <view | ||||
|                 v-if="state.list.length - 1 !== index" | ||||
|                 class="line" | ||||
|                 :class="state.active >= index ? 'activity-bg' : 'info-bg'" | ||||
|               /> | ||||
|             </view> | ||||
|             <view | ||||
|               class="steps-item-title" | ||||
|               :class="state.active >= index ? 'activity-color' : 'info-color'" | ||||
|             > | ||||
|               {{ item.title }} | ||||
|             </view> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
| 							<view v-if="state.list.length - 1 !== index" class="line" | ||||
|                     :class="state.active >= index ? 'activity-bg' : 'info-bg'" /> | ||||
| 						</view> | ||||
| 						<view class="steps-item-title" :class="state.active >= index ? 'activity-color' : 'info-color'"> | ||||
| 							{{ item.title }} | ||||
| 						</view> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 
 | ||||
|       <!-- 服务状态 --> | ||||
|       <view | ||||
|         class="status-box ss-flex ss-col-center ss-row-between ss-m-x-20" | ||||
|         @tap="sheep.$router.go('/pages/order/aftersale/log', { id: state.id })" | ||||
|       > | ||||
|         <view class=""> | ||||
|           <view class="status-text"> | ||||
| 			<!-- 服务状态 --> | ||||
| 			<view class="status-box ss-flex ss-col-center ss-row-between ss-m-x-20" | ||||
|             @tap="sheep.$router.go('/pages/order/aftersale/log', { id: state.id })"> | ||||
| 				<view class=""> | ||||
| 					<view class="status-text"> | ||||
|             {{ formatAfterSaleStatusDescription(state.info) }} | ||||
|           </view> | ||||
|           <view class="status-time"> | ||||
| 					<view class="status-time"> | ||||
|             {{ sheep.$helper.timeFormat(state.info.updateTime, 'yyyy-mm-dd hh:MM:ss') }} | ||||
|           </view> | ||||
|         </view> | ||||
|         <text class="ss-iconfont _icon-forward" style="color: #666" /> | ||||
|       </view> | ||||
| 				</view> | ||||
| 				<text class="ss-iconfont _icon-forward" style="color: #666" /> | ||||
| 			</view> | ||||
| 
 | ||||
|       <!-- 退款金额 --> | ||||
|       <view class="aftersale-money ss-flex ss-col-center ss-row-between"> | ||||
|         <view class="aftersale-money--title">退款总额</view> | ||||
|         <view class="aftersale-money--num">¥{{ fen2yuan(state.info.refundPrice) }}</view> | ||||
|       </view> | ||||
|       <!-- 服务商品 --> | ||||
|       <view class="order-shop"> | ||||
|         <s-goods-item | ||||
|           :img="state.info.picUrl" | ||||
|           :title="state.info.spuName" | ||||
|           :titleWidth="480" | ||||
| 			<!-- 退款金额 --> | ||||
| 			<view class="aftersale-money ss-flex ss-col-center ss-row-between"> | ||||
| 				<view class="aftersale-money--title">退款总额</view> | ||||
| 				<view class="aftersale-money--num">¥{{ fen2yuan(state.info.refundPrice) }}</view> | ||||
| 			</view> | ||||
| 			<!-- 服务商品 --> | ||||
| 			<view class="order-shop"> | ||||
| 				<s-goods-item | ||||
|           :img=" state.info.picUrl" | ||||
|           :title=" state.info.spuName" | ||||
| 					:titleWidth="480" | ||||
|           :skuText="state.info.properties.map((property) => property.valueName).join(' ')" | ||||
|           :num="state.info.count" | ||||
|           :num=" state.info.count" | ||||
|         /> | ||||
|       </view> | ||||
| 			</view> | ||||
| 
 | ||||
|       <!-- 服务内容 --> | ||||
|       <view class="aftersale-content"> | ||||
|         <view class="aftersale-item ss-flex ss-col-center"> | ||||
|           <view class="item-title">服务单号:</view> | ||||
|           <view class="item-content ss-m-r-16">{{ state.info.no }}</view> | ||||
|           <button class="ss-reset-button copy-btn" @tap="onCopy">复制</button> | ||||
|         </view> | ||||
|         <view class="aftersale-item ss-flex ss-col-center"> | ||||
|           <view class="item-title">申请时间:</view> | ||||
|           <view class="item-content"> | ||||
|             {{ sheep.$helper.timeFormat(state.info.createTime, 'yyyy-mm-dd hh:MM:ss') }} | ||||
|           </view> | ||||
|         </view> | ||||
|         <view class="aftersale-item ss-flex ss-col-center"> | ||||
|           <view class="item-title">售后类型:</view> | ||||
|           <view class="item-content">{{ state.info.way === 10 ? '仅退款' : '退款退货' }}</view> | ||||
|         </view> | ||||
|         <view class="aftersale-item ss-flex ss-col-center"> | ||||
|           <view class="item-title">申请原因:</view> | ||||
|           <view class="item-content">{{ state.info.applyReason }}</view> | ||||
|         </view> | ||||
|         <view class="aftersale-item ss-flex ss-col-center"> | ||||
|           <view class="item-title">相关描述:</view> | ||||
|           <view class="item-content">{{ state.info.applyDescription }}</view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
| 			<!-- 服务内容 --> | ||||
| 			<view class="aftersale-content"> | ||||
| 				<view class="aftersale-item ss-flex ss-col-center"> | ||||
| 					<view class="item-title">服务单号:</view> | ||||
| 					<view class="item-content ss-m-r-16">{{ state.info.no }}</view> | ||||
| 					<button class="ss-reset-button copy-btn" @tap="onCopy">复制</button> | ||||
| 				</view> | ||||
| 				<view class="aftersale-item ss-flex ss-col-center"> | ||||
| 					<view class="item-title">申请时间:</view> | ||||
| 					<view class="item-content"> | ||||
| 						{{ sheep.$helper.timeFormat(state.info.createTime, 'yyyy-mm-dd hh:MM:ss') }} | ||||
| 					</view> | ||||
| 				</view> | ||||
| 				<view class="aftersale-item ss-flex ss-col-center"> | ||||
| 					<view class="item-title">售后类型:</view> | ||||
| 					<view class="item-content">{{ state.info.way === 10 ? '仅退款' : '退款退货' }}</view> | ||||
| 				</view> | ||||
| 				<view class="aftersale-item ss-flex ss-col-center"> | ||||
| 					<view class="item-title">申请原因:</view> | ||||
| 					<view class="item-content">{{ state.info.applyReason }}</view> | ||||
| 				</view> | ||||
| 				<view class="aftersale-item ss-flex ss-col-center"> | ||||
| 					<view class="item-title">相关描述:</view> | ||||
| 					<view class="item-content">{{ state.info.applyDescription }}</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 
 | ||||
|     <!-- 操作区 --> | ||||
|     <s-empty | ||||
|       v-if="isEmpty(state.info) && state.loading" | ||||
|       icon="/static/order-empty.png" | ||||
|       text="暂无该订单售后详情" | ||||
|     /> | ||||
|     <su-fixed bottom placeholder bg="bg-white" v-if="!isEmpty(state.info)"> | ||||
|       <view class="foot_box"> | ||||
|         <button | ||||
|           class="ss-reset-button btn" | ||||
|           v-if="state.info.buttons?.includes('cancel')" | ||||
|           @tap="onApply(state.info.id)" | ||||
|         > | ||||
| 		<s-empty v-if="isEmpty(state.info) && state.loading" icon="/static/order-empty.png" text="暂无该订单售后详情" /> | ||||
| 		<su-fixed bottom placeholder bg="bg-white" v-if="!isEmpty(state.info)"> | ||||
| 			<view class="foot_box"> | ||||
|         <button class="ss-reset-button btn" v-if="state.info.buttons?.includes('cancel')" | ||||
|                 @tap="onApply(state.info.id)"> | ||||
|           取消申请 | ||||
|         </button> | ||||
|         <button | ||||
|           class="ss-reset-button btn" | ||||
|           v-if="state.info.buttons?.includes('delivery')" | ||||
|           @tap="sheep.$router.go('/pages/order/aftersale/return-delivery', { id: state.info.id })" | ||||
|         > | ||||
|         <button class="ss-reset-button btn" v-if="state.info.buttons?.includes('delivery')" | ||||
|                 @tap="sheep.$router.go('/pages/order/aftersale/return-delivery', { id: state.info.id })"> | ||||
|           填写退货 | ||||
|         </button> | ||||
|         <button | ||||
|           class="ss-reset-button contcat-btn btn" | ||||
|           @tap="sheep.$router.go('/pages/chat/index')" | ||||
|         > | ||||
| 				<button class="ss-reset-button contcat-btn btn" @tap="sheep.$router.go('/pages/chat/index')"> | ||||
|           联系客服 | ||||
|         </button> | ||||
|       </view> | ||||
|     </su-fixed> | ||||
|   </s-layout> | ||||
| 			</view> | ||||
| 		</su-fixed> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
|   import { | ||||
|     fen2yuan, | ||||
|     formatAfterSaleStatusDescription, | ||||
|     handleAfterSaleButtons, | ||||
|   } from '@/sheep/hooks/useGoods'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { onLoad } from '@dcloudio/uni-app'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 	import { isEmpty } from 'lodash'; | ||||
|   import { fen2yuan, formatAfterSaleStatusDescription, handleAfterSaleButtons } from '@/sheep/hooks/useGoods'; | ||||
|   import AfterSaleApi from '@/sheep/api/trade/afterSale'; | ||||
| 
 | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png'); | ||||
|   const state = reactive({ | ||||
| 	const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
| 	const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png'); | ||||
| 	const state = reactive({ | ||||
|     id: 0, // 售后编号 | ||||
|     info: {}, // 收货信息 | ||||
|     loading: false, | ||||
| 		loading: false, | ||||
|     active: 0, // 在 list 的激活位置 | ||||
|     list: [ | ||||
|       { | ||||
|         title: '提交申请', | ||||
|       }, | ||||
|       { | ||||
|         title: '处理中', | ||||
|       }, | ||||
|       { | ||||
|         title: '完成', | ||||
|       }, | ||||
|     ], // 时间轴 | ||||
|   }); | ||||
|     list: [{ | ||||
|       title: '提交申请', | ||||
|     }, { | ||||
|       title: '处理中', | ||||
|     }, { | ||||
|       title: '完成' | ||||
|     }], // 时间轴 | ||||
| 	}); | ||||
| 
 | ||||
|   function onApply(id) { | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确定要取消此申请吗?', | ||||
|       success: async function (res) { | ||||
|         if (!res.confirm) { | ||||
|           return; | ||||
|         } | ||||
| 	function onApply(id) { | ||||
| 		uni.showModal({ | ||||
| 			title: '提示', | ||||
| 			content: '确定要取消此申请吗?', | ||||
| 			success: async function(res) { | ||||
| 				if (!res.confirm) { | ||||
| 					return; | ||||
| 				} | ||||
|         const { code } = await AfterSaleApi.cancelAfterSale(id); | ||||
|         if (code === 0) { | ||||
|           await getDetail(id); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|   // 复制 | ||||
|   const onCopy = () => { | ||||
|     sheep.$helper.copyText(state.info.no); | ||||
|   }; | ||||
| 		sheep.$helper.copyText(state.info.no); | ||||
| 	}; | ||||
| 
 | ||||
|   async function getDetail(id) { | ||||
| 	async function getDetail(id) { | ||||
|     state.loading = true; | ||||
|     const { code, data } = await AfterSaleApi.getAfterSale(id); | ||||
|     if (code !== 0) { | ||||
|  | @ -207,173 +170,173 @@ | |||
|     } else if ([61, 62, 63].includes(state.info.status)) { | ||||
|       state.active = 2; | ||||
|     } | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   onLoad((options) => { | ||||
| 	onLoad((options) => { | ||||
|     if (!options.id) { | ||||
|       sheep.$helper.toast(`缺少订单信息,请检查`); | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     state.id = options.id; | ||||
|     getDetail(options.id); | ||||
|   }); | ||||
| 		state.id = options.id; | ||||
| 		getDetail(options.id); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   // 步骤条 | ||||
|   .steps-box { | ||||
|     width: 100%; | ||||
|     height: 190rpx; | ||||
|     background: v-bind(headerBg) no-repeat, | ||||
|       linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|     background-size: 750rpx 100%; | ||||
|     padding-left: 72rpx; | ||||
| 	// 步骤条 | ||||
| 	.steps-box { | ||||
| 		width: 100%; | ||||
| 		height: 190rpx; | ||||
| 		background: v-bind(headerBg) no-repeat, | ||||
| 			linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
| 		background-size: 750rpx 100%; | ||||
| 		padding-left: 72rpx; | ||||
| 
 | ||||
|     .steps-item { | ||||
|       .sicon-circleclose { | ||||
|         font-size: 24rpx; | ||||
|         color: #fff; | ||||
|       } | ||||
| 		.steps-item { | ||||
| 			.sicon-circleclose { | ||||
| 				font-size: 24rpx; | ||||
| 				color: #fff; | ||||
| 			} | ||||
| 
 | ||||
|       .sicon-circlecheck { | ||||
|         font-size: 24rpx; | ||||
|       } | ||||
| 			.sicon-circlecheck { | ||||
| 				font-size: 24rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .steps-item-title { | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 400; | ||||
|         margin-top: 16rpx; | ||||
|         margin-left: -36rpx; | ||||
|         width: 100rpx; | ||||
|         text-align: center; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 			.steps-item-title { | ||||
| 				font-size: 24rpx; | ||||
| 				font-weight: 400; | ||||
| 				margin-top: 16rpx; | ||||
| 				margin-left: -36rpx; | ||||
| 				width: 100rpx; | ||||
| 				text-align: center; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .activity-color { | ||||
|     color: #fff; | ||||
|   } | ||||
| 	.activity-color { | ||||
| 		color: #fff; | ||||
| 	} | ||||
| 
 | ||||
|   .info-color { | ||||
|     color: rgba(#fff, 0.4); | ||||
|   } | ||||
| 	.info-color { | ||||
| 		color: rgba(#fff, 0.4); | ||||
| 	} | ||||
| 
 | ||||
|   .activity-bg { | ||||
|     background: #fff; | ||||
|   } | ||||
| 	.activity-bg { | ||||
| 		background: #fff; | ||||
| 	} | ||||
| 
 | ||||
|   .info-bg { | ||||
|     background: rgba(#fff, 0.4); | ||||
|   } | ||||
| 	.info-bg { | ||||
| 		background: rgba(#fff, 0.4); | ||||
| 	} | ||||
| 
 | ||||
|   .line { | ||||
|     width: 270rpx; | ||||
|     height: 4rpx; | ||||
|   } | ||||
| 	.line { | ||||
| 		width: 270rpx; | ||||
| 		height: 4rpx; | ||||
| 	} | ||||
| 
 | ||||
|   // 服务状态 | ||||
|   .status-box { | ||||
|     position: relative; | ||||
|     z-index: 3; | ||||
|     background-color: #fff; | ||||
|     border-radius: 20rpx 20rpx 0px 0px; | ||||
|     padding: 20rpx; | ||||
|     margin-top: -20rpx; | ||||
| 	// 服务状态 | ||||
| 	.status-box { | ||||
| 		position: relative; | ||||
| 		z-index: 3; | ||||
| 		background-color: #fff; | ||||
| 		border-radius: 20rpx 20rpx 0px 0px; | ||||
| 		padding: 20rpx; | ||||
| 		margin-top: -20rpx; | ||||
| 
 | ||||
|     .status-text { | ||||
|       font-size: 28rpx; | ||||
| 		.status-text { | ||||
| 			font-size: 28rpx; | ||||
| 
 | ||||
|       font-weight: 500; | ||||
|       color: rgba(51, 51, 51, 1); | ||||
|       margin-bottom: 20rpx; | ||||
|     } | ||||
| 			font-weight: 500; | ||||
| 			color: rgba(51, 51, 51, 1); | ||||
| 			margin-bottom: 20rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .status-time { | ||||
|       font-size: 24rpx; | ||||
| 		.status-time { | ||||
| 			font-size: 24rpx; | ||||
| 
 | ||||
|       font-weight: 400; | ||||
|       color: rgba(153, 153, 153, 1); | ||||
|     } | ||||
|   } | ||||
| 			font-weight: 400; | ||||
| 			color: rgba(153, 153, 153, 1); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   // 退款金额 | ||||
|   .aftersale-money { | ||||
|     background-color: #fff; | ||||
|     height: 98rpx; | ||||
|     padding: 0 20rpx; | ||||
|     margin: 20rpx; | ||||
| 	// 退款金额 | ||||
| 	.aftersale-money { | ||||
| 		background-color: #fff; | ||||
| 		height: 98rpx; | ||||
| 		padding: 0 20rpx; | ||||
| 		margin: 20rpx; | ||||
| 
 | ||||
|     .aftersale-money--title { | ||||
|       font-size: 28rpx; | ||||
| 		.aftersale-money--title { | ||||
| 			font-size: 28rpx; | ||||
| 
 | ||||
|       font-weight: 500; | ||||
|       color: rgba(51, 51, 51, 1); | ||||
|     } | ||||
| 			font-weight: 500; | ||||
| 			color: rgba(51, 51, 51, 1); | ||||
| 		} | ||||
| 
 | ||||
|     .aftersale-money--num { | ||||
|       font-size: 28rpx; | ||||
|       font-family: OPPOSANS; | ||||
|       font-weight: 500; | ||||
|       color: #ff3000; | ||||
|     } | ||||
|   } | ||||
| 		.aftersale-money--num { | ||||
| 			font-size: 28rpx; | ||||
| 			font-family: OPPOSANS; | ||||
| 			font-weight: 500; | ||||
| 			color: #ff3000; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   // order-shop | ||||
|   .order-shop { | ||||
|     padding: 20rpx; | ||||
|     background-color: #fff; | ||||
|     margin: 0 20rpx 20rpx 20rpx; | ||||
|   } | ||||
| 	// order-shop | ||||
| 	.order-shop { | ||||
| 		padding: 20rpx; | ||||
| 		background-color: #fff; | ||||
| 		margin: 0 20rpx 20rpx 20rpx; | ||||
| 	} | ||||
| 
 | ||||
|   // 服务内容 | ||||
|   .aftersale-content { | ||||
|     background-color: #fff; | ||||
|     padding: 20rpx; | ||||
|     margin: 0 20rpx; | ||||
| 	// 服务内容 | ||||
| 	.aftersale-content { | ||||
| 		background-color: #fff; | ||||
| 		padding: 20rpx; | ||||
| 		margin: 0 20rpx; | ||||
| 
 | ||||
|     .aftersale-item { | ||||
|       height: 60rpx; | ||||
| 		.aftersale-item { | ||||
| 			height: 60rpx; | ||||
| 
 | ||||
|       .copy-btn { | ||||
|         background: #eeeeee; | ||||
|         color: #333; | ||||
|         border-radius: 20rpx; | ||||
|         width: 75rpx; | ||||
|         height: 40rpx; | ||||
|         font-size: 22rpx; | ||||
|       } | ||||
| 			.copy-btn { | ||||
| 				background: #eeeeee; | ||||
| 				color: #333; | ||||
| 				border-radius: 20rpx; | ||||
| 				width: 75rpx; | ||||
| 				height: 40rpx; | ||||
| 				font-size: 22rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .item-title { | ||||
|         color: #999; | ||||
|         font-size: 28rpx; | ||||
|       } | ||||
| 			.item-title { | ||||
| 				color: #999; | ||||
| 				font-size: 28rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .item-content { | ||||
|         color: #333; | ||||
|         font-size: 28rpx; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 			.item-content { | ||||
| 				color: #333; | ||||
| 				font-size: 28rpx; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   // 底部功能 | ||||
|   .foot_box { | ||||
|     height: 100rpx; | ||||
|     background-color: #fff; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     justify-content: flex-end; | ||||
| 	// 底部功能 | ||||
| 	.foot_box { | ||||
| 		height: 100rpx; | ||||
| 		background-color: #fff; | ||||
| 		display: flex; | ||||
| 		align-items: center; | ||||
| 		justify-content: flex-end; | ||||
| 
 | ||||
|     .btn { | ||||
|       width: 160rpx; | ||||
|       line-height: 60rpx; | ||||
|       background: rgba(238, 238, 238, 1); | ||||
|       border-radius: 30rpx; | ||||
|       padding: 0; | ||||
|       margin-right: 20rpx; | ||||
|       font-size: 26rpx; | ||||
| 		.btn { | ||||
| 			width: 160rpx; | ||||
| 			line-height: 60rpx; | ||||
| 			background: rgba(238, 238, 238, 1); | ||||
| 			border-radius: 30rpx; | ||||
| 			padding: 0; | ||||
| 			margin-right: 20rpx; | ||||
| 			font-size: 26rpx; | ||||
| 
 | ||||
|       font-weight: 400; | ||||
|       color: rgba(51, 51, 51, 1); | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 			font-weight: 400; | ||||
| 			color: rgba(51, 51, 51, 1); | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,209 +1,193 @@ | |||
| <!-- 售后列表 --> | ||||
| <template> | ||||
|   <s-layout title="售后列表"> | ||||
|     <!-- tab --> | ||||
|     <su-sticky bgColor="#fff"> | ||||
|       <su-tabs | ||||
|         :list="tabMaps" | ||||
|         :scrollable="false" | ||||
|         @change="onTabsChange" | ||||
|         :current="state.currentTab" | ||||
|       /> | ||||
|     </su-sticky> | ||||
|     <s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无数据" /> | ||||
|     <!-- 列表 --> | ||||
|     <view v-if="state.pagination.total > 0"> | ||||
|       <view | ||||
|         class="list-box ss-m-y-20" | ||||
|         v-for="order in state.pagination.list" | ||||
|         :key="order.id" | ||||
|         @tap="sheep.$router.go('/pages/order/aftersale/detail', { id: order.id })" | ||||
|       > | ||||
|         <view class="order-head ss-flex ss-col-center ss-row-between"> | ||||
|           <text class="no">服务单号:{{ order.no }}</text> | ||||
|           <text class="state">{{ formatAfterSaleStatus(order) }}</text> | ||||
|         </view> | ||||
|         <s-goods-item | ||||
| 	<s-layout title="售后列表"> | ||||
| 		<!-- tab --> | ||||
| 		<su-sticky bgColor="#fff"> | ||||
| 			<su-tabs :list="tabMaps" :scrollable="false" @change="onTabsChange" :current="state.currentTab" /> | ||||
| 		</su-sticky> | ||||
| 		<s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无数据" /> | ||||
| 		<!-- 列表 --> | ||||
| 		<view v-if="state.pagination.total > 0"> | ||||
| 			<view class="list-box ss-m-y-20" v-for="order in state.pagination.list" :key="order.id" | ||||
| 				@tap="sheep.$router.go('/pages/order/aftersale/detail', { id: order.id })"> | ||||
| 				<view class="order-head ss-flex ss-col-center ss-row-between"> | ||||
| 					<text class="no">服务单号:{{ order.no }}</text> | ||||
| 					<text class="state">{{ formatAfterSaleStatus(order) }}</text> | ||||
| 				</view> | ||||
| 				<s-goods-item | ||||
|           :img="order.picUrl" | ||||
|           :title="order.spuName" | ||||
|           :skuText="order.properties.map((property) => property.valueName).join(' ')" | ||||
| 					:skuText="order.properties.map((property) => property.valueName).join(' ')" | ||||
|           :price="order.refundPrice" | ||||
|         /> | ||||
|         <view class="apply-box ss-flex ss-col-center ss-row-between border-bottom ss-p-x-20"> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <view class="title ss-m-r-20">{{ order.way === 10 ? '仅退款' : '退款退货' }}</view> | ||||
|             <view class="value">{{ formatAfterSaleStatusDescription(order) }}</view> | ||||
|           </view> | ||||
|           <text class="_icon-forward"></text> | ||||
|         </view> | ||||
|         <view class="tool-btn-box ss-flex ss-col-center ss-row-right ss-p-r-20"> | ||||
|           <view> | ||||
|             <button | ||||
|               class="ss-reset-button tool-btn" | ||||
|               @tap.stop="onApply(order.id)" | ||||
|               v-if="order?.buttons.includes('cancel')" | ||||
|             > | ||||
|               取消申请 | ||||
|             </button> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <uni-load-more | ||||
|       v-if="state.pagination.total > 0" | ||||
|       :status="state.loadStatus" | ||||
|       :content-text="{ | ||||
| 				<view class="apply-box ss-flex ss-col-center ss-row-between border-bottom ss-p-x-20"> | ||||
| 					<view class="ss-flex ss-col-center"> | ||||
| 						<view class="title ss-m-r-20">{{ order.way === 10 ? '仅退款' : '退款退货' }}</view> | ||||
| 						 <view class="value">{{ formatAfterSaleStatusDescription(order) }}</view> | ||||
| 					</view> | ||||
| 					<text class="_icon-forward"></text> | ||||
| 				</view> | ||||
| 				<view class="tool-btn-box ss-flex ss-col-center ss-row-right ss-p-r-20"> | ||||
|           <!-- TODO 功能缺失:填写退货信息 --> | ||||
| 					<view> | ||||
| 						<button class="ss-reset-button tool-btn" @tap.stop="onApply(order.id)" | ||||
| 							v-if="order?.buttons.includes('cancel')">取消申请</button> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 		<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{ | ||||
|         contentdown: '上拉加载更多', | ||||
|       }" | ||||
|       @tap="loadMore" | ||||
|     /> | ||||
|   </s-layout> | ||||
|       }" @tap="loadMore" /> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { | ||||
|     formatAfterSaleStatus, | ||||
|     formatAfterSaleStatusDescription, | ||||
|     handleAfterSaleButtons, | ||||
|   } from '@/sheep/hooks/useGoods'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 	import _ from 'lodash'; | ||||
|   import { formatAfterSaleStatus, formatAfterSaleStatusDescription, handleAfterSaleButtons } from '@/sheep/hooks/useGoods'; | ||||
|   import AfterSaleApi from '@/sheep/api/trade/afterSale'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     currentTab: 0, | ||||
|     showApply: false, | ||||
|     pagination: { | ||||
| 	const paginationNull = { | ||||
| 		list: [], | ||||
|     total: 0, | ||||
|     pageNo: 1, | ||||
|     pageSize: 10 | ||||
| 	}; | ||||
| 
 | ||||
| 	const state = reactive({ | ||||
| 		currentTab: 0, | ||||
| 		showApply: false, | ||||
| 		pagination: { | ||||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 10, | ||||
|     }, | ||||
|     loadStatus: '', | ||||
|   }); | ||||
|       pageSize: 10 | ||||
| 		}, | ||||
| 		loadStatus: '', | ||||
| 	}); | ||||
| 
 | ||||
|   const tabMaps = [ | ||||
|     { | ||||
|       name: '全部', | ||||
|       value: [], | ||||
|     }, | ||||
|     { | ||||
|       name: '申请中', | ||||
|       value: [10], | ||||
|     }, | ||||
|     { | ||||
|       name: '处理中', | ||||
|       value: [20, 30, 40], | ||||
|     }, | ||||
|     { | ||||
|       name: '已完成', | ||||
|       value: [50], | ||||
|     }, | ||||
|     { | ||||
|       name: '已拒绝', | ||||
|       value: [61, 62, 63], | ||||
|     }, | ||||
|   ]; | ||||
|   // TODO 芋艿:优化点,增加筛选 | ||||
| 	const tabMaps = [{ | ||||
| 			name: '全部', | ||||
| 			value: 'all', | ||||
| 		}, | ||||
| 		// { | ||||
| 		//   name: '申请中', | ||||
| 		//   value: 'nooper', | ||||
| 		// }, | ||||
| 		// { | ||||
| 		//   name: '处理中', | ||||
| 		//   value: 'ing', | ||||
| 		// }, | ||||
| 		// { | ||||
| 		//   name: '已完成', | ||||
| 		//   value: 'completed', | ||||
| 		// }, | ||||
| 		// { | ||||
| 		//   name: '已拒绝', | ||||
| 		//   value: 'refuse', | ||||
| 		// }, | ||||
| 	]; | ||||
| 
 | ||||
|   // 切换选项卡 | ||||
|   function onTabsChange(e) { | ||||
|     resetPagination(state.pagination); | ||||
|     state.currentTab = e.index; | ||||
|     getOrderList(); | ||||
|   } | ||||
| 	// 切换选项卡 | ||||
| 	function onTabsChange(e) { | ||||
| 		state.pagination = paginationNull | ||||
| 		state.currentTab = e.index; | ||||
| 		getOrderList(); | ||||
| 	} | ||||
| 
 | ||||
|   // 获取售后列表 | ||||
|   async function getOrderList() { | ||||
|     state.loadStatus = 'loading'; | ||||
|     let { data, code } = await AfterSaleApi.getAfterSalePage({ | ||||
| 	// 获取售后列表 | ||||
| 	async function getOrderList() { | ||||
| 		state.loadStatus = 'loading'; | ||||
| 		let { data, code } = await AfterSaleApi.getAfterSalePage({ | ||||
| 			// type: tabMaps[state.currentTab].value, | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       statuses: tabMaps[state.currentTab].value.join(','), | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
| 		}); | ||||
| 		if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     data.list.forEach((order) => handleAfterSaleButtons(order)); | ||||
| 		} | ||||
|     data.list.forEach(order => handleAfterSaleButtons(order)); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   function onApply(orderId) { | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确定要取消此申请吗?', | ||||
|       success: async function (res) { | ||||
|         if (!res.confirm) { | ||||
| 	function onApply(orderId) { | ||||
| 		uni.showModal({ | ||||
| 			title: '提示', | ||||
| 			content: '确定要取消此申请吗?', | ||||
| 			success: async function(res) { | ||||
| 				if (!res.confirm) { | ||||
|           return; | ||||
|         } | ||||
| 				} | ||||
|         const { code } = await AfterSaleApi.cancelAfterSale(orderId); | ||||
|         if (code === 0) { | ||||
|           resetPagination(state.pagination); | ||||
|           state.pagination = paginationNull | ||||
|           await getOrderList(); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|   onLoad(async (options) => { | ||||
|     if (options.type) { | ||||
|       state.currentTab = options.type; | ||||
|     } | ||||
|     await getOrderList(); | ||||
|   }); | ||||
| 	onLoad(async (options) => { | ||||
| 		if (options.type) { | ||||
| 			state.currentTab = options.type; | ||||
| 		} | ||||
| 		await getOrderList(); | ||||
| 	}); | ||||
| 
 | ||||
|   // 加载更多 | ||||
|   function loadMore() { | ||||
| 	// 加载更多 | ||||
| 	function loadMore() { | ||||
|     if (state.loadStatus === 'noMore') { | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     state.pagination.pageNo++; | ||||
|     getOrderList(); | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   // 上拉加载更多 | ||||
|   onReachBottom(() => { | ||||
|     loadMore(); | ||||
|   }); | ||||
| 	// 上拉加载更多 | ||||
| 	onReachBottom(() => { | ||||
| 		loadMore(); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .list-box { | ||||
|     background-color: #fff; | ||||
| 	.list-box { | ||||
| 		background-color: #fff; | ||||
| 
 | ||||
|     .order-head { | ||||
|       padding: 0 25rpx; | ||||
|       height: 77rpx; | ||||
|     } | ||||
| 		.order-head { | ||||
| 			padding: 0 25rpx; | ||||
| 			height: 77rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .apply-box { | ||||
|       height: 82rpx; | ||||
| 		.apply-box { | ||||
| 			height: 82rpx; | ||||
| 
 | ||||
|       .title { | ||||
|         font-size: 24rpx; | ||||
|       } | ||||
| 			.title { | ||||
| 				font-size: 24rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .value { | ||||
|         font-size: 22rpx; | ||||
|         color: $dark-6; | ||||
|       } | ||||
|     } | ||||
| 			.value { | ||||
| 				font-size: 22rpx; | ||||
| 				color: $dark-6; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|     .tool-btn-box { | ||||
|       height: 100rpx; | ||||
| 		.tool-btn-box { | ||||
| 			height: 100rpx; | ||||
| 
 | ||||
|       .tool-btn { | ||||
|         width: 160rpx; | ||||
|         height: 60rpx; | ||||
|         background: #f6f6f6; | ||||
|         border-radius: 30rpx; | ||||
|         font-size: 26rpx; | ||||
|         font-weight: 400; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 			.tool-btn { | ||||
| 				width: 160rpx; | ||||
| 				height: 60rpx; | ||||
| 				background: #f6f6f6; | ||||
| 				border-radius: 30rpx; | ||||
| 				font-size: 26rpx; | ||||
| 				font-weight: 400; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -74,10 +74,4 @@ | |||
|     color: #999999; | ||||
|     margin-bottom: 40rpx; | ||||
|   } | ||||
| 
 | ||||
|   :deep() { | ||||
|     image { | ||||
|       display: block; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -6,15 +6,14 @@ | |||
| 					<view class='list borRadius14'> | ||||
| 						<view class='item acea-row row-between-wrapper' style="display: flex;align-items: center;"> | ||||
| 							<view>物流公司</view> | ||||
| 							<view v-if="state.expresses.length>0" style="flex:1"> | ||||
| 								<picker mode='selector' class='num' @change="bindPickerChange" :value="state.expressIndex" | ||||
| 									:range="state.expresses" range-key="name"> | ||||
| 									<view class="picker acea-row row-between-wrapper" style="display: flex;justify-content: space-between;"> | ||||
| 										<view class='reason'>{{ state.expresses[state.expressIndex].name }}</view> | ||||
| 										<text class='iconfont _icon-forward' /> | ||||
| 									</view> | ||||
| 								</picker> | ||||
| 							</view> | ||||
| 							<picker mode='selector' class='num' @change="bindPickerChange" :value="state.expressIndex" | ||||
|                       :range="state.expresses" range-key="name"> | ||||
| 								<view class="picker acea-row row-between-wrapper"> | ||||
| 									<view class='reason'>{{ state.expresses[state.expressIndex].name }}</view> | ||||
|                   <!-- TODO 芋艿:这里样式有问题,少了 > 按钮 --> | ||||
| 									<text class='iconfont icon-jiantou' /> | ||||
| 								</view> | ||||
| 							</picker> | ||||
| 						</view> | ||||
| 						<view class='item textarea acea-row row-between' style="display: flex;align-items: center;"> | ||||
| 							<view>物流单号</view> | ||||
|  |  | |||
|  | @ -1,7 +1,13 @@ | |||
| <template> | ||||
|   <s-layout title="确认订单"> | ||||
|     <!-- 头部地址选择【配送地址】【自提地址】 --> | ||||
|     <AddressSelection v-model="addressState" /> | ||||
|     <!-- TODO:这个判断先删除 v-if="state.orderInfo.need_address === 1" --> | ||||
|     <view class="bg-white address-box ss-m-b-14 ss-r-b-10" @tap="onSelectAddress"> | ||||
|       <s-address-item :item="state.addressInfo" :hasBorderBottom="false"> | ||||
|         <view class="ss-rest-button"> | ||||
|           <text class="_icon-forward" /> | ||||
|         </view> | ||||
|       </s-address-item> | ||||
|     </view> | ||||
| 
 | ||||
|     <!-- 商品信息 --> | ||||
|     <view class="order-card-box ss-m-b-14"> | ||||
|  | @ -40,89 +46,26 @@ | |||
|             </text> | ||||
|           </view> | ||||
|         </view> | ||||
|         <!-- TODO 芋艿:接入积分 --> | ||||
|         <view | ||||
|           v-if="state.orderPayload.pointActivityId" | ||||
|           class="order-item ss-flex ss-col-center ss-row-between" | ||||
|           v-if="state.orderPayload.order_type === 'score'" | ||||
|         > | ||||
|           <view class="item-title">兑换积分</view> | ||||
|           <view class="item-title">扣除积分</view> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <image | ||||
|               :src="sheep.$url.static('/static/img/shop/goods/score1.svg')" | ||||
|               class="score-img" | ||||
|             /> | ||||
|             <text class="item-value ss-m-r-24"> | ||||
|               {{ state.orderInfo.usePoint }} | ||||
|             </text> | ||||
|             <text class="item-value ss-m-r-24">{{ state.orderInfo.score_amount }}</text> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view | ||||
|           class="order-item ss-flex ss-col-center ss-row-between" | ||||
|           v-if="state.orderInfo.type === 0 || state.orderPayload.pointActivityId" | ||||
|         > | ||||
|           <view class="item-title">积分抵扣</view> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             {{ state.pointStatus || state.orderPayload.pointActivityId ? '剩余积分' : '当前积分' }} | ||||
|             <image | ||||
|               :src="sheep.$url.static('/static/img/shop/goods/score1.svg')" | ||||
|               class="score-img" | ||||
|             /> | ||||
|             <text class="item-value ss-m-r-24"> | ||||
|               {{ | ||||
|                 state.pointStatus || state.orderPayload.pointActivityId | ||||
|                   ? state.orderInfo.totalPoint - state.orderInfo.usePoint | ||||
|                   : state.orderInfo.totalPoint || 0 | ||||
|               }} | ||||
|             </text> | ||||
|             <checkbox-group @change="changeIntegral" v-if="!state.orderPayload.pointActivityId"> | ||||
|               <checkbox | ||||
|                 :checked="state.pointStatus" | ||||
|                 :disabled="!state.orderInfo.totalPoint || state.orderInfo.totalPoint <= 0" | ||||
|               /> | ||||
|             </checkbox-group> | ||||
|           </view> | ||||
|         </view> | ||||
|         <!-- 快递配置时,信息的展示 --> | ||||
|         <view | ||||
|           class="order-item ss-flex ss-col-center ss-row-between" | ||||
|           v-if="addressState.deliveryType === 1" | ||||
|         > | ||||
|         <view class="order-item ss-flex ss-col-center ss-row-between"> | ||||
|           <view class="item-title">运费</view> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <text class="item-value ss-m-r-24" v-if="state.orderInfo.price.deliveryPrice > 0"> | ||||
|             <text class="item-value ss-m-r-24"> | ||||
|               +¥{{ fen2yuan(state.orderInfo.price.deliveryPrice) }} | ||||
|             </text> | ||||
|             <view class="item-value ss-m-r-24" v-else>免运费</view> | ||||
|           </view> | ||||
|         </view> | ||||
|         <!-- 门店自提时,需要填写姓名和手机号 --> | ||||
|         <view | ||||
|           class="order-item ss-flex ss-col-center ss-row-between" | ||||
|           v-if="addressState.deliveryType === 2" | ||||
|         > | ||||
|           <view class="item-title">联系人</view> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <uni-easyinput | ||||
|               maxlength="20" | ||||
|               placeholder="请填写您的联系姓名" | ||||
|               v-model="addressState.receiverName" | ||||
|               :inputBorder="false" | ||||
|               :clearable="false" | ||||
|             /> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view | ||||
|           class="order-item ss-flex ss-col-center ss-row-between" | ||||
|           v-if="addressState.deliveryType === 2" | ||||
|         > | ||||
|           <view class="item-title">联系电话</view> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <uni-easyinput | ||||
|               maxlength="20" | ||||
|               placeholder="请填写您的联系电话" | ||||
|               v-model="addressState.receiverMobile" | ||||
|               :inputBorder="false" | ||||
|               :clearable="false" | ||||
|             /> | ||||
|           </view> | ||||
|         </view> | ||||
|         <!-- 优惠劵:只有 type = 0 普通订单(非拼团、秒杀、砍价),才可以使用优惠劵 --> | ||||
|  | @ -137,17 +80,11 @@ | |||
|             </text> | ||||
|             <text | ||||
|               class="item-value" | ||||
|               :class=" | ||||
|                 state.couponInfo.filter((coupon) => coupon.match).length > 0 | ||||
|                   ? 'text-red' | ||||
|                   : 'text-disabled' | ||||
|               " | ||||
|               :class="state.couponInfo.length > 0 ? 'text-red' : 'text-disabled'" | ||||
|               v-else | ||||
|             > | ||||
|               {{ | ||||
|                 state.couponInfo.filter((coupon) => coupon.match).length > 0 | ||||
|                   ? state.couponInfo.filter((coupon) => coupon.match).length + ' 张可用' | ||||
|                   : '暂无可用优惠券' | ||||
|                 state.couponInfo.length > 0 ? state.couponInfo.length + ' 张可用' : '暂无可用优惠券' | ||||
|               }} | ||||
|             </text> | ||||
|             <text class="_icon-forward item-icon" /> | ||||
|  | @ -158,31 +95,21 @@ | |||
|           v-if="state.orderInfo.price.discountPrice > 0" | ||||
|         > | ||||
|           <view class="item-title">活动优惠</view> | ||||
|           <view class="ss-flex ss-col-center" @tap="state.showDiscount = true"> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <!--                @tap="state.showDiscount = true" TODO 芋艿:后续要把优惠信息打进去 --> | ||||
|             <text class="item-value text-red"> | ||||
|               -¥{{ fen2yuan(state.orderInfo.price.discountPrice) }} | ||||
|             </text> | ||||
|             <text class="_icon-forward item-icon" /> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view | ||||
|           class="order-item ss-flex ss-col-center ss-row-between" | ||||
|           v-if="state.orderInfo.price.vipPrice > 0" | ||||
|         > | ||||
|           <view class="item-title">会员优惠</view> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <text class="item-value text-red"> | ||||
|               -¥{{ fen2yuan(state.orderInfo.price.vipPrice) }} | ||||
|             </text> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|       <view class="total-box-footer ss-font-28 ss-flex ss-row-right ss-col-center ss-m-r-28"> | ||||
|         <view class="total-num ss-m-r-20"> | ||||
|           共{{ state.orderInfo.items.reduce((acc, item) => acc + item.count, 0) }}件 | ||||
|         </view> | ||||
|         <view>合计:</view> | ||||
|         <view class="total-num text-red"> ¥{{ fen2yuan(state.orderInfo.price.payPrice) }}</view> | ||||
|         <view class="total-num text-red"> ¥{{ fen2yuan(state.orderInfo.price.payPrice) }} </view> | ||||
|       </view> | ||||
|     </view> | ||||
| 
 | ||||
|  | @ -194,7 +121,7 @@ | |||
|       @close="state.showCoupon = false" | ||||
|     /> | ||||
| 
 | ||||
|     <!-- 满额折扣弹框 --> | ||||
|     <!-- 满额折扣弹框 TODO 芋艿:后续要把优惠信息打进去 --> | ||||
|     <s-discount-list | ||||
|       v-model="state.orderInfo" | ||||
|       :show="state.showDiscount" | ||||
|  | @ -221,14 +148,13 @@ | |||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { reactive, ref, watch } from 'vue'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import AddressSelection from '@/pages/order/addressSelection.vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isEmpty } from 'lodash'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|   import TradeConfigApi from '@/sheep/api/trade/config'; | ||||
|   import CouponApi from '@/sheep/api/promotion/coupon'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import { DeliveryTypeEnum } from '@/sheep/helper/const'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     orderPayload: {}, | ||||
|  | @ -236,62 +162,41 @@ | |||
|       items: [], // 商品项列表 | ||||
|       price: {}, // 价格信息 | ||||
|     }, | ||||
|     addressInfo: {}, // 选择的收货地址 | ||||
|     showCoupon: false, // 是否展示优惠劵 | ||||
|     couponInfo: [], // 优惠劵列表 | ||||
|     showDiscount: false, // 是否展示营销活动 | ||||
|     // ========== 积分 ========== | ||||
|     pointStatus: false, //是否使用积分 | ||||
|   }); | ||||
| 
 | ||||
|   const addressState = ref({ | ||||
|     addressInfo: {}, // 选择的收货地址 | ||||
|     deliveryType: undefined, // 收货方式:1-快递配送,2-门店自提 | ||||
|     isPickUp: true, // 门店自提是否开启 | ||||
|     pickUpInfo: {}, // 选择的自提门店信息 | ||||
|     receiverName: '', // 收件人名称 | ||||
|     receiverMobile: '', // 收件人手机 | ||||
|   }); | ||||
|   // 选择地址 | ||||
|   function onSelectAddress() { | ||||
|     uni.$once('SELECT_ADDRESS', (e) => { | ||||
|       changeConsignee(e.addressInfo); | ||||
|     }); | ||||
|     sheep.$router.go('/pages/user/address/list'); | ||||
|   } | ||||
| 
 | ||||
|   // ========== 积分 ========== | ||||
|   /** | ||||
|    * 使用积分抵扣 | ||||
|    */ | ||||
|   const changeIntegral = async () => { | ||||
|     state.pointStatus = !state.pointStatus; | ||||
|   // 更改收货人地址&计算订单信息 | ||||
|   async function changeConsignee(addressInfo = {}) { | ||||
|     if (!isEmpty(addressInfo)) { | ||||
|       state.addressInfo = addressInfo; | ||||
|     } | ||||
|     await getOrderInfo(); | ||||
|   }; | ||||
|   } | ||||
| 
 | ||||
|   // 选择优惠券 | ||||
|   async function onSelectCoupon(couponId) { | ||||
|     state.orderPayload.couponId = couponId; | ||||
|     state.orderPayload.couponId = couponId || 0; | ||||
|     await getOrderInfo(); | ||||
|     state.showCoupon = false; | ||||
|   } | ||||
| 
 | ||||
|   // 提交订单 | ||||
|   function onConfirm() { | ||||
|     if (addressState.value.deliveryType === 1 && !addressState.value.addressInfo.id) { | ||||
|     if (!state.addressInfo.id) { | ||||
|       sheep.$helper.toast('请选择收货地址'); | ||||
|       return; | ||||
|     } | ||||
|     if (addressState.value.deliveryType === 2) { | ||||
|       if (!addressState.value.pickUpInfo.id) { | ||||
|         sheep.$helper.toast('请选择自提门店地址'); | ||||
|         return; | ||||
|       } | ||||
|       if (addressState.value.receiverName === '' || addressState.value.receiverMobile === '') { | ||||
|         sheep.$helper.toast('请填写联系人或联系人电话'); | ||||
|         return; | ||||
|       } | ||||
|       if (!/^[\u4e00-\u9fa5\w]{2,16}$/.test(addressState.value.receiverName)) { | ||||
|         sheep.$helper.toast('请填写您的真实姓名'); | ||||
|         return; | ||||
|       } | ||||
|       if (!/^1(3|4|5|7|8|9|6)\d{9}$/.test(addressState.value.receiverMobile)) { | ||||
|         sheep.$helper.toast('请填写正确的手机号'); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     submitOrder(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -300,17 +205,12 @@ | |||
|     const { code, data } = await OrderApi.createOrder({ | ||||
|       items: state.orderPayload.items, | ||||
|       couponId: state.orderPayload.couponId, | ||||
|       remark: state.orderPayload.remark, | ||||
|       deliveryType: addressState.value.deliveryType, | ||||
|       addressId: addressState.value.addressInfo.id, // 收件地址编号 | ||||
|       pickUpStoreId: addressState.value.pickUpInfo.id, //自提门店编号 | ||||
|       receiverName: addressState.value.receiverName, // 选择门店自提时,该字段为联系人名 | ||||
|       receiverMobile: addressState.value.receiverMobile, // 选择门店自提时,该字段为联系人手机 | ||||
|       pointStatus: state.pointStatus, | ||||
|       addressId: state.addressInfo.id, | ||||
|       deliveryType: 1, // TODO 芋艿:需要支持【门店自提】 | ||||
|       pointStatus: false, // TODO 芋艿:需要支持【积分选择】 | ||||
|       combinationActivityId: state.orderPayload.combinationActivityId, | ||||
|       combinationHeadId: state.orderPayload.combinationHeadId, | ||||
|       seckillActivityId: state.orderPayload.seckillActivityId, | ||||
|       pointActivityId: state.orderPayload.pointActivityId, | ||||
|       seckillActivityId: state.orderPayload.seckillActivityId | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|  | @ -319,17 +219,10 @@ | |||
|     if (state.orderPayload.items[0].cartId > 0) { | ||||
|       sheep.$store('cart').getList(); | ||||
|     } | ||||
| 
 | ||||
|     // 跳转到支付页面 | ||||
|     if (data.payOrderId && data.payOrderId > 0) { | ||||
|       sheep.$router.redirect('/pages/pay/index', { | ||||
|         id: data.payOrderId, | ||||
|       }); | ||||
|     } else { | ||||
|       sheep.$router.redirect('/pages/order/detail', { | ||||
|         id: data.id, | ||||
|       }); | ||||
|     } | ||||
|     sheep.$router.redirect('/pages/pay/index', { | ||||
|       id: data.payOrderId, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 检查库存 & 计算订单价格 | ||||
|  | @ -338,72 +231,44 @@ | |||
|     const { data, code } = await OrderApi.settlementOrder({ | ||||
|       items: state.orderPayload.items, | ||||
|       couponId: state.orderPayload.couponId, | ||||
|       deliveryType: addressState.value.deliveryType, | ||||
|       addressId: addressState.value.addressInfo.id, // 收件地址编号 | ||||
|       pickUpStoreId: addressState.value.pickUpInfo.id, //自提门店编号 | ||||
|       receiverName: addressState.value.receiverName, // 选择门店自提时,该字段为联系人名 | ||||
|       receiverMobile: addressState.value.receiverMobile, // 选择门店自提时,该字段为联系人手机 | ||||
|       pointStatus: state.pointStatus, | ||||
|       addressId: state.addressInfo.id, | ||||
|       deliveryType: 1, // TODO 芋艿:需要支持【门店自提】 | ||||
|       pointStatus: false, // TODO 芋艿:需要支持【积分选择】 | ||||
|       combinationActivityId: state.orderPayload.combinationActivityId, | ||||
|       combinationHeadId: state.orderPayload.combinationHeadId, | ||||
|       seckillActivityId: state.orderPayload.seckillActivityId, | ||||
|       pointActivityId: state.orderPayload.pointActivityId, | ||||
|       seckillActivityId: state.orderPayload.seckillActivityId | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return code; | ||||
|       return; | ||||
|     } | ||||
|     state.orderInfo = data; | ||||
|     state.couponInfo = data.coupons || []; | ||||
|     // 设置收货地址 | ||||
|     if (state.orderInfo.address) { | ||||
|       addressState.value.addressInfo = state.orderInfo.address; | ||||
|       state.addressInfo = state.orderInfo.address; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // 获取可用优惠券 | ||||
|   async function getCoupons() { | ||||
|     const { code, data } = await CouponApi.getMatchCouponList( | ||||
|       state.orderInfo.price.payPrice, | ||||
|       state.orderInfo.items.map((item) => item.spuId), | ||||
|       state.orderPayload.items.map((item) => item.skuId), | ||||
|       state.orderPayload.items.map((item) => item.categoryId), | ||||
|     ); | ||||
|     if (code === 0) { | ||||
|       state.couponInfo = data; | ||||
|     } | ||||
|     return code; | ||||
|   } | ||||
| 
 | ||||
|   onLoad(async (options) => { | ||||
|     // 解析参数 | ||||
|     if (!options.data) { | ||||
|       sheep.$helper.toast('参数不正确,请检查!'); | ||||
|       return; | ||||
|     } | ||||
|     state.orderPayload = JSON.parse(options.data); | ||||
| 
 | ||||
|     // 获取交易配置 | ||||
|     const { data, code } = await TradeConfigApi.getTradeConfig(); | ||||
|     if (code === 0) { | ||||
|       addressState.value.isPickUp = data.deliveryPickUpEnabled; | ||||
|     } | ||||
| 
 | ||||
|     // 价格计算 | ||||
|     // 情况一:先自动选择“快递物流” | ||||
|     addressState.value.deliveryType = DeliveryTypeEnum.EXPRESS.type; | ||||
|     let orderCode = await getOrderInfo(); | ||||
|     if (orderCode === 0) { | ||||
|       return; | ||||
|     } | ||||
|     // 情况二:失败,再自动选择“门店自提” | ||||
|     if (addressState.value.isPickUp) { | ||||
|       addressState.value.deliveryType = DeliveryTypeEnum.PICK_UP.type; | ||||
|       let orderCode = await getOrderInfo(); | ||||
|       if (orderCode === 0) { | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     // 情况三:都失败,则不选择 | ||||
|     addressState.value.deliveryType = undefined; | ||||
|     await getOrderInfo(); | ||||
|   }); | ||||
| 
 | ||||
|   // 使用 watch 监听地址和配送方式的变化 | ||||
|   watch(addressState, async (newAddress, oldAddress) => { | ||||
|     // 如果收货地址或配送方式有变化,则重新计算价格 | ||||
|     if ( | ||||
|       newAddress.addressInfo.id !== oldAddress.addressInfo.id || | ||||
|       newAddress.deliveryType !== oldAddress.deliveryType | ||||
|     ) { | ||||
|       await getOrderInfo(); | ||||
|     } | ||||
|     await getCoupons(); | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| <!-- 订单详情 --> | ||||
| <template> | ||||
|   <s-layout title="订单详情" class="index-wrap" navbar="inner"> | ||||
|     <!-- 订单状态 TODO --> | ||||
|     <view | ||||
|       class="state-box ss-flex-col ss-col-center ss-row-right" | ||||
|       :style="[ | ||||
|  | @ -11,41 +12,42 @@ | |||
|       ]" | ||||
|     > | ||||
|       <view class="ss-flex ss-m-t-32 ss-m-b-20"> | ||||
|         <!-- 待支付 --> | ||||
|         <image | ||||
|           v-if="state.orderInfo.status === 0" | ||||
|           class="state-img" | ||||
|           :src="sheep.$url.static('/static/img/shop/order/no_pay.png')" | ||||
|         /> | ||||
|         <!-- 待发货 --> | ||||
|         <image | ||||
|           v-if="state.orderInfo.status === 10" | ||||
|           v-if=" | ||||
|             state.orderInfo.status_code == 'unpaid' || | ||||
|             state.orderInfo.status === 10 || // 待发货 | ||||
|             state.orderInfo.status_code == 'nocomment' | ||||
|           " | ||||
|           class="state-img" | ||||
|           :src="sheep.$url.static('/static/img/shop/order/order_loading.png')" | ||||
|         /> | ||||
|         <!-- 已完成 --> | ||||
|         > | ||||
|         </image> | ||||
|         <image | ||||
|           v-else-if="state.orderInfo.status === 30" | ||||
|           v-if=" | ||||
|             state.orderInfo.status_code == 'completed' || | ||||
|             state.orderInfo.status_code == 'refund_agree' | ||||
|           " | ||||
|           class="state-img" | ||||
|           :src="sheep.$url.static('/static/img/shop/order/order_success.png')" | ||||
|         /> | ||||
|         <!-- 已关闭 --> | ||||
|         > | ||||
|         </image> | ||||
|         <image | ||||
|           v-else-if="state.orderInfo.status === 40" | ||||
|           v-if="state.orderInfo.status_code == 'cancel' || state.orderInfo.status_code == 'closed'" | ||||
|           class="state-img" | ||||
|           :src="sheep.$url.static('/static/img/shop/order/order_close.png')" | ||||
|         /> | ||||
|         <!-- 已发货 --> | ||||
|         > | ||||
|         </image> | ||||
|         <image | ||||
|           v-else-if="state.orderInfo.status === 20" | ||||
|           v-if="state.orderInfo.status_code == 'noget'" | ||||
|           class="state-img" | ||||
|           :src="sheep.$url.static('/static/img/shop/order/order_express.png')" | ||||
|         /> | ||||
|         > | ||||
|         </image> | ||||
|         <view class="ss-font-30">{{ formatOrderStatus(state.orderInfo) }}</view> | ||||
|       </view> | ||||
|       <view class="ss-font-26 ss-m-x-20 ss-m-b-70"> | ||||
|         {{ formatOrderStatusDescription(state.orderInfo) }} | ||||
|       </view> | ||||
|       <view class="ss-font-26 ss-m-x-20 ss-m-b-70">{{ | ||||
|         formatOrderStatusDescription(state.orderInfo) | ||||
|       }}</view> | ||||
|     </view> | ||||
| 
 | ||||
|     <!-- 收货地址 --> | ||||
|  | @ -65,11 +67,11 @@ | |||
|       class="detail-goods" | ||||
|       :style="[{ marginTop: state.orderInfo.receiverAreaId > 0 ? '0' : '-40rpx' }]" | ||||
|     > | ||||
|       <!-- 订单信 --> | ||||
|       <!-- 订单信息 TODO 芋艿: --> | ||||
|       <view class="order-list" v-for="item in state.orderInfo.items" :key="item.goods_id"> | ||||
|         <view class="order-card"> | ||||
|           <s-goods-item | ||||
|             @tap="onGoodsDetail(item.spuId)" | ||||
|             @tap="onGoodsDetail(item.skuId)" | ||||
|             :img="item.picUrl" | ||||
|             :title="item.spuName" | ||||
|             :skuText="item.properties.map((property) => property.valueName).join(' ')" | ||||
|  | @ -124,13 +126,6 @@ | |||
|       </view> | ||||
|     </view> | ||||
| 
 | ||||
|     <!--  自提核销  --> | ||||
|     <PickUpVerify | ||||
|       :order-info="state.orderInfo" | ||||
|       :systemStore="systemStore" | ||||
|       ref="pickUpVerifyRef" | ||||
|     ></PickUpVerify> | ||||
| 
 | ||||
|     <!-- 订单信息 --> | ||||
|     <view class="notice-box"> | ||||
|       <view class="notice-box__content"> | ||||
|  | @ -172,22 +167,11 @@ | |||
|         <text class="title">运费</text> | ||||
|         <text class="detail">¥{{ fen2yuan(state.orderInfo.deliveryPrice) }}</text> | ||||
|       </view> | ||||
|       <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.couponPrice > 0"> | ||||
|         <text class="title">优惠劵金额</text> | ||||
|         <text class="detail">-¥{{ fen2yuan(state.orderInfo.couponPrice) }}</text> | ||||
|       </view> | ||||
|       <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.pointPrice > 0"> | ||||
|         <text class="title">积分抵扣</text> | ||||
|         <text class="detail">-¥{{ fen2yuan(state.orderInfo.pointPrice) }}</text> | ||||
|       </view> | ||||
|       <!-- TODO 芋艿:优惠劵抵扣、积分抵扣 --> | ||||
|       <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.discountPrice > 0"> | ||||
|         <text class="title">活动优惠</text> | ||||
|         <text class="title">优惠金额</text> | ||||
|         <text class="detail">¥{{ fen2yuan(state.orderInfo.discountPrice) }}</text> | ||||
|       </view> | ||||
|       <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.vipPrice > 0"> | ||||
|         <text class="title">会员优惠</text> | ||||
|         <text class="detail">-¥{{ fen2yuan(state.orderInfo.vipPrice) }}</text> | ||||
|       </view> | ||||
|       <view class="notice-item all-rpice-item ss-flex ss-m-t-20"> | ||||
|         <text class="title">{{ state.orderInfo.payStatus ? '已付款' : '需付款' }}</text> | ||||
|         <text class="detail all-price">¥{{ fen2yuan(state.orderInfo.payPrice) }}</text> | ||||
|  | @ -219,12 +203,13 @@ | |||
|         > | ||||
|           继续支付 | ||||
|         </button> | ||||
|         <!-- TODO 芋艿:拼团接入 --> | ||||
|         <button | ||||
|           class="ss-reset-button cancel-btn" | ||||
|           v-if="state.orderInfo.buttons?.includes('combination')" | ||||
|           @tap=" | ||||
|             sheep.$router.go('/pages/activity/groupon/detail', { | ||||
|               id: state.orderInfo.combinationRecordId, | ||||
|               id: state.orderInfo.ext.groupon_id, | ||||
|             }) | ||||
|           " | ||||
|         > | ||||
|  | @ -258,9 +243,9 @@ | |||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onShow } from '@dcloudio/uni-app'; | ||||
|   import { reactive, ref } from 'vue'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import { isEmpty } from 'lodash'; | ||||
|   import { | ||||
|     fen2yuan, | ||||
|     formatOrderStatus, | ||||
|  | @ -268,8 +253,6 @@ | |||
|     handleOrderButtons, | ||||
|   } from '@/sheep/hooks/useGoods'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|   import DeliveryApi from '@/sheep/api/trade/delivery'; | ||||
|   import PickUpVerify from '@/pages/order/pickUpVerify.vue'; | ||||
| 
 | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png'); | ||||
|  | @ -280,12 +263,9 @@ | |||
|     comeinType: '', // 进入订单详情的来源类型 | ||||
|   }); | ||||
| 
 | ||||
|   // ========== 门店自提(核销) ========== | ||||
|   const systemStore = ref({}); // 门店信息 | ||||
| 
 | ||||
|   // 复制 | ||||
|   const onCopy = () => { | ||||
|     sheep.$helper.copyText(state.orderInfo.no); | ||||
|     sheep.$helper.copyText(state.orderInfo.sn); | ||||
|   }; | ||||
| 
 | ||||
|   // 去支付 | ||||
|  | @ -326,10 +306,10 @@ | |||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 确认收货 | ||||
|   // 确认收货 TODO 芋艿:待测试 | ||||
|   async function onConfirm(orderId, ignore = false) { | ||||
|     // 需开启确认收货组件 | ||||
|     // todo: 芋艿:【微信物流】待接入微信 https://gitee.com/sheepjs/shopro-uniapp/commit/a6bbba49b84dd418b84c5fefc8b7463df8f4901f | ||||
|     // todo: 芋艿:待接入微信 | ||||
|     // 1.怎么检测是否开启了发货组件功能?如果没有开启的话就不能在这里return出去 | ||||
|     // 2.如果开启了走mpConfirm方法,需要在App.vue的show方法中拿到确认收货结果 | ||||
|     let isOpenBusinessView = true; | ||||
|  | @ -343,20 +323,11 @@ | |||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确认收货吗?', | ||||
|       success: async function (res) { | ||||
|         if (!res.confirm) { | ||||
|           return; | ||||
|         } | ||||
|         // 正常的确认收货流程 | ||||
|         const { code } = await OrderApi.receiveOrder(orderId); | ||||
|         if (code === 0) { | ||||
|           await getOrderDetail(orderId); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|     // 正常的确认收货流程 | ||||
|     const { code } = await OrderApi.receiveOrder(orderId); | ||||
|     if (code === 0) { | ||||
|       await getOrderDetail(orderId); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // #ifdef MP-WEIXIN | ||||
|  | @ -388,61 +359,45 @@ | |||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // #endif | ||||
| 
 | ||||
|   // 评价 | ||||
|   function onComment(id) { | ||||
|     sheep.$router.go('/pages/goods/comment/add', { | ||||
|       id, | ||||
|       id | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   const pickUpVerifyRef = ref(); | ||||
| 
 | ||||
|   async function getOrderDetail(id) { | ||||
|     // 对详情数据进行适配 | ||||
|     let res; | ||||
|     if (state.comeinType === 'wechat') { | ||||
|       // TODO 芋艿:【微信物流】微信场景下 | ||||
|       res = await OrderApi.getOrderDetail(id, { | ||||
|       // TODO 芋艿:微信场景下 | ||||
|       res = await OrderApi.getOrder(id, { | ||||
|         merchant_trade_no: state.merchantTradeNo, | ||||
|       }); | ||||
|     } else { | ||||
|       res = await OrderApi.getOrderDetail(id); | ||||
|       res = await OrderApi.getOrder(id); | ||||
|     } | ||||
|     if (res.code === 0) { | ||||
|       state.orderInfo = res.data; | ||||
|       handleOrderButtons(state.orderInfo); | ||||
|       // 配送方式:门店自提 | ||||
|       if (res.data.pickUpStoreId) { | ||||
|         const { data } = await DeliveryApi.getDeliveryPickUpStore(res.data.pickUpStoreId); | ||||
|         systemStore.value = data || {}; | ||||
|       } | ||||
|       if (state.orderInfo.deliveryType === 2 && state.orderInfo.payStatus) { | ||||
|         pickUpVerifyRef.value && pickUpVerifyRef.value.markCode(res.data.pickUpVerifyCode); | ||||
|       } | ||||
|     } else { | ||||
|       sheep.$router.back(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onShow(async () => { | ||||
|     //onShow中获取订单列表,保证跳转后页面为最新状态 | ||||
|     await getOrderDetail(state.orderInfo.id); | ||||
|   }); | ||||
| 
 | ||||
|   onLoad(async (options) => { | ||||
|     let id = 0; | ||||
|     if (options.id) { | ||||
|       id = options.id; | ||||
|     } | ||||
|     // TODO 芋艿:【微信物流】下面两个变量,后续接入 | ||||
|     // TODO 芋艿:下面两个变量,后续接入 | ||||
|     state.comeinType = options.comein_type; | ||||
|     if (state.comeinType === 'wechat') { | ||||
|       state.merchantTradeNo = options.merchant_trade_no; | ||||
|     } | ||||
|     state.orderInfo.id = id; | ||||
|     await getOrderDetail(id); | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,11 +12,11 @@ | |||
|           </swiper> | ||||
|         </uni-swiper-dot> | ||||
|         <view class="log-card-msg"> | ||||
|           <!-- TODO 芋艿:【物流】优化点:展示状态 --> | ||||
|           <!--          <view class="ss-flex ss-m-b-8">--> | ||||
|           <!--            <view>物流状态:</view>--> | ||||
|           <!--            <view class="warning-color">{{ state.info.status_text }}</view>--> | ||||
|           <!--          </view>--> | ||||
|           <!-- TODO 芋艿:优化点:展示状态 --> | ||||
| <!--          <view class="ss-flex ss-m-b-8">--> | ||||
| <!--            <view>物流状态:</view>--> | ||||
| <!--            <view class="warning-color">{{ state.info.status_text }}</view>--> | ||||
| <!--          </view>--> | ||||
|           <view class="ss-m-b-8">快递单号:{{ state.info.logisticsNo }}</view> | ||||
|           <view>快递公司:{{ state.info.logisticsName }}</view> | ||||
|         </view> | ||||
|  | @ -34,15 +34,11 @@ | |||
|             <view v-if="state.tracks.length - 1 !== index" class="line" /> | ||||
|           </view> | ||||
|           <view class="log-content-msg"> | ||||
|             <!-- TODO 芋艿:【物流】优化点:展示状态 --> | ||||
|             <!--            <view class="log-msg-title ss-m-b-20">--> | ||||
|             <!--              {{ item.status_text }}--> | ||||
|             <!--            </view>--> | ||||
|             <!--            <view class="log-msg-desc ss-m-b-16">{{ item.content }}</view>--> | ||||
|             <view class="log-msg-desc ss-m-b-16"> | ||||
|               <highlight-number :content="item.content" @phone-click="handlePhoneClick" /> | ||||
|               <!--              <rich-text :nodes="item.content"></rich-text>--> | ||||
|             </view> | ||||
|             <!-- TODO 芋艿:优化点:展示状态 --> | ||||
| <!--            <view class="log-msg-title ss-m-b-20">--> | ||||
| <!--              {{ item.status_text }}--> | ||||
| <!--            </view>--> | ||||
|             <view class="log-msg-desc ss-m-b-16">{{ item.content }}</view> | ||||
|             <view class="log-msg-date ss-m-b-40"> | ||||
|               {{ sheep.$helper.timeFormat(item.time, 'yyyy-mm-dd hh:MM:ss') }} | ||||
|             </view> | ||||
|  | @ -58,7 +54,6 @@ | |||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { computed, reactive } from 'vue'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|   import HighlightNumber from '@/pages/components/HighlightNumberText.vue'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     info: [], | ||||
|  | @ -79,11 +74,11 @@ | |||
| 
 | ||||
|   async function getExpressDetail(id) { | ||||
|     const { data } = await OrderApi.getOrderExpressTrackList(id); | ||||
|     state.tracks = data; | ||||
|     state.tracks = data.reverse(); | ||||
|   } | ||||
| 
 | ||||
|   async function getOrderDetail(id) { | ||||
|     const { data } = await OrderApi.getOrderDetail(id); | ||||
|     const { data } = await OrderApi.getOrder(id) | ||||
|     state.info = data; | ||||
|   } | ||||
| 
 | ||||
|  | @ -91,117 +86,53 @@ | |||
|     getExpressDetail(options.id); | ||||
|     getOrderDetail(options.id); | ||||
|   }); | ||||
| 
 | ||||
|   function handlePhoneClick(data) { | ||||
|     handleClick(data); | ||||
|   } | ||||
| 
 | ||||
|   function handleClick(data) { | ||||
|     const phoneNumber = data.phoneNumber; | ||||
| 
 | ||||
|     if (!phoneNumber) return; | ||||
| 
 | ||||
|     // 获取当前平台 | ||||
|     const platform = uni.getSystemInfoSync().platform.toLowerCase(); | ||||
| 
 | ||||
|     if (platform === 'devtools') { | ||||
|       uni.showToast({ title: '真机才可拨打电话', icon: 'none' }); | ||||
|       handleCopy(phoneNumber); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (platform === 'wechat') { | ||||
|       uni.showToast({ title: '请手动拨打', icon: 'none' }); | ||||
|       handleCopy(phoneNumber); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     uni.makePhoneCall({ | ||||
|       phoneNumber: phoneNumber, | ||||
|       success: () => { | ||||
|         console.log('拨打电话成功'); | ||||
|       }, | ||||
|       fail: (err) => { | ||||
|         console.error('拨打电话失败', err); | ||||
|         uni.showToast({ title: '拨号失败,请手动拨打', icon: 'none' }); | ||||
|         handleCopy(phoneNumber); | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
|   function handleCopy(text) { | ||||
|     uni.setClipboardData({ | ||||
|       data: text, | ||||
|       success: () => { | ||||
|         uni.showToast({ title: '已复制到剪贴板', icon: 'success' }); | ||||
|       }, | ||||
|       fail: () => { | ||||
|         uni.showToast({ title: '复制失败', icon: 'none' }); | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .highlight { | ||||
|     color: red; /* 高亮颜色 */ | ||||
|     font-weight: bold; | ||||
|   } | ||||
| 
 | ||||
|   .swiper-box { | ||||
|     width: 200rpx; | ||||
|     height: 200rpx; | ||||
|   } | ||||
| 
 | ||||
|   .log-card { | ||||
|     border-top: 2rpx solid rgba(#dfdfdf, 0.5); | ||||
|     padding: 20rpx; | ||||
|     background: #fff; | ||||
|     margin-bottom: 20rpx; | ||||
| 
 | ||||
|     .log-card-img { | ||||
|       width: 200rpx; | ||||
|       height: 200rpx; | ||||
|       margin-right: 20rpx; | ||||
|     } | ||||
| 
 | ||||
|     .log-card-msg { | ||||
|       font-size: 28rpx; | ||||
|       font-weight: 500; | ||||
|       width: 490rpx; | ||||
|       color: #333333; | ||||
| 
 | ||||
|       .warning-color { | ||||
|         color: #999; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .log-content { | ||||
|     padding: 34rpx 20rpx 0rpx 20rpx; | ||||
|     background: #fff; | ||||
| 
 | ||||
|     .log-content-box { | ||||
|       align-items: stretch; | ||||
|     } | ||||
| 
 | ||||
|     .log-icon { | ||||
|       height: inherit; | ||||
| 
 | ||||
|       .cicon-title { | ||||
|         color: #ccc; | ||||
|         font-size: 40rpx; | ||||
|       } | ||||
| 
 | ||||
|       .activity-color { | ||||
|         color: #f0c785; | ||||
|         font-size: 40rpx; | ||||
|       } | ||||
| 
 | ||||
|       .info-color { | ||||
|         color: #ccc; | ||||
|         font-size: 40rpx; | ||||
|       } | ||||
| 
 | ||||
|       .line { | ||||
|         width: 1px; | ||||
|         height: 100%; | ||||
|  | @ -215,18 +146,16 @@ | |||
|         font-weight: bold; | ||||
|         color: #333333; | ||||
|       } | ||||
| 
 | ||||
|       .log-msg-desc { | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 400; | ||||
|         color: #333333; | ||||
|         line-height: 36rpx; | ||||
|       } | ||||
| 
 | ||||
|       .log-msg-date { | ||||
|         font-size: 24rpx; | ||||
|         font-weight: 500; | ||||
|         color: #000000; | ||||
|         color: #999999; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -1,293 +1,249 @@ | |||
| <!-- 订单列表 --> | ||||
| <template> | ||||
|   <s-layout title="我的订单"> | ||||
|     <su-sticky bgColor="#fff"> | ||||
|       <su-tabs | ||||
|         :list="tabMaps" | ||||
|         :scrollable="false" | ||||
|         @change="onTabsChange" | ||||
|         :current="state.currentTab" | ||||
|       /> | ||||
|     </su-sticky> | ||||
|     <s-empty v-if="state.pagination.total === 0" icon="/static/order-empty.png" text="暂无订单" /> | ||||
|     <view v-if="state.pagination.total > 0"> | ||||
|       <view | ||||
|         class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20" | ||||
|         v-for="order in state.pagination.list" | ||||
|         :key="order.id" | ||||
|         @tap="onOrderDetail(order.id)" | ||||
|       > | ||||
|         <view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20"> | ||||
|           <view class="order-no">订单号:{{ order.no }}</view> | ||||
|           <view class="order-state ss-font-26" :class="formatOrderColor(order)"> | ||||
| 	<s-layout title="我的订单"> | ||||
| 		<su-sticky bgColor="#fff"> | ||||
| 			<su-tabs :list="tabMaps" :scrollable="false" @change="onTabsChange" :current="state.currentTab" /> | ||||
| 		</su-sticky> | ||||
| 		<s-empty v-if="state.pagination.total === 0" icon="/static/order-empty.png" text="暂无订单" /> | ||||
| 		<view v-if="state.pagination.total > 0"> | ||||
| 			<view class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20" v-for="order in state.pagination.list" | ||||
|             :key="order.id" @tap="onOrderDetail(order.id)"> | ||||
| 				<view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20"> | ||||
| 					<view class="order-no">订单号:{{ order.no }}</view> | ||||
| 					<view class="order-state ss-font-26" :class="formatOrderColor(order)"> | ||||
|             {{ formatOrderStatus(order) }} | ||||
|           </view> | ||||
|         </view> | ||||
|         <view class="border-bottom" v-for="item in order.items" :key="item.id"> | ||||
|           <s-goods-item | ||||
| 				</view> | ||||
| 				<view class="border-bottom" v-for="item in order.items" :key="item.id"> | ||||
| 					<s-goods-item | ||||
|             :img="item.picUrl" | ||||
|             :title="item.spuName" | ||||
|             :skuText="item.properties.map((property) => property.valueName).join(' ')" | ||||
|             :price="item.price" | ||||
| 						:skuText="item.properties.map((property) => property.valueName).join(' ')" | ||||
| 						:price="item.price" | ||||
|             :num="item.count" | ||||
|           /> | ||||
|         </view> | ||||
|         <view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20"> | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <view class="discounts-title pay-color" | ||||
|               >共 {{ order.productCount }} 件商品,总金额:</view | ||||
|             > | ||||
|             <view class="discounts-money pay-color"> ¥{{ fen2yuan(order.payPrice) }} </view> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view | ||||
|           class="order-card-footer ss-flex ss-col-center ss-p-x-20" | ||||
|           :class="order.buttons.length > 3 ? 'ss-row-between' : 'ss-row-right'" | ||||
|         > | ||||
|           <view class="ss-flex ss-col-center"> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('combination')" | ||||
|               class="tool-btn ss-reset-button" | ||||
|               @tap.stop="onOrderGroupon(order)" | ||||
|             > | ||||
| 				</view> | ||||
| 				<view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20"> | ||||
| 					<view class="ss-flex ss-col-center"> | ||||
| 						<view class="discounts-title pay-color">共 {{ order.productCount }} 件商品,总金额:</view> | ||||
| 						<view class="discounts-money pay-color"> | ||||
| 							¥{{ fen2yuan(order.payPrice) }} | ||||
|             </view> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 				<view class="order-card-footer ss-flex ss-col-center ss-p-x-20" | ||||
|               :class="order.buttons.length > 3 ? 'ss-row-between' : 'ss-row-right'"> | ||||
| 					<view class="ss-flex ss-col-center"> | ||||
| 						<button v-if="order.buttons.includes('combination')" class="tool-btn ss-reset-button" | ||||
| 							@tap.stop="onOrderGroupon(order)"> | ||||
|               拼团详情 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.length === 0" | ||||
|               class="tool-btn ss-reset-button" | ||||
|               @tap.stop="onOrderDetail(order.id)" | ||||
|             > | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.length === 0" class="tool-btn ss-reset-button" | ||||
|                     @tap.stop="onOrderDetail(order.id)"> | ||||
|               查看详情 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('confirm')" | ||||
|               class="tool-btn ss-reset-button" | ||||
|               @tap.stop="onConfirm(order)" | ||||
|             > | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.includes('confirm')" class="tool-btn ss-reset-button" | ||||
|                     @tap.stop="onConfirm(order)"> | ||||
|               确认收货 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('express')" | ||||
|               class="tool-btn ss-reset-button" | ||||
|               @tap.stop="onExpress(order.id)" | ||||
|             > | ||||
|               查看物流 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('cancel')" | ||||
|               class="tool-btn ss-reset-button" | ||||
|               @tap.stop="onCancel(order.id)" | ||||
|             > | ||||
|               取消订单 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('comment')" | ||||
|               class="tool-btn ss-reset-button" | ||||
|               @tap.stop="onComment(order.id)" | ||||
|             > | ||||
|               评价 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('delete')" | ||||
|               class="delete-btn ss-reset-button" | ||||
|               @tap.stop="onDelete(order.id)" | ||||
|             > | ||||
|               删除订单 | ||||
|             </button> | ||||
|             <button | ||||
|               v-if="order.buttons.includes('pay')" | ||||
|               class="tool-btn ss-reset-button ui-BG-Main-Gradient" | ||||
|               @tap.stop="onPay(order.payOrderId)" | ||||
|             > | ||||
|               继续支付 | ||||
|             </button> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.includes('express')" class="tool-btn ss-reset-button" | ||||
|                     @tap.stop="onExpress(order.id)"> | ||||
| 							查看物流 | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.includes('cancel')" class="tool-btn ss-reset-button" | ||||
|                     @tap.stop="onCancel(order.id)"> | ||||
| 							取消订单 | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.includes('comment')" class="tool-btn ss-reset-button" | ||||
|                     @tap.stop="onComment(order.id)"> | ||||
| 							评价 | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.includes('delete')" class="delete-btn ss-reset-button" | ||||
|                     @tap.stop="onDelete(order.id)"> | ||||
| 							删除订单 | ||||
| 						</button> | ||||
| 						<button v-if="order.buttons.includes('pay')" class="tool-btn ss-reset-button ui-BG-Main-Gradient" | ||||
|                     @tap.stop="onPay(order.payOrderId)"> | ||||
| 							继续支付 | ||||
| 						</button> | ||||
| 					</view> | ||||
| 				</view> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 
 | ||||
|     <!-- 加载更多 --> | ||||
|     <uni-load-more | ||||
|       v-if="state.pagination.total > 0" | ||||
|       :status="state.loadStatus" | ||||
|       :content-text="{ | ||||
| 		<!-- 加载更多 --> | ||||
| 		<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{ | ||||
|         contentdown: '上拉加载更多', | ||||
|       }" | ||||
|       @tap="loadMore" | ||||
|     /> | ||||
|   </s-layout> | ||||
|       }" @tap="loadMore" /> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'; | ||||
| 	import { reactive } from 'vue'; | ||||
| 	import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'; | ||||
|   import { | ||||
|     fen2yuan, | ||||
|     formatOrderColor, | ||||
|     formatOrderStatus, | ||||
|     handleOrderButtons, | ||||
|     formatOrderColor, formatOrderStatus, handleOrderButtons, | ||||
|   } from '@/sheep/hooks/useGoods'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import _ from 'lodash'; | ||||
| 	import { | ||||
| 		isEmpty | ||||
| 	} from 'lodash'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
| 
 | ||||
|   // 数据 | ||||
|   const state = reactive({ | ||||
|     currentTab: 0, // 选中的 tabMaps 下标 | ||||
|     pagination: { | ||||
| 	const paginationNull = { | ||||
| 		list: [], | ||||
|     total: 0, | ||||
| 		pageNo: 1, | ||||
|     pageSize: 5, | ||||
| 	}; | ||||
| 
 | ||||
| 	// 数据 | ||||
| 	const state = reactive({ | ||||
| 		currentTab: 0, // 选中的 tabMaps 下标 | ||||
| 		pagination: { | ||||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 5, | ||||
|     }, | ||||
|     loadStatus: '', | ||||
|   }); | ||||
| 		}, | ||||
| 		loadStatus: '' | ||||
| 	}); | ||||
| 
 | ||||
|   const tabMaps = [ | ||||
|     { | ||||
|       name: '全部', | ||||
|     }, | ||||
|     { | ||||
|       name: '待付款', | ||||
|       value: 0, | ||||
|     }, | ||||
|     { | ||||
|       name: '待发货', | ||||
|       value: 10, | ||||
|     }, | ||||
|     { | ||||
|       name: '待收货', | ||||
|       value: 20, | ||||
|     }, | ||||
|     { | ||||
|       name: '待评价', | ||||
|       value: 30, | ||||
|     }, | ||||
|   ]; | ||||
| 	const tabMaps = [{ | ||||
| 			name: '全部' | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: '待付款', | ||||
| 			value: 0, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: '待发货', | ||||
| 			value: 10, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: '待收货', | ||||
| 			value: 20, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: '待评价', | ||||
| 			value: 30, | ||||
| 		}, | ||||
| 	]; | ||||
| 
 | ||||
|   // 切换选项卡 | ||||
|   function onTabsChange(e) { | ||||
|     if (state.currentTab === e.index) { | ||||
| 	// 切换选项卡 | ||||
| 	function onTabsChange(e) { | ||||
| 		if (state.currentTab === e.index) { | ||||
|       return; | ||||
|     } | ||||
|     // 重头加载代码 | ||||
|     resetPagination(state.pagination); | ||||
|     state.currentTab = e.index; | ||||
|     getOrderList(); | ||||
|   } | ||||
| 		state.pagination = paginationNull; | ||||
| 		state.currentTab = e.index; | ||||
| 		getOrderList(); | ||||
| 	} | ||||
| 
 | ||||
|   // 订单详情 | ||||
|   function onOrderDetail(id) { | ||||
|     sheep.$router.go('/pages/order/detail', { | ||||
| 	// 订单详情 | ||||
| 	function onOrderDetail(id) { | ||||
| 		sheep.$router.go('/pages/order/detail', { | ||||
| 			id, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	// 分享拼团 TODO 芋艿:待测试 | ||||
| 	function onOrderGroupon(order) { | ||||
| 		sheep.$router.go('/pages/activity/groupon/detail', { | ||||
| 			id: order.ext.groupon_id, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	// 继续支付 | ||||
| 	function onPay(payOrderId) { | ||||
| 		sheep.$router.go('/pages/pay/index', { | ||||
| 			id: payOrderId, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	// 评价 | ||||
| 	function onComment(id) { | ||||
| 		sheep.$router.go('/pages/goods/comment/add', { | ||||
| 			id, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	// 确认收货 TODO 芋艿:待测试 | ||||
| 	async function onConfirm(order, ignore = false) { | ||||
| 		// 需开启确认收货组件 | ||||
| 		// todo: 芋艿:需要后续接入微信收货组件 | ||||
| 		// 1.怎么检测是否开启了发货组件功能?如果没有开启的话就不能在这里return出去 | ||||
| 		// 2.如果开启了走mpConfirm方法,需要在App.vue的show方法中拿到确认收货结果 | ||||
| 		let isOpenBusinessView = true; | ||||
| 		if ( | ||||
| 			sheep.$platform.name === 'WechatMiniProgram' && | ||||
| 			!isEmpty(order.wechat_extra_data) && | ||||
| 			isOpenBusinessView && | ||||
| 			!ignore | ||||
| 		) { | ||||
| 			mpConfirm(order); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		// 正常的确认收货流程 | ||||
| 		const { code } = await OrderApi.receiveOrder(order.id); | ||||
| 		if (code === 0) { | ||||
| 			state.pagination = paginationNull; | ||||
| 			await getOrderList(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// #ifdef MP-WEIXIN | ||||
| 	// 小程序确认收货组件 TODO 芋艿:后续再接入 | ||||
| 	function mpConfirm(order) { | ||||
| 		if (!wx.openBusinessView) { | ||||
| 			sheep.$helper.toast(`请升级微信版本`); | ||||
| 			return; | ||||
| 		} | ||||
| 		wx.openBusinessView({ | ||||
| 			businessType: 'weappOrderConfirm', | ||||
| 			extraData: { | ||||
| 				merchant_id: '1481069012', | ||||
| 				merchant_trade_no: order.wechat_extra_data.merchant_trade_no, | ||||
| 				transaction_id: order.wechat_extra_data.transaction_id, | ||||
| 			}, | ||||
| 			success(response) { | ||||
| 				console.log('success:', response); | ||||
| 				if (response.errMsg === 'openBusinessView:ok') { | ||||
| 					if (response.extraData.status === 'success') { | ||||
| 						onConfirm(order, true); | ||||
| 					} | ||||
| 				} | ||||
| 			}, | ||||
| 			fail(error) { | ||||
| 				console.log('error:', error); | ||||
| 			}, | ||||
| 			complete(result) { | ||||
| 				console.log('result:', result); | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 	// #endif | ||||
| 
 | ||||
| 	// 查看物流 | ||||
| 	async function onExpress(id) { | ||||
| 		sheep.$router.go('/pages/order/express/log', { | ||||
|       id, | ||||
|     }); | ||||
|   } | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|   // 跳转拼团记录的详情 | ||||
|   function onOrderGroupon(order) { | ||||
|     sheep.$router.go('/pages/activity/groupon/detail', { | ||||
|       id: order.combinationRecordId, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 继续支付 | ||||
|   function onPay(payOrderId) { | ||||
|     sheep.$router.go('/pages/pay/index', { | ||||
|       id: payOrderId, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 评价 | ||||
|   function onComment(id) { | ||||
|     sheep.$router.go('/pages/goods/comment/add', { | ||||
|       id, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 确认收货 | ||||
|   async function onConfirm(order, ignore = false) { | ||||
|     // 需开启确认收货组件 | ||||
|     // todo: 芋艿:【微信物流】需要后续接入微信收货组件 https://gitee.com/sheepjs/shopro-uniapp/commit/a6bbba49b84dd418b84c5fefc8b7463df8f4901f | ||||
|     // 1.怎么检测是否开启了发货组件功能?如果没有开启的话就不能在这里return出去 | ||||
|     // 2.如果开启了走mpConfirm方法,需要在App.vue的show方法中拿到确认收货结果 | ||||
|     let isOpenBusinessView = true; | ||||
|     if ( | ||||
|       sheep.$platform.name === 'WechatMiniProgram' && | ||||
|       !isEmpty(order.wechat_extra_data) && | ||||
|       isOpenBusinessView && | ||||
|       !ignore | ||||
|     ) { | ||||
|       mpConfirm(order); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确认收货吗?', | ||||
|       success: async function (res) { | ||||
|         if (!res.confirm) { | ||||
| 	// 取消订单 | ||||
| 	async function onCancel(orderId) { | ||||
| 		uni.showModal({ | ||||
| 			title: '提示', | ||||
| 			content: '确定要取消订单吗?', | ||||
| 			success: async function(res) { | ||||
| 				if (!res.confirm) { | ||||
|           return; | ||||
|         } | ||||
|         // 正常的确认收货流程 | ||||
|         const { code } = await OrderApi.receiveOrder(order.id); | ||||
|         if (code === 0) { | ||||
|           resetPagination(state.pagination); | ||||
|           await getOrderList(); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // #ifdef MP-WEIXIN | ||||
|   // 小程序确认收货组件 TODO 芋艿:【微信物流】后续再接入 | ||||
|   function mpConfirm(order) { | ||||
|     if (!wx.openBusinessView) { | ||||
|       sheep.$helper.toast(`请升级微信版本`); | ||||
|       return; | ||||
|     } | ||||
|     wx.openBusinessView({ | ||||
|       businessType: 'weappOrderConfirm', | ||||
|       extraData: { | ||||
|         merchant_id: '1481069012', | ||||
|         merchant_trade_no: order.wechat_extra_data.merchant_trade_no, | ||||
|         transaction_id: order.wechat_extra_data.transaction_id, | ||||
|       }, | ||||
|       success(response) { | ||||
|         console.log('success:', response); | ||||
|         if (response.errMsg === 'openBusinessView:ok') { | ||||
|           if (response.extraData.status === 'success') { | ||||
|             onConfirm(order, true); | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       fail(error) { | ||||
|         console.log('error:', error); | ||||
|       }, | ||||
|       complete(result) { | ||||
|         console.log('result:', result); | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
|   // #endif | ||||
| 
 | ||||
|   // 查看物流 | ||||
|   async function onExpress(id) { | ||||
|     sheep.$router.go('/pages/order/express/log', { | ||||
|       id, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 取消订单 | ||||
|   async function onCancel(orderId) { | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确定要取消订单吗?', | ||||
|       success: async function (res) { | ||||
|         if (!res.confirm) { | ||||
|           return; | ||||
|         } | ||||
| 				} | ||||
|         const { code } = await OrderApi.cancelOrder(orderId); | ||||
|         if (code === 0) { | ||||
|           // 修改数据的状态 | ||||
|  | @ -296,209 +252,208 @@ | |||
|           orderInfo.status = 40; | ||||
|           handleOrderButtons(orderInfo); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|   // 删除订单 | ||||
|   function onDelete(orderId) { | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确定要删除订单吗?', | ||||
|       success: async function (res) { | ||||
|         if (res.confirm) { | ||||
|           const { code } = await OrderApi.deleteOrder(orderId); | ||||
|           if (code === 0) { | ||||
| 	// 删除订单 | ||||
| 	function onDelete(orderId) { | ||||
| 		uni.showModal({ | ||||
| 			title: '提示', | ||||
| 			content: '确定要删除订单吗?', | ||||
| 			success: async function(res) { | ||||
| 				if (res.confirm) { | ||||
| 					const { code } = await OrderApi.deleteOrder(orderId); | ||||
| 					if (code === 0) { | ||||
|             // 删除数据 | ||||
|             let index = state.pagination.list.findIndex((order) => order.id === orderId); | ||||
|             state.pagination.list.splice(index, 1); | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 						let index = state.pagination.list.findIndex((order) => order.id === orderId); | ||||
| 						state.pagination.list.splice(index, 1); | ||||
| 					} | ||||
| 				} | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|   // 获取订单列表 | ||||
|   async function getOrderList() { | ||||
|     state.loadStatus = 'loading'; | ||||
|     let { code, data } = await OrderApi.getOrderPage({ | ||||
| 	// 获取订单列表 | ||||
| 	async function getOrderList() { | ||||
| 		state.loadStatus = 'loading'; | ||||
| 		let { code, data } = await OrderApi.getOrderPage({ | ||||
|       pageNo: state.pagination.pageNo, | ||||
|       pageSize: state.pagination.pageSize, | ||||
|       status: tabMaps[state.currentTab].value, | ||||
|       commentStatus: tabMaps[state.currentTab].value === 30 ? false : null, | ||||
|     }); | ||||
| 			commentStatus: tabMaps[state.currentTab].value === 30 ? false : null | ||||
| 		}); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     data.list.forEach((order) => handleOrderButtons(order)); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     data.list.forEach(order => handleOrderButtons(order)); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list) | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|   } | ||||
| 
 | ||||
|   onLoad(async (options) => { | ||||
|     if (options.type) { | ||||
|       state.currentTab = options.type; | ||||
|     } | ||||
|     await getOrderList(); | ||||
|   }); | ||||
| 	onLoad(async (options) => { | ||||
| 		if (options.type) { | ||||
| 			state.currentTab = options.type; | ||||
| 		} | ||||
| 		await getOrderList(); | ||||
| 	}); | ||||
| 
 | ||||
|   // 加载更多 | ||||
|   function loadMore() { | ||||
|     if (state.loadStatus === 'noMore') { | ||||
|       return; | ||||
|     } | ||||
| 	// 加载更多 | ||||
| 	function loadMore() { | ||||
| 		if (state.loadStatus === 'noMore') { | ||||
|       return | ||||
| 		} | ||||
|     state.pagination.pageNo++; | ||||
|     getOrderList(); | ||||
|   } | ||||
| 
 | ||||
|   // 上拉加载更多 | ||||
|   onReachBottom(() => { | ||||
|     loadMore(); | ||||
|   }); | ||||
| 	// 上拉加载更多 | ||||
| 	onReachBottom(() => { | ||||
| 		loadMore(); | ||||
| 	}); | ||||
| 
 | ||||
|   // 下拉刷新 | ||||
|   onPullDownRefresh(() => { | ||||
|     resetPagination(state.pagination); | ||||
|     getOrderList(); | ||||
|     setTimeout(function () { | ||||
|       uni.stopPullDownRefresh(); | ||||
|     }, 800); | ||||
|   }); | ||||
| 	// 下拉刷新 | ||||
| 	onPullDownRefresh(() => { | ||||
| 		state.pagination = paginationNull; | ||||
| 		getOrderList(); | ||||
| 		setTimeout(function() { | ||||
| 			uni.stopPullDownRefresh(); | ||||
| 		}, 800); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .score-img { | ||||
|     width: 36rpx; | ||||
|     height: 36rpx; | ||||
|     margin: 0 4rpx; | ||||
|   } | ||||
| 	.score-img { | ||||
| 		width: 36rpx; | ||||
| 		height: 36rpx; | ||||
| 		margin: 0 4rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .tool-btn { | ||||
|     width: 160rpx; | ||||
|     height: 60rpx; | ||||
|     background: #f6f6f6; | ||||
|     font-size: 26rpx; | ||||
|     border-radius: 30rpx; | ||||
|     margin-right: 10rpx; | ||||
| 	.tool-btn { | ||||
| 		width: 160rpx; | ||||
| 		height: 60rpx; | ||||
| 		background: #f6f6f6; | ||||
| 		font-size: 26rpx; | ||||
| 		border-radius: 30rpx; | ||||
| 		margin-right: 10rpx; | ||||
| 
 | ||||
|     &:last-of-type { | ||||
|       margin-right: 0; | ||||
|     } | ||||
|   } | ||||
| 		&:last-of-type { | ||||
| 			margin-right: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .delete-btn { | ||||
|     width: 160rpx; | ||||
|     height: 56rpx; | ||||
|     color: #ff3000; | ||||
|     background: #fee; | ||||
|     border-radius: 28rpx; | ||||
|     font-size: 26rpx; | ||||
|     margin-right: 10rpx; | ||||
|     line-height: normal; | ||||
| 	.delete-btn { | ||||
| 		width: 160rpx; | ||||
| 		height: 56rpx; | ||||
| 		color: #ff3000; | ||||
| 		background: #fee; | ||||
| 		border-radius: 28rpx; | ||||
| 		font-size: 26rpx; | ||||
| 		margin-right: 10rpx; | ||||
| 		line-height: normal; | ||||
| 
 | ||||
|     &:last-of-type { | ||||
|       margin-right: 0; | ||||
|     } | ||||
|   } | ||||
| 		&:last-of-type { | ||||
| 			margin-right: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .apply-btn { | ||||
|     width: 140rpx; | ||||
|     height: 50rpx; | ||||
|     border-radius: 25rpx; | ||||
|     font-size: 24rpx; | ||||
|     border: 2rpx solid #dcdcdc; | ||||
|     line-height: normal; | ||||
|     margin-left: 16rpx; | ||||
|   } | ||||
| 	.apply-btn { | ||||
| 		width: 140rpx; | ||||
| 		height: 50rpx; | ||||
| 		border-radius: 25rpx; | ||||
| 		font-size: 24rpx; | ||||
| 		border: 2rpx solid #dcdcdc; | ||||
| 		line-height: normal; | ||||
| 		margin-left: 16rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .swiper-box { | ||||
|     flex: 1; | ||||
| 	.swiper-box { | ||||
| 		flex: 1; | ||||
| 
 | ||||
|     .swiper-item { | ||||
|       height: 100%; | ||||
|       width: 100%; | ||||
|     } | ||||
|   } | ||||
| 		.swiper-item { | ||||
| 			height: 100%; | ||||
| 			width: 100%; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .order-list-card-box { | ||||
|     .order-card-header { | ||||
|       height: 80rpx; | ||||
| 	.order-list-card-box { | ||||
| 		.order-card-header { | ||||
| 			height: 80rpx; | ||||
| 
 | ||||
|       .order-no { | ||||
|         font-size: 26rpx; | ||||
|         font-weight: 500; | ||||
|       } | ||||
| 			.order-no { | ||||
| 				font-size: 26rpx; | ||||
| 				font-weight: 500; | ||||
| 			} | ||||
| 
 | ||||
|       .order-state { | ||||
|       } | ||||
|     } | ||||
| 			.order-state {} | ||||
| 		} | ||||
| 
 | ||||
|     .pay-box { | ||||
|       .discounts-title { | ||||
|         font-size: 24rpx; | ||||
|         line-height: normal; | ||||
|         color: #999999; | ||||
|       } | ||||
| 		.pay-box { | ||||
| 			.discounts-title { | ||||
| 				font-size: 24rpx; | ||||
| 				line-height: normal; | ||||
| 				color: #999999; | ||||
| 			} | ||||
| 
 | ||||
|       .discounts-money { | ||||
|         font-size: 24rpx; | ||||
|         line-height: normal; | ||||
|         color: #999; | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
| 			.discounts-money { | ||||
| 				font-size: 24rpx; | ||||
| 				line-height: normal; | ||||
| 				color: #999; | ||||
| 				font-family: OPPOSANS; | ||||
| 			} | ||||
| 
 | ||||
|       .pay-color { | ||||
|         color: #333; | ||||
|       } | ||||
|     } | ||||
| 			.pay-color { | ||||
| 				color: #333; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|     .order-card-footer { | ||||
|       height: 100rpx; | ||||
| 		.order-card-footer { | ||||
| 			height: 100rpx; | ||||
| 
 | ||||
|       .more-item-box { | ||||
|         padding: 20rpx; | ||||
| 			.more-item-box { | ||||
| 				padding: 20rpx; | ||||
| 
 | ||||
|         .more-item { | ||||
|           height: 60rpx; | ||||
| 				.more-item { | ||||
| 					height: 60rpx; | ||||
| 
 | ||||
|           .title { | ||||
|             font-size: 26rpx; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 					.title { | ||||
| 						font-size: 26rpx; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|       .more-btn { | ||||
|         color: $dark-9; | ||||
|         font-size: 24rpx; | ||||
|       } | ||||
| 			.more-btn { | ||||
| 				color: $dark-9; | ||||
| 				font-size: 24rpx; | ||||
| 			} | ||||
| 
 | ||||
|       .content { | ||||
|         width: 154rpx; | ||||
|         color: #333333; | ||||
|         font-size: 26rpx; | ||||
|         font-weight: 500; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 			.content { | ||||
| 				width: 154rpx; | ||||
| 				color: #333333; | ||||
| 				font-size: 26rpx; | ||||
| 				font-weight: 500; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   :deep(.uni-tooltip-popup) { | ||||
|     background: var(--ui-BG); | ||||
|   } | ||||
| 	:deep(.uni-tooltip-popup) { | ||||
| 		background: var(--ui-BG); | ||||
| 	} | ||||
| 
 | ||||
|   .warning-color { | ||||
|     color: #faad14; | ||||
|   } | ||||
| 	.warning-color { | ||||
| 		color: #faad14; | ||||
| 	} | ||||
| 
 | ||||
|   .danger-color { | ||||
|     color: #ff3000; | ||||
|   } | ||||
| 	.danger-color { | ||||
| 		color: #ff3000; | ||||
| 	} | ||||
| 
 | ||||
|   .success-color { | ||||
|     color: #52c41a; | ||||
|   } | ||||
| 	.success-color { | ||||
| 		color: #52c41a; | ||||
| 	} | ||||
| 
 | ||||
|   .info-color { | ||||
|     color: #999999; | ||||
|   } | ||||
| </style> | ||||
| 	.info-color { | ||||
| 		color: #999999; | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,260 +0,0 @@ | |||
| <template> | ||||
|   <view class="order-details"> | ||||
|     <!--  自提商品核销  --> | ||||
|     <view v-if="orderInfo.deliveryType === 2 && orderInfo.payStatus" class="writeOff borRadius14"> | ||||
|       <view class="title">核销信息</view> | ||||
|       <view class="grayBg flex-center"> | ||||
|         <view class="pictrue"> | ||||
|           <image | ||||
|             v-if="!!painterImageUrl" | ||||
|             :src="painterImageUrl" | ||||
|             :style="{ width: `${state.qrcodeSize}px`, height: `${state.qrcodeSize}px` }" | ||||
|             :show-menu-by-longpress="true" | ||||
|           /> | ||||
|         </view> | ||||
|       </view> | ||||
|       <view class="gear"> | ||||
|         <image :src="sheep.$url.static('/static/img/shop/writeOff.png')"></image> | ||||
|       </view> | ||||
|       <view class="num">{{ orderInfo.pickUpVerifyCode }}</view> | ||||
|       <view class="rules"> | ||||
|         <view class="item"> | ||||
|           <view class="rulesTitle flex flex-wrap align-center"> 核销时间 </view> | ||||
|           <view class="info"> | ||||
|             每日: | ||||
|             <text class="time">{{ systemStore.openingTime }} - {{ systemStore.closingTime }}</text> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view class="item"> | ||||
|           <view class="rulesTitle flex flex-wrap align-center"> | ||||
|             <text class="iconfont icon-shuoming1"></text> | ||||
|             使用说明 | ||||
|           </view> | ||||
|           <view class="info">可将二维码出示给店员扫描或提供数字核销码</view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|     <view | ||||
|       v-if="orderInfo.deliveryType === 2" | ||||
|       class="map flex flex-wrap align-center ss-row-between borRadius14" | ||||
|     > | ||||
|       <view>自提地址信息</view> | ||||
|       <view class="place cart-color flex flex-wrap flex-center" @tap="showMaoLocation"> | ||||
|         查看位置 | ||||
|       </view> | ||||
|     </view> | ||||
|     <!--  海报画板:默认隐藏只用来生成海报。生成方式为主动调用  --> | ||||
|     <l-painter | ||||
|       v-if="showPainter" | ||||
|       isCanvasToTempFilePath | ||||
|       pathType="url" | ||||
|       @success="setPainterImageUrl" | ||||
|       hidden | ||||
|       ref="painterRef" | ||||
|     /> | ||||
|   </view> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import sheep from '@/sheep'; | ||||
|   import { reactive, ref } from 'vue'; | ||||
| 
 | ||||
|   const props = defineProps({ | ||||
|     orderInfo: { | ||||
|       type: Object, | ||||
|       default() {}, | ||||
|     }, | ||||
|     systemStore: { | ||||
|       type: Object, | ||||
|       default() {}, | ||||
|     }, | ||||
|   }); | ||||
|   const state = reactive({ | ||||
|     qrcodeSize: 145, | ||||
|   }); | ||||
| 
 | ||||
|   /** | ||||
|    * 打开地图 | ||||
|    */ | ||||
|   const showMaoLocation = () => { | ||||
|     if (!props.systemStore.latitude || !props.systemStore.longitude) { | ||||
|       sheep.$helper.toast('缺少经纬度信息无法查看地图!'); | ||||
|       return; | ||||
|     } | ||||
|     uni.openLocation({ | ||||
|       latitude: props.systemStore.latitude, | ||||
|       longitude: props.systemStore.longitude, | ||||
|       scale: 8, | ||||
|       name: props.systemStore.name, | ||||
|       address: props.systemStore.areaName + props.systemStore.detailAddress, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 拨打电话 | ||||
|    */ | ||||
|   const makePhone = () => { | ||||
|     uni.makePhoneCall({ | ||||
|       phoneNumber: props.systemStore.phone, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   const painterRef = ref(); // 海报画板 | ||||
|   const painterImageUrl = ref(); // 海报 url | ||||
|   const showPainter = ref(true); | ||||
|   // 渲染海报 | ||||
|   const renderPoster = async (poster) => { | ||||
|     await painterRef.value.render(poster); | ||||
|   }; | ||||
|   // 获得生成的图片 | ||||
|   const setPainterImageUrl = (path) => { | ||||
|     painterImageUrl.value = path; | ||||
|     showPainter.value = false; | ||||
|   }; | ||||
|   /** | ||||
|    * 生成核销二维码 | ||||
|    */ | ||||
|   const markCode = (text) => { | ||||
|     renderPoster({ | ||||
|       css: { | ||||
|         width: `${state.qrcodeSize}px`, | ||||
|         height: `${state.qrcodeSize}px`, | ||||
|       }, | ||||
|       views: [ | ||||
|         { | ||||
|           type: 'qrcode', | ||||
|           text: text, | ||||
|           css: { | ||||
|             width: `${state.qrcodeSize}px`, | ||||
|             height: `${state.qrcodeSize}px`, | ||||
|           }, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   }; | ||||
|   defineExpose({ | ||||
|     markCode, | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
|   .borRadius14 { | ||||
|     border-radius: 14rpx !important; | ||||
|   } | ||||
|   .cart-color { | ||||
|     color: #e93323 !important; | ||||
|     border: 1px solid #e93323 !important; | ||||
|   } | ||||
|   .order-details { | ||||
|     border-radius: 10rpx; | ||||
|     margin: 0 20rpx 20rpx 20rpx; | ||||
|   } | ||||
|   .order-details .writeOff { | ||||
|     background-color: #fff; | ||||
|     margin-top: 15rpx; | ||||
|     padding-bottom: 50rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .title { | ||||
|     font-size: 30rpx; | ||||
|     color: #282828; | ||||
|     height: 87rpx; | ||||
|     border-bottom: 1px solid #f0f0f0; | ||||
|     padding: 0 24rpx; | ||||
|     line-height: 87rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .grayBg { | ||||
|     background-color: #f2f5f7; | ||||
|     width: 590rpx; | ||||
|     height: 384rpx; | ||||
|     border-radius: 20rpx 20rpx 0 0; | ||||
|     margin: 50rpx auto 0 auto; | ||||
|     padding-top: 55rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .grayBg .pictrue { | ||||
|     width: 290rpx; | ||||
|     height: 290rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .grayBg .pictrue image { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .gear { | ||||
|     width: 590rpx; | ||||
|     height: 30rpx; | ||||
|     margin: 0 auto; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .gear image { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .num { | ||||
|     background-color: #f0c34c; | ||||
|     width: 590rpx; | ||||
|     height: 84rpx; | ||||
|     color: #282828; | ||||
|     font-size: 48rpx; | ||||
|     margin: 0 auto; | ||||
|     border-radius: 0 0 20rpx 20rpx; | ||||
|     text-align: center; | ||||
|     padding-top: 4rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .rules { | ||||
|     margin: 46rpx 30rpx 0 30rpx; | ||||
|     border-top: 1px solid #f0f0f0; | ||||
|     padding-top: 10rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .rules .item { | ||||
|     margin-top: 20rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .rules .item .rulesTitle { | ||||
|     font-size: 28rpx; | ||||
|     color: #282828; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .rules .item .rulesTitle .iconfont { | ||||
|     font-size: 30rpx; | ||||
|     color: #333; | ||||
|     margin-right: 8rpx; | ||||
|     margin-top: 5rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .rules .item .info { | ||||
|     font-size: 28rpx; | ||||
|     color: #999; | ||||
|     margin-top: 7rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .writeOff .rules .item .info .time { | ||||
|     margin-left: 20rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .map { | ||||
|     height: 86rpx; | ||||
|     font-size: 30rpx; | ||||
|     color: #282828; | ||||
|     line-height: 86rpx; | ||||
|     border-bottom: 1px solid #f0f0f0; | ||||
|     margin-top: 15rpx; | ||||
|     background-color: #fff; | ||||
|     padding: 0 24rpx; | ||||
|   } | ||||
| 
 | ||||
|   .order-details .map .place { | ||||
|     font-size: 26rpx; | ||||
|     width: 176rpx; | ||||
|     height: 50rpx; | ||||
|     border-radius: 25rpx; | ||||
|     line-height: 50rpx; | ||||
|     text-align: center; | ||||
|   } | ||||
| </style> | ||||
|  | @ -81,7 +81,7 @@ | |||
|   import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods'; | ||||
|   import PayOrderApi from '@/sheep/api/pay/order'; | ||||
|   import PayChannelApi from '@/sheep/api/pay/channel'; | ||||
|   import { getPayMethods, goPayResult } from '@/sheep/platform/pay'; | ||||
|   import { getPayMethods } from '@/sheep/platform/pay'; | ||||
| 
 | ||||
|   const userWallet = computed(() => sheep.$store('user').userWallet); | ||||
| 
 | ||||
|  | @ -135,22 +135,12 @@ | |||
| 
 | ||||
|   // 状态转换:payOrder.status => payStatus | ||||
|   function checkPayStatus() { | ||||
|     if (state.orderInfo.status === 10 || state.orderInfo.status === 20) { | ||||
|       // 支付成功 | ||||
|     if (state.orderInfo.status === 10 | ||||
|       || state.orderInfo.status === 20 ) { // 支付成功 | ||||
|       state.payStatus = 2; | ||||
|       // 跳转回支付成功页 | ||||
|       uni.showModal({ | ||||
|         title: '提示', | ||||
|         content: '订单已支付', | ||||
|         showCancel: false, | ||||
|         success: function () { | ||||
|           goPayResult(state.orderInfo.id, state.orderType); | ||||
|         }, | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     if (state.orderInfo.status === 30) { | ||||
|       // 支付关闭 | ||||
|     if (state.orderInfo.status === 30) { // 支付关闭 | ||||
|       state.payStatus = -1; | ||||
|       return; | ||||
|     } | ||||
|  | @ -165,39 +155,31 @@ | |||
|   // 设置支付订单信息 | ||||
|   async function setOrder(id) { | ||||
|     // 获得支付订单信息 | ||||
|     const { data, code } = await PayOrderApi.getOrder(id, true); | ||||
|     const { data, code } = await PayOrderApi.getOrder(id); | ||||
|     if (code !== 0 || !data) { | ||||
|       state.payStatus = -2; | ||||
|       return; | ||||
|     } | ||||
|     state.orderInfo = data; | ||||
|     // 设置支付状态 | ||||
|     checkPayStatus(); | ||||
|     // 获得支付方式 | ||||
|     await setPayMethods(); | ||||
|     // 设置支付状态 | ||||
|     checkPayStatus(); | ||||
|   } | ||||
| 
 | ||||
|   // 获得支付方式 | ||||
|   async function setPayMethods() { | ||||
|     const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId); | ||||
|     const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId) | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     state.payMethods = getPayMethods(data); | ||||
|     state.payMethods.find((item) => { | ||||
|       if (item.value && !item.disabled) { | ||||
|         state.payment = item.value; | ||||
|         return true; | ||||
|       } | ||||
|     }); | ||||
|     state.payMethods = getPayMethods(data) | ||||
|   } | ||||
| 
 | ||||
|   onLoad((options) => { | ||||
|     if ( | ||||
|       sheep.$platform.name === 'WechatOfficialAccount' && | ||||
|       sheep.$platform.os === 'ios' && | ||||
|       !sheep.$platform.landingPage.includes('pages/pay/index') | ||||
|     ) { | ||||
|     if (sheep.$platform.name === 'WechatOfficialAccount' | ||||
|       && sheep.$platform.os === 'ios' | ||||
|       && !sheep.$platform.landingPage.includes('pages/pay/index')) { | ||||
|       location.reload(); | ||||
|       return; | ||||
|     } | ||||
|  | @ -226,6 +208,7 @@ | |||
|       position: relative; | ||||
|       padding: 60rpx 20rpx 40rpx; | ||||
| 
 | ||||
| 
 | ||||
|       .money-text { | ||||
|         color: $red; | ||||
|         font-size: 46rpx; | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|           <view class="title">充值金额</view> | ||||
|           <view class="num" :class="item.refundStatus === 10 ? 'danger-color' : 'success-color'"> | ||||
|             {{ fen2yuan(item.payPrice) }} 元 | ||||
|             <text v-if="item.bonusPrice > 0">(赠送 {{ fen2yuan(item.bonusPrice) }} 元)</text> | ||||
|             <text v-if="item.bonusPrice > 0">(赠送 {{ fen2yuan(item.bonusPrice)}} 元)</text> | ||||
|           </view> | ||||
|         </view> | ||||
|         <view class="status-box item ss-flex ss-col-center ss-row-between"> | ||||
|  | @ -30,9 +30,7 @@ | |||
|         </view> | ||||
|         <view class="time-box item ss-flex ss-col-center ss-row-between"> | ||||
|           <text class="item-title">充值时间</text> | ||||
|           <view class="time"> | ||||
|             {{ sheep.$helper.timeFormat(item.payTime, 'yyyy-mm-dd hh:MM:ss') }}</view | ||||
|           > | ||||
|           <view class="time"> {{ sheep.$helper.timeFormat(item.payTime, 'yyyy-mm-dd hh:MM:ss') }}</view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|  | @ -55,7 +53,7 @@ | |||
| <script setup> | ||||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import PayWalletApi from '@/sheep/api/pay/wallet'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { fen2yuan } from '../../sheep/hooks/useGoods'; | ||||
|  |  | |||
|  | @ -1,277 +1,259 @@ | |||
| <!-- 充值界面 --> | ||||
| <template> | ||||
|   <s-layout title="充值" class="withdraw-wrap" navbar="inner"> | ||||
|     <view | ||||
|       class="wallet-num-box ss-flex ss-col-center ss-row-between" | ||||
|       :style="[ | ||||
|         { | ||||
|           marginTop: '-' + Number(statusBarHeight + 88) + 'rpx', | ||||
|           paddingTop: Number(statusBarHeight + 108) + 'rpx', | ||||
|         }, | ||||
|       ]" | ||||
|     > | ||||
|       <view class=""> | ||||
|         <view class="num-title">当前余额(元)</view> | ||||
|         <view class="wallet-num">{{ fen2yuan(userWallet.balance) }}</view> | ||||
|       </view> | ||||
|       <button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/pay/recharge-log')"> | ||||
| 	<s-layout title="充值" class="withdraw-wrap" navbar="inner"> | ||||
| 		<view class="wallet-num-box ss-flex ss-col-center ss-row-between" :style="[ | ||||
|       { | ||||
|         marginTop: '-' + Number(statusBarHeight + 88) + 'rpx', | ||||
|         paddingTop: Number(statusBarHeight + 108) + 'rpx', | ||||
|       }, | ||||
|     ]"> | ||||
| 			<view class=""> | ||||
| 				<view class="num-title">当前余额(元)</view> | ||||
| 				<view class="wallet-num">{{ fen2yuan(userWallet.balance) }}</view> | ||||
| 			</view> | ||||
| 			<button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/pay/recharge-log')"> | ||||
|         充值记录 | ||||
|       </button> | ||||
|     </view> | ||||
|     <view class="recharge-box"> | ||||
|       <view class="recharge-card-box"> | ||||
|         <view class="input-label ss-m-b-50">充值金额</view> | ||||
|         <view class="input-box ss-flex border-bottom ss-p-b-20"> | ||||
|           <view class="unit">¥</view> | ||||
|           <uni-easyinput | ||||
|             v-model="state.recharge_money" | ||||
|             type="digit" | ||||
|             placeholder="请输入充值金额" | ||||
|             :inputBorder="false" | ||||
|           /> | ||||
|         </view> | ||||
|         <view class="face-value-box ss-flex ss-flex-wrap ss-m-y-40"> | ||||
|           <button | ||||
|             class="ss-reset-button face-value-btn" | ||||
|             v-for="item in state.packageList" | ||||
|             :key="item.money" | ||||
|             :class="[{ 'btn-active': state.recharge_money === fen2yuan(item.payPrice) }]" | ||||
|             @tap="onCard(item.payPrice)" | ||||
|           > | ||||
|             <text class="face-value-title">{{ fen2yuan(item.payPrice) }}</text> | ||||
|             <view v-if="item.bonusPrice" class="face-value-tag"> | ||||
|               送 {{ fen2yuan(item.bonusPrice) }} 元 | ||||
| 		</view> | ||||
| 		<view class="recharge-box"> | ||||
| 			<view class="recharge-card-box"> | ||||
| 				<view class="input-label ss-m-b-50">充值金额</view> | ||||
| 				<view class="input-box ss-flex border-bottom ss-p-b-20"> | ||||
| 					<view class="unit">¥</view> | ||||
| 					<uni-easyinput v-model="state.recharge_money" type="digit" placeholder="请输入充值金额" | ||||
|                          :inputBorder="false" /> | ||||
| 				</view> | ||||
| 				<view class="face-value-box ss-flex ss-flex-wrap ss-m-y-40"> | ||||
| 					<button class="ss-reset-button face-value-btn" v-for="item in state.packageList" :key="item.money" | ||||
| 						:class="[{ 'btn-active': state.recharge_money === fen2yuan(item.payPrice) }]" | ||||
| 						@tap="onCard(item.payPrice)"> | ||||
| 						<text class="face-value-title">{{ fen2yuan(item.payPrice) }}</text> | ||||
| 						<view v-if="item.bonusPrice" class="face-value-tag"> | ||||
| 							送 {{ fen2yuan(item.bonusPrice) }} 元 | ||||
|             </view> | ||||
|           </button> | ||||
|         </view> | ||||
|         <button | ||||
|           class="ss-reset-button save-btn ui-BG-Main-Gradient ss-m-t-60 ui-Shadow-Main" | ||||
|           @tap="onConfirm" | ||||
|         > | ||||
|           确认充值 | ||||
|         </button> | ||||
|       </view> | ||||
|     </view> | ||||
|   </s-layout> | ||||
| 					</button> | ||||
| 				</view> | ||||
| 				<button class="ss-reset-button save-btn ui-BG-Main-Gradient ss-m-t-60 ui-Shadow-Main" @tap="onConfirm"> | ||||
| 					确认充值 | ||||
| 				</button> | ||||
| 			</view> | ||||
| 		</view> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { computed, reactive } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
| 	import { computed, reactive } from 'vue'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import PayWalletApi from '@/sheep/api/pay/wallet'; | ||||
|   import { WxaSubscribeTemplate } from '@/sheep/helper/const'; | ||||
| 
 | ||||
|   const userWallet = computed(() => sheep.$store('user').userWallet); | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png'); | ||||
| 	const userWallet = computed(() => sheep.$store('user').userWallet); | ||||
| 	const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
| 	const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png'); | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     recharge_money: '', // 输入的充值金额 | ||||
| 	const state = reactive({ | ||||
| 		recharge_money: '', // 输入的充值金额 | ||||
|     packageList: [], | ||||
|   }); | ||||
| 	}); | ||||
| 
 | ||||
|   // 点击卡片,选择充值金额 | ||||
|   function onCard(e) { | ||||
|     state.recharge_money = fen2yuan(e); | ||||
|   } | ||||
| 	// 点击卡片,选择充值金额 | ||||
| 	function onCard(e) { | ||||
| 		state.recharge_money = fen2yuan(e); | ||||
| 	} | ||||
| 
 | ||||
|   // 获得钱包充值套餐列表 | ||||
|   async function getRechargeTabs() { | ||||
| 	async function getRechargeTabs() { | ||||
|     const { code, data } = await PayWalletApi.getWalletRechargePackageList(); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     state.packageList = data; | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   // 发起支付 | ||||
|   async function onConfirm() { | ||||
|     const { code, data } = await PayWalletApi.createWalletRecharge({ | ||||
|       packageId: state.packageList.find((item) => fen2yuan(item.payPrice) === state.recharge_money) | ||||
|         ?.id, | ||||
|       payPrice: state.recharge_money * 100, | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
| 	async function onConfirm() { | ||||
| 		const { code, data } = await PayWalletApi.createWalletRecharge({ | ||||
| 			packageId: state.packageList.find((item) => fen2yuan(item.payPrice) === state.recharge_money)?.id, | ||||
| 			payPrice: state.recharge_money * 100 | ||||
| 		}); | ||||
| 		if (code !== 0) { | ||||
| 			return; | ||||
| 		} | ||||
|     // #ifdef MP | ||||
|     sheep.$platform | ||||
|       .useProvider('wechat') | ||||
|       .subscribeMessage(WxaSubscribeTemplate.PAY_WALLET_RECHARGER_SUCCESS); | ||||
|     sheep.$platform.useProvider('wechat').subscribeMessage('money_change'); | ||||
|     // #endif | ||||
|     sheep.$router.go('/pages/pay/index', { | ||||
|       id: data.payOrderId, | ||||
|       orderType: 'recharge', | ||||
|       orderType: 'recharge' | ||||
|     }); | ||||
|   } | ||||
| 	} | ||||
| 
 | ||||
|   onLoad(() => { | ||||
|     getRechargeTabs(); | ||||
|   }); | ||||
| 	onLoad(() => { | ||||
| 		getRechargeTabs(); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   :deep() { | ||||
|     .uni-input-input { | ||||
|       font-family: OPPOSANS !important; | ||||
|     } | ||||
|   } | ||||
| 	:deep() { | ||||
| 		.uni-input-input { | ||||
| 			font-family: OPPOSANS !important; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .wallet-num-box { | ||||
|     padding: 0 40rpx 80rpx; | ||||
|     background: var(--ui-BG-Main) v-bind(headerBg) center/750rpx 100% no-repeat; | ||||
|     border-radius: 0 0 5% 5%; | ||||
| 	.wallet-num-box { | ||||
| 		padding: 0 40rpx 80rpx; | ||||
| 		background: var(--ui-BG-Main) v-bind(headerBg) center/750rpx 100% no-repeat; | ||||
| 		border-radius: 0 0 5% 5%; | ||||
| 
 | ||||
|     .num-title { | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: $white; | ||||
|       margin-bottom: 20rpx; | ||||
|     } | ||||
| 		.num-title { | ||||
| 			font-size: 26rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: $white; | ||||
| 			margin-bottom: 20rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .wallet-num { | ||||
|       font-size: 60rpx; | ||||
|       font-weight: 500; | ||||
|       color: $white; | ||||
|       font-family: OPPOSANS; | ||||
|     } | ||||
| 		.wallet-num { | ||||
| 			font-size: 60rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: $white; | ||||
| 			font-family: OPPOSANS; | ||||
| 		} | ||||
| 
 | ||||
|     .log-btn { | ||||
|       width: 170rpx; | ||||
|       height: 60rpx; | ||||
|       line-height: 60rpx; | ||||
|       border: 1rpx solid $white; | ||||
|       border-radius: 30rpx; | ||||
|       padding: 0; | ||||
|       font-size: 26rpx; | ||||
|       font-weight: 500; | ||||
|       color: $white; | ||||
|     } | ||||
|   } | ||||
| 		.log-btn { | ||||
| 			width: 170rpx; | ||||
| 			height: 60rpx; | ||||
| 			line-height: 60rpx; | ||||
| 			border: 1rpx solid $white; | ||||
| 			border-radius: 30rpx; | ||||
| 			padding: 0; | ||||
| 			font-size: 26rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: $white; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .recharge-box { | ||||
|     position: relative; | ||||
|     padding: 0 30rpx; | ||||
|     margin-top: -60rpx; | ||||
|   } | ||||
| 	.recharge-box { | ||||
| 		position: relative; | ||||
| 		padding: 0 30rpx; | ||||
| 		margin-top: -60rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .save-btn { | ||||
|     width: 620rpx; | ||||
|     height: 86rpx; | ||||
|     border-radius: 44rpx; | ||||
|     font-size: 30rpx; | ||||
|   } | ||||
| 	.save-btn { | ||||
| 		width: 620rpx; | ||||
| 		height: 86rpx; | ||||
| 		border-radius: 44rpx; | ||||
| 		font-size: 30rpx; | ||||
| 	} | ||||
| 
 | ||||
|   .recharge-card-box { | ||||
|     width: 690rpx; | ||||
|     background: var(--ui-BG); | ||||
|     border-radius: 20rpx; | ||||
|     padding: 30rpx; | ||||
|     box-sizing: border-box; | ||||
| 	.recharge-card-box { | ||||
| 		width: 690rpx; | ||||
| 		background: var(--ui-BG); | ||||
| 		border-radius: 20rpx; | ||||
| 		padding: 30rpx; | ||||
| 		box-sizing: border-box; | ||||
| 
 | ||||
|     .input-label { | ||||
|       font-size: 30rpx; | ||||
|       font-weight: 500; | ||||
|       color: #333; | ||||
|     } | ||||
| 		.input-label { | ||||
| 			font-size: 30rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: #333; | ||||
| 		} | ||||
| 
 | ||||
|     .unit { | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|       font-size: 48rpx; | ||||
|       font-weight: 500; | ||||
|     } | ||||
| 		.unit { | ||||
| 			display: flex; | ||||
| 			align-items: center; | ||||
| 			font-size: 48rpx; | ||||
| 			font-weight: 500; | ||||
| 		} | ||||
| 
 | ||||
|     .uni-easyinput__placeholder-class { | ||||
|       font-size: 30rpx; | ||||
|       height: 60rpx; | ||||
|       display: flex; | ||||
|       align-items: center; | ||||
|     } | ||||
| 		.uni-easyinput__placeholder-class { | ||||
| 			font-size: 30rpx; | ||||
| 			height: 60rpx; | ||||
| 			display: flex; | ||||
| 			align-items: center; | ||||
| 		} | ||||
| 
 | ||||
|     :deep(.uni-easyinput__content-input) { | ||||
|       font-size: 48rpx; | ||||
|     } | ||||
| 		:deep(.uni-easyinput__content-input) { | ||||
| 			font-size: 48rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .face-value-btn { | ||||
|       width: 200rpx; | ||||
|       height: 144rpx; | ||||
|       border: 1px solid var(--ui-BG-Main); | ||||
|       border-radius: 10rpx; | ||||
|       position: relative; | ||||
|       z-index: 1; | ||||
|       margin-bottom: 15rpx; | ||||
|       margin-right: 15rpx; | ||||
| 		.face-value-btn { | ||||
| 			width: 200rpx; | ||||
| 			height: 144rpx; | ||||
| 			border: 1px solid var(--ui-BG-Main); | ||||
| 			border-radius: 10rpx; | ||||
| 			position: relative; | ||||
| 			z-index: 1; | ||||
| 			margin-bottom: 15rpx; | ||||
| 			margin-right: 15rpx; | ||||
| 
 | ||||
|       &:nth-of-type(3n) { | ||||
|         margin-right: 0; | ||||
|       } | ||||
| 			&:nth-of-type(3n) { | ||||
| 				margin-right: 0; | ||||
| 			} | ||||
| 
 | ||||
|       .face-value-title { | ||||
|         font-size: 36rpx; | ||||
|         font-weight: 500; | ||||
|         color: var(--ui-BG-Main); | ||||
|         font-family: OPPOSANS; | ||||
| 			.face-value-title { | ||||
| 				font-size: 36rpx; | ||||
| 				font-weight: 500; | ||||
| 				color: var(--ui-BG-Main); | ||||
| 				font-family: OPPOSANS; | ||||
| 
 | ||||
|         &::after { | ||||
|           content: '元'; | ||||
|           font-size: 24rpx; | ||||
|           margin-left: 6rpx; | ||||
|         } | ||||
|       } | ||||
| 				&::after { | ||||
| 					content: '元'; | ||||
| 					font-size: 24rpx; | ||||
| 					margin-left: 6rpx; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|       .face-value-tag { | ||||
|         position: absolute; | ||||
|         z-index: 2; | ||||
|         height: 40rpx; | ||||
|         line-height: 40rpx; | ||||
|         background: var(--ui-BG-Main); | ||||
|         opacity: 0.8; | ||||
|         border-radius: 10rpx 0 20rpx 0; | ||||
|         top: 0; | ||||
|         left: -2rpx; | ||||
|         padding: 0 16rpx; | ||||
|         font-size: 22rpx; | ||||
|         color: $white; | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
| 			.face-value-tag { | ||||
| 				position: absolute; | ||||
| 				z-index: 2; | ||||
| 				height: 40rpx; | ||||
| 				line-height: 40rpx; | ||||
| 				background: var(--ui-BG-Main); | ||||
| 				opacity: 0.8; | ||||
| 				border-radius: 10rpx 0 20rpx 0; | ||||
| 				top: 0; | ||||
| 				left: -2rpx; | ||||
| 				padding: 0 16rpx; | ||||
| 				font-size: 22rpx; | ||||
| 				color: $white; | ||||
| 				font-family: OPPOSANS; | ||||
| 			} | ||||
| 
 | ||||
|       &::before { | ||||
|         position: absolute; | ||||
|         content: ' '; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         background: var(--ui-BG-Main); | ||||
|         opacity: 0.1; | ||||
|         z-index: 0; | ||||
|         left: 0; | ||||
|         top: 0; | ||||
|       } | ||||
|     } | ||||
| 			&::before { | ||||
| 				position: absolute; | ||||
| 				content: ' '; | ||||
| 				width: 100%; | ||||
| 				height: 100%; | ||||
| 				background: var(--ui-BG-Main); | ||||
| 				opacity: 0.1; | ||||
| 				z-index: 0; | ||||
| 				left: 0; | ||||
| 				top: 0; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|     .btn-active { | ||||
|       z-index: 1; | ||||
| 		.btn-active { | ||||
| 			z-index: 1; | ||||
| 
 | ||||
|       &::before { | ||||
|         content: ''; | ||||
|         background: var(--ui-BG-Main); | ||||
|         opacity: 1; | ||||
|       } | ||||
| 			&::before { | ||||
| 				content: ''; | ||||
| 				background: var(--ui-BG-Main); | ||||
| 				opacity: 1; | ||||
| 			} | ||||
| 
 | ||||
|       .face-value-title { | ||||
|         color: $white; | ||||
|         position: relative; | ||||
|         z-index: 1; | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
| 			.face-value-title { | ||||
| 				color: $white; | ||||
| 				position: relative; | ||||
| 				z-index: 1; | ||||
| 				font-family: OPPOSANS; | ||||
| 			} | ||||
| 
 | ||||
|       .face-value-tag { | ||||
|         background: $white; | ||||
|         color: var(--ui-BG-Main); | ||||
|         font-family: OPPOSANS; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 			.face-value-tag { | ||||
| 				background: $white; | ||||
| 				color: var(--ui-BG-Main); | ||||
| 				font-family: OPPOSANS; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 支付结果页面 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FFF' }" title="支付结果"> | ||||
|   <s-layout title="支付结果" :bgStyle="{ color: '#FFF' }"> | ||||
|     <view class="pay-result-box ss-flex-col ss-row-center ss-col-center"> | ||||
|       <!-- 信息展示 --> | ||||
|       <view class="pay-waiting ss-m-b-30" v-if="payResult === 'waiting'" /> | ||||
|  | @ -39,6 +39,7 @@ | |||
|         <button class="check-btn ss-reset-button" v-if="payResult === 'success'" @tap="onOrder"> | ||||
|           查看订单 | ||||
|         </button> | ||||
|         <!-- TODO 芋艿:拼团接入 --> | ||||
|         <button | ||||
|           class="check-btn ss-reset-button" | ||||
|           v-if="payResult === 'success' && state.tradeOrder.type === 3" | ||||
|  | @ -48,11 +49,9 @@ | |||
|         </button> | ||||
|       </view> | ||||
| 
 | ||||
|       <!-- TODO 芋艿:订阅 --> | ||||
|       <!-- #ifdef MP --> | ||||
|       <view | ||||
|         class="subscribe-box ss-flex ss-m-t-44" | ||||
|         v-if="showSubscribeBtn && state.orderType === 'goods'" | ||||
|       > | ||||
|       <view class="subscribe-box ss-flex ss-m-t-44"> | ||||
|         <image class="subscribe-img" :src="sheep.$url.static('/static/img/shop/order/cargo.png')" /> | ||||
|         <view class="subscribe-title ss-m-r-48 ss-m-l-16">获取实时发货信息与订单状态</view> | ||||
|         <view class="subscribe-start" @tap="subscribeMessage">立即订阅</view> | ||||
|  | @ -63,14 +62,13 @@ | |||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { onHide, onLoad, onShow } from '@dcloudio/uni-app'; | ||||
|   import { computed, reactive, ref } from 'vue'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
|   import { onLoad, onHide, onShow } from '@dcloudio/uni-app'; | ||||
|   import { reactive, computed } from 'vue'; | ||||
|   import { isEmpty } from 'lodash'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import PayOrderApi from '@/sheep/api/pay/order'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import { fen2yuan } from '../../sheep/hooks/useGoods'; | ||||
|   import OrderApi from '@/sheep/api/trade/order'; | ||||
|   import { WxaSubscribeTemplate } from '@/sheep/helper/const'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     id: 0, // 支付单号 | ||||
|  | @ -97,24 +95,6 @@ | |||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   function showRepayModal() { | ||||
|     if (state.result !== 'failed') return; | ||||
|     uni.showModal({ | ||||
|       title: '确认支付', | ||||
|       content: '未检测到您的支付结果,请确认您是否已经支付完成?', | ||||
|       cancelText: '取消', | ||||
|       confirmText: '我已支付', | ||||
|       success: function (res) { | ||||
|         if (res.confirm) { | ||||
|           state.counter = 0; | ||||
|           setTimeout(() => { | ||||
|             getOrderInfo(state.id); | ||||
|           }, 100); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 获得订单信息 | ||||
|   async function getOrderInfo(id) { | ||||
|     state.counter++; | ||||
|  | @ -131,23 +111,11 @@ | |||
|         // 非待支付,可能是已支付,可能是已退款 | ||||
|         state.result = 'paid'; | ||||
|         // #ifdef MP | ||||
|         uni.showModal({ | ||||
|           title: '支付结果', | ||||
|           showCancel: false, // 不要取消按钮 | ||||
|           content: '支付成功', | ||||
|           success: () => { | ||||
|             // 订阅只能由用户主动触发,只能包一层 showModal 诱导用户点击 | ||||
|             autoSubscribeMessage(); | ||||
|           }, | ||||
|         }); | ||||
| 
 | ||||
|         subscribeMessage(); | ||||
|         // #endif | ||||
|         // 特殊:获得商品订单信息 | ||||
|         if (state.orderType === 'goods') { | ||||
|           const { data, code } = await OrderApi.getOrderDetail( | ||||
|             state.orderInfo.merchantOrderId, | ||||
|             true, | ||||
|           ); | ||||
|           const { data, code } = await OrderApi.getOrder(state.orderInfo.merchantOrderId); | ||||
|           if (code === 0) { | ||||
|             state.tradeOrder = data; | ||||
|           } | ||||
|  | @ -155,20 +123,20 @@ | |||
|         return; | ||||
|       } | ||||
|     } | ||||
|     // 2.1 情况三一:未支付,且轮询次数小于五次,则继续轮询 | ||||
|     if (state.counter < 5 && state.result === 'unpaid') { | ||||
|     // 2.1 情况三一:未支付,且轮询次数小于三次,则继续轮询 | ||||
|     if (state.counter < 3 && state.result === 'unpaid') { | ||||
|       setTimeout(() => { | ||||
|         getOrderInfo(id); | ||||
|       }, 2000); | ||||
|       }, 1500); | ||||
|     } | ||||
|     // 2.2 情况二:超过五次检测才判断为支付失败 | ||||
|     if (state.counter >= 5) { | ||||
|     // 2.2 情况二:超过三次检测才判断为支付失败 | ||||
|     if (state.counter >= 3) { | ||||
|       state.result = 'failed'; | ||||
|       showRepayModal(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function onOrder() { | ||||
|     // TODO 芋艿:待测试 | ||||
|     if (state.orderType === 'recharge') { | ||||
|       sheep.$router.redirect('/pages/pay/recharge-log'); | ||||
|     } else { | ||||
|  | @ -176,35 +144,15 @@ | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // TODO 芋艿:待测试 | ||||
|   // #ifdef MP | ||||
|   const showSubscribeBtn = ref(false); // 默认隐藏 | ||||
|   const SUBSCRIBE_BTN_STATUS_STORAGE_KEY = 'subscribe_btn_status'; | ||||
|   function subscribeMessage() { | ||||
|     if (state.orderType !== 'goods') { | ||||
|       return; | ||||
|     } | ||||
|     const event = [WxaSubscribeTemplate.TRADE_ORDER_DELIVERY]; | ||||
|     let event = ['order_dispatched']; | ||||
|     if (state.tradeOrder.type === 3) { | ||||
|       event.push(WxaSubscribeTemplate.PROMOTION_COMBINATION_SUCCESS); | ||||
|       event.push('groupon_finish'); | ||||
|       event.push('groupon_fail'); | ||||
|     } | ||||
|     sheep.$platform.useProvider('wechat').subscribeMessage(event, () => { | ||||
|       // 订阅后记录一下订阅状态 | ||||
|       uni.removeStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY); | ||||
|       uni.setStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY, '已订阅'); | ||||
|       // 隐藏订阅按钮 | ||||
|       showSubscribeBtn.value = false; | ||||
|     }); | ||||
|   } | ||||
|   async function autoSubscribeMessage() { | ||||
|     // 1. 校验是否手动订阅过 | ||||
|     const subscribeBtnStatus = uni.getStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY); | ||||
|     if (!subscribeBtnStatus) { | ||||
|       showSubscribeBtn.value = true; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 2. 订阅消息 | ||||
|     subscribeMessage(); | ||||
|     sheep.$platform.useProvider('wechat').subscribeMessage(event); | ||||
|   } | ||||
|   // #endif | ||||
| 
 | ||||
|  | @ -222,7 +170,7 @@ | |||
|     if (options.payState === 'fail') { | ||||
|       state.result = 'failed'; | ||||
|     } else { | ||||
|       // 轮询五次检测订单支付结果 | ||||
|       // 轮询三次检测订单支付结果 | ||||
|       await getOrderInfo(state.id); | ||||
|     } | ||||
|   }); | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ | |||
|     <s-empty | ||||
|       v-else-if="errCode === 'TemplateError'" | ||||
|       icon="/static/internet-empty.png" | ||||
|       text="未找到模板,请前往后台启用对应模板" | ||||
|       text="未找到模板" | ||||
|       showAction | ||||
|       actionText="重新加载" | ||||
|       @clickAction="onReconnect" | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <!-- FAQ 常见问题 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FFF' }" class="set-wrap" title="常见问题"> | ||||
|   <s-layout class="set-wrap" title="常见问题" :bgStyle="{ color: '#FFF' }"> | ||||
|     <uni-collapse> | ||||
|       <uni-collapse-item v-for="(item, index) in state.list" :key="item"> | ||||
|         <template v-slot:title> | ||||
|  | @ -49,7 +49,7 @@ | |||
|     } | ||||
|   } | ||||
|   onLoad(() => { | ||||
|     // TODO 芋艿:【文章】目前简单做,使用营销文章,作为 faq | ||||
|     // TODO 芋艿:目前简单做,使用营销文章,作为 faq | ||||
|     if (true) { | ||||
|       sheep.$router.go('/pages/public/richtext', { | ||||
|         title: '常见问题', | ||||
|  |  | |||
|  | @ -1,7 +1,9 @@ | |||
| <!-- 文章展示 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FFF' }" :title="state.title" class="set-wrap"> | ||||
|     <view class="ss-p-30 richtext"><mp-html :content="state.content"></mp-html></view> | ||||
|   <s-layout class="set-wrap" :title="state.title" :bgStyle="{ color: '#FFF' }"> | ||||
|     <view class="ss-p-30"> | ||||
|       <mp-html class="richtext" :content="state.content" /> | ||||
|     </view> | ||||
|   </s-layout> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -39,6 +41,7 @@ | |||
|     } | ||||
|     getRichTextContent(options.id, options.title); | ||||
|   }); | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|  | @ -46,9 +49,6 @@ | |||
|     margin: 0 30rpx; | ||||
|   } | ||||
| 
 | ||||
|   :deep() { | ||||
|     image { | ||||
|       display: block; | ||||
|     } | ||||
|   .richtext { | ||||
|   } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#fff' }" class="set-wrap" title="系统设置"> | ||||
|   <s-layout class="set-wrap" title="系统设置" :bgStyle="{ color: '#fff' }"> | ||||
|     <view class="header-box ss-flex-col ss-row-center ss-col-center"> | ||||
|       <image | ||||
|         class="logo-img ss-m-b-46" | ||||
|  |  | |||
|  | @ -1,118 +1,78 @@ | |||
| <!-- 收货地址的新增/编辑 --> | ||||
| <template> | ||||
|   <s-layout :title="state.model.id ? '编辑地址' : '新增地址'"> | ||||
|     <uni-forms | ||||
|       ref="addressFormRef" | ||||
|       v-model="state.model" | ||||
|       :rules="rules" | ||||
|       validateTrigger="bind" | ||||
|       labelWidth="160" | ||||
|       labelAlign="left" | ||||
|       border | ||||
|       :labelStyle="{ fontWeight: 'bold' }" | ||||
|     > | ||||
|       <view class="bg-white form-box ss-p-x-30"> | ||||
|         <uni-forms-item name="name" label="收货人" class="form-item"> | ||||
|           <uni-easyinput | ||||
|             v-model="state.model.name" | ||||
|             placeholder="请填写收货人姓名" | ||||
|             :inputBorder="false" | ||||
|             placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" | ||||
|           /> | ||||
|         </uni-forms-item> | ||||
| 	<s-layout :title="state.model.id ? '编辑地址' : '新增地址'"> | ||||
| 		<uni-forms ref="addressFormRef" v-model="state.model" :rules="rules" validateTrigger="bind" | ||||
| 			labelWidth="160" labelAlign="left" border :labelStyle="{ fontWeight: 'bold' }"> | ||||
| 			<view class="bg-white form-box ss-p-x-30"> | ||||
| 				<uni-forms-item name="name" label="收货人" class="form-item"> | ||||
| 					<uni-easyinput v-model="state.model.name" placeholder="请填写收货人姓名" :inputBorder="false" | ||||
| 						placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" /> | ||||
| 				</uni-forms-item> | ||||
| 
 | ||||
|         <uni-forms-item name="mobile" label="手机号" class="form-item"> | ||||
|           <uni-easyinput | ||||
|             v-model="state.model.mobile" | ||||
|             type="number" | ||||
|             placeholder="请输入手机号" | ||||
|             :inputBorder="false" | ||||
|             placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" | ||||
|           > | ||||
|           </uni-easyinput> | ||||
|         </uni-forms-item> | ||||
|         <uni-forms-item | ||||
|           name="areaName" | ||||
|           label="省市区" | ||||
|           @tap="state.showRegion = true" | ||||
|           class="form-item" | ||||
|         > | ||||
|           <uni-easyinput | ||||
|             v-model="state.model.areaName" | ||||
|             disabled | ||||
|             :inputBorder="false" | ||||
|             :styles="{ disableColor: '#fff', color: '#333' }" | ||||
|             placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" | ||||
|             placeholder="请选择省市区" | ||||
|           > | ||||
|             <template v-slot:right> | ||||
|               <uni-icons type="right" /> | ||||
|             </template> | ||||
|           </uni-easyinput> | ||||
|         </uni-forms-item> | ||||
|         <uni-forms-item | ||||
|           name="detailAddress" | ||||
|           label="详细地址" | ||||
|           :formItemStyle="{ alignItems: 'flex-start' }" | ||||
|           :labelStyle="{ lineHeight: '5em' }" | ||||
|           class="textarea-item" | ||||
|         > | ||||
|           <uni-easyinput | ||||
|             :inputBorder="false" | ||||
|             type="textarea" | ||||
|             v-model="state.model.detailAddress" | ||||
|             placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" | ||||
|             placeholder="请输入详细地址" | ||||
|             clearable | ||||
|           /> | ||||
|         </uni-forms-item> | ||||
|       </view> | ||||
|       <view class="ss-m-y-20 bg-white ss-p-x-30 ss-flex ss-row-between ss-col-center default-box"> | ||||
|         <view class="default-box-title"> 设为默认地址 </view> | ||||
|         <su-switch style="transform: scale(0.8)" v-model="state.model.defaultStatus" /> | ||||
|       </view> | ||||
|     </uni-forms> | ||||
|     <su-fixed bottom :opacity="false" bg="" placeholder :noFixed="false" :index="10"> | ||||
|       <view class="footer-box ss-flex-col ss-row-between ss-p-20"> | ||||
|         <view class="ss-m-b-20"> | ||||
| 				<uni-forms-item name="mobile" label="手机号" class="form-item"> | ||||
| 					<uni-easyinput v-model="state.model.mobile" type="number" placeholder="请输入手机号" :inputBorder="false" | ||||
| 						placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"> | ||||
| 					</uni-easyinput> | ||||
| 				</uni-forms-item> | ||||
| 				<uni-forms-item name="areaName" label="省市区" @tap="state.showRegion = true" class="form-item"> | ||||
| 					<uni-easyinput v-model="state.model.areaName" disabled :inputBorder="false" | ||||
| 						:styles="{ disableColor: '#fff', color: '#333' }" | ||||
| 						placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" | ||||
| 						placeholder="请选择省市区"> | ||||
| 						<template v-slot:right> | ||||
| 							<uni-icons type="right" /> | ||||
| 						</template> | ||||
| 					</uni-easyinput> | ||||
| 				</uni-forms-item> | ||||
| 				<uni-forms-item name="detailAddress" label="详细地址" :formItemStyle="{ alignItems: 'flex-start' }" | ||||
| 					:labelStyle="{ lineHeight: '5em' }" class="textarea-item"> | ||||
| 					<uni-easyinput :inputBorder="false" type="textarea" v-model="state.model.detailAddress" | ||||
| 						placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" | ||||
| 						placeholder="请输入详细地址" clearable /> | ||||
| 				</uni-forms-item> | ||||
| 			</view> | ||||
| 			<view class="ss-m-y-20 bg-white ss-p-x-30 ss-flex ss-row-between ss-col-center default-box"> | ||||
| 				<view class="default-box-title"> 设为默认地址 </view> | ||||
| 				<su-switch style="transform: scale(0.8)" v-model="state.model.defaultStatus" /> | ||||
| 			</view> | ||||
| 		</uni-forms> | ||||
| 		<su-fixed bottom :opacity="false" bg="" placeholder :noFixed="false" :index="10"> | ||||
| 			<view class="footer-box ss-flex-col ss-row-between ss-p-20"> | ||||
| 				<view class="ss-m-b-20"> | ||||
|           <button class="ss-reset-button save-btn ui-Shadow-Main" @tap="onSave">保存</button> | ||||
|         </view> | ||||
|         <button v-if="state.model.id" class="ss-reset-button cancel-btn" @tap="onDelete"> | ||||
|           删除 | ||||
|         </button> | ||||
|       </view> | ||||
|     </su-fixed> | ||||
| 				<button v-if="state.model.id" class="ss-reset-button cancel-btn" @tap="onDelete"> | ||||
| 					删除 | ||||
| 				</button> | ||||
| 			</view> | ||||
| 		</su-fixed> | ||||
| 
 | ||||
|     <!-- 省市区弹窗 --> | ||||
|     <su-region-picker | ||||
|       :show="state.showRegion" | ||||
|       @cancel="state.showRegion = false" | ||||
|       @confirm="onRegionConfirm" | ||||
|     /> | ||||
|   </s-layout> | ||||
| 		<!-- 省市区弹窗 --> | ||||
| 		<su-region-picker :show="state.showRegion" @cancel="state.showRegion = false" @confirm="onRegionConfirm" /> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { ref, reactive, unref } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import { mobile } from '@/sheep/validate/form'; | ||||
| 	import { ref, reactive, unref } from 'vue'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { onLoad } from '@dcloudio/uni-app'; | ||||
| 	import _ from 'lodash'; | ||||
| 	import { mobile } from '@/sheep/validate/form'; | ||||
|   import AreaApi from '@/sheep/api/system/area'; | ||||
|   import AddressApi from '@/sheep/api/member/address'; | ||||
| 
 | ||||
|   const addressFormRef = ref(null); | ||||
|   const state = reactive({ | ||||
|     showRegion: false, | ||||
|     model: { | ||||
|       name: '', | ||||
|       mobile: '', | ||||
| 	const addressFormRef = ref(null); | ||||
| 	const state = reactive({ | ||||
| 		showRegion: false, | ||||
| 		model: { | ||||
| 			name: '', | ||||
| 			mobile: '', | ||||
|       detailAddress: '', | ||||
|       defaultStatus: false, | ||||
| 			defaultStatus: false, | ||||
|       areaName: '', | ||||
|     }, | ||||
| 		}, | ||||
|     rules: {}, | ||||
|   }); | ||||
| 	}); | ||||
| 
 | ||||
|   const rules = { | ||||
|     name: { | ||||
|  | @ -125,181 +85,167 @@ | |||
|     }, | ||||
|     mobile, | ||||
|     detailAddress: { | ||||
|       rules: [ | ||||
|         { | ||||
|           required: true, | ||||
|           errorMessage: '请输入详细地址', | ||||
|         }, | ||||
|       ], | ||||
|       rules: [{ | ||||
|         required: true, | ||||
|         errorMessage: '请输入详细地址', | ||||
|       }] | ||||
|     }, | ||||
|     areaName: { | ||||
|       rules: [ | ||||
|         { | ||||
|           required: true, | ||||
|           errorMessage: '请选择您的位置', | ||||
|         }, | ||||
|       ], | ||||
|       rules: [{ | ||||
|         required: true, | ||||
|         errorMessage: '请选择您的位置' | ||||
|       }] | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   // 确认选择地区 | ||||
|   const onRegionConfirm = (e) => { | ||||
|     state.model.areaName = `${e.province_name} ${e.city_name} ${e.district_name}`; | ||||
| 	const onRegionConfirm = (e) => { | ||||
|     state.model.areaName = `${e.province_name} ${e.city_name} ${e.district_name}` | ||||
|     state.model.areaId = e.district_id; | ||||
|     state.showRegion = false; | ||||
|   }; | ||||
| 		state.showRegion = false; | ||||
| 	}; | ||||
| 
 | ||||
|   // 获得地区数据 | ||||
|   const getAreaData = () => { | ||||
|     if (_.isEmpty(uni.getStorageSync('areaData'))) { | ||||
| 	const getAreaData = () => { | ||||
| 		if (_.isEmpty(uni.getStorageSync('areaData'))) { | ||||
|       AreaApi.getAreaTree().then((res) => { | ||||
|         if (res.code === 0) { | ||||
|           uni.setStorageSync('areaData', res.data); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   }; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
|   // 保存收货地址 | ||||
|   const onSave = async () => { | ||||
| 	const onSave = async () => { | ||||
|     // 参数校验 | ||||
|     const validate = await unref(addressFormRef) | ||||
|       .validate() | ||||
|       .catch((error) => { | ||||
|         console.log('error: ', error); | ||||
|       }); | ||||
|     if (!validate) { | ||||
| 		const validate = await unref(addressFormRef) | ||||
| 			.validate() | ||||
| 			.catch((error) => { | ||||
| 				console.log('error: ', error); | ||||
| 			}); | ||||
| 		if (!validate) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // 提交请求 | ||||
|     const formData = { | ||||
|       ...state.model, | ||||
|     }; | ||||
|     const { code } = | ||||
|       state.model.id > 0 | ||||
|         ? await AddressApi.updateAddress(formData) | ||||
|         : await AddressApi.createAddress(formData); | ||||
|     if (code === 0) { | ||||
|       sheep.$router.back(); | ||||
|       ...state.model | ||||
|     } | ||||
|   }; | ||||
|     const {code } = state.model.id > 0 ? await AddressApi.updateAddress(formData) | ||||
|       : await AddressApi.createAddress(formData); | ||||
| 		if (code === 0) { | ||||
| 			sheep.$router.back(); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
|   // 删除收货地址 | ||||
|   const onDelete = () => { | ||||
|     uni.showModal({ | ||||
|       title: '提示', | ||||
|       content: '确认删除此收货地址吗?', | ||||
|       success: async function (res) { | ||||
|         if (!res.confirm) { | ||||
|           return; | ||||
|         } | ||||
| 	const onDelete = () => { | ||||
| 		uni.showModal({ | ||||
| 			title: '提示', | ||||
| 			content: '确认删除此收货地址吗?', | ||||
| 			success: async function(res) { | ||||
| 				if (!res.confirm) { | ||||
| 					return; | ||||
| 				} | ||||
|         const { code } = await AddressApi.deleteAddress(state.model.id); | ||||
|         if (code === 0) { | ||||
|           sheep.$router.back(); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 			}, | ||||
| 		}); | ||||
| 	}; | ||||
| 
 | ||||
|   onLoad(async (options) => { | ||||
| 	onLoad(async (options) => { | ||||
|     // 获得地区数据 | ||||
|     getAreaData(); | ||||
| 		getAreaData(); | ||||
|     // 情况一:基于 id 获得收件地址 | ||||
|     if (options.id) { | ||||
|       let { code, data } = await AddressApi.getAddress(options.id); | ||||
| 		if (options.id) { | ||||
| 			let { code, data} = await AddressApi.getAddress(options.id); | ||||
|       if (code !== 0) { | ||||
|         return; | ||||
|       } | ||||
|       state.model = data; | ||||
|     } | ||||
|     // 情况二:微信导入 | ||||
|     if (options.data) { | ||||
|       let data = JSON.parse(options.data); | ||||
|       const areaData = uni.getStorageSync('areaData'); | ||||
|       const findAreaByName = (areas, name) => areas.find((item) => item.name === name); | ||||
| 
 | ||||
|       let provinceObj = findAreaByName(areaData, data.province_name); | ||||
|       let cityObj = provinceObj ? findAreaByName(provinceObj.children, data.city_name) : undefined; | ||||
|       let districtObj = cityObj ? findAreaByName(cityObj.children, data.district_name) : undefined; | ||||
|       let areaId = (districtObj || cityObj || provinceObj).id; | ||||
| 
 | ||||
|       state.model = { | ||||
|         ...state.model, | ||||
|         areaId, | ||||
|         areaName: [data.province_name, data.city_name, data.district_name] | ||||
|           .filter(Boolean) | ||||
|           .join(' '), | ||||
|         defaultStatus: false, | ||||
|         detailAddress: data.address, | ||||
|         mobile: data.mobile, | ||||
|         name: data.consignee, | ||||
|       }; | ||||
|     } | ||||
|   }); | ||||
| 		} | ||||
|     // 情况二:微信导入 TODO 芋艿:待接入 | ||||
| 		if (options.data) { | ||||
| 			let data = JSON.parse(options.data); | ||||
| 			const areaData = uni.getStorageSync('areaData'); | ||||
| 			let provinceArr = areaData.filter(item => item.name == data.province_name); | ||||
| 			data.province_id = provinceArr[0].id; | ||||
| 			let provinceArr2 = provinceArr[0].children.filter(item => item.name == data.city_name); | ||||
| 			data.city_id = provinceArr2[0].id; | ||||
| 			let provinceArr3 = provinceArr2[0].children.filter(item => item.name == data.district_name); | ||||
| 			data.district_id = provinceArr3[0].id; | ||||
| 			state.model = { | ||||
| 				...state.model, | ||||
| 				...data, | ||||
| 			}; | ||||
| 		} | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   :deep() { | ||||
|     .uni-forms-item__label .label-text { | ||||
|       font-size: 28rpx !important; | ||||
|       color: #333333 !important; | ||||
|       line-height: normal !important; | ||||
|     } | ||||
| 	:deep() { | ||||
| 		.uni-forms-item__label .label-text { | ||||
| 			font-size: 28rpx !important; | ||||
| 			color: #333333 !important; | ||||
| 			line-height: normal !important; | ||||
| 		} | ||||
| 
 | ||||
|     .uni-easyinput__content-input { | ||||
|       font-size: 28rpx !important; | ||||
|       color: #333333 !important; | ||||
|       line-height: normal !important; | ||||
|       padding-left: 0 !important; | ||||
|     } | ||||
| 		.uni-easyinput__content-input { | ||||
| 			font-size: 28rpx !important; | ||||
| 			color: #333333 !important; | ||||
| 			line-height: normal !important; | ||||
| 			padding-left: 0 !important; | ||||
| 		} | ||||
| 
 | ||||
|     .uni-easyinput__content-textarea { | ||||
|       font-size: 28rpx !important; | ||||
|       color: #333333 !important; | ||||
|       line-height: normal !important; | ||||
|       margin-top: 8rpx !important; | ||||
|     } | ||||
| 		.uni-easyinput__content-textarea { | ||||
| 			font-size: 28rpx !important; | ||||
| 			color: #333333 !important; | ||||
| 			line-height: normal !important; | ||||
| 			margin-top: 8rpx !important; | ||||
| 		} | ||||
| 
 | ||||
|     .uni-icons { | ||||
|       font-size: 40rpx !important; | ||||
|     } | ||||
| 		.uni-icons { | ||||
| 			font-size: 40rpx !important; | ||||
| 		} | ||||
| 
 | ||||
|     .is-textarea-icon { | ||||
|       margin-top: 22rpx; | ||||
|     } | ||||
| 		.is-textarea-icon { | ||||
| 			margin-top: 22rpx; | ||||
| 		} | ||||
| 
 | ||||
|     .is-disabled { | ||||
|       color: #333333; | ||||
|     } | ||||
|   } | ||||
| 		.is-disabled { | ||||
| 			color: #333333; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .default-box { | ||||
|     width: 100%; | ||||
|     box-sizing: border-box; | ||||
|     height: 100rpx; | ||||
| 	.default-box { | ||||
| 		width: 100%; | ||||
| 		box-sizing: border-box; | ||||
| 		height: 100rpx; | ||||
| 
 | ||||
|     .default-box-title { | ||||
|       font-size: 28rpx; | ||||
|       color: #333333; | ||||
|       line-height: normal; | ||||
|     } | ||||
|   } | ||||
| 		.default-box-title { | ||||
| 			font-size: 28rpx; | ||||
| 			color: #333333; | ||||
| 			line-height: normal; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|   .footer-box { | ||||
|     .save-btn { | ||||
|       width: 710rpx; | ||||
|       height: 80rpx; | ||||
|       border-radius: 40rpx; | ||||
|       background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|       color: $white; | ||||
|     } | ||||
| 	.footer-box { | ||||
| 		.save-btn { | ||||
| 			width: 710rpx; | ||||
| 			height: 80rpx; | ||||
| 			border-radius: 40rpx; | ||||
| 			background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
| 			color: $white; | ||||
| 		} | ||||
| 
 | ||||
|     .cancel-btn { | ||||
|       width: 710rpx; | ||||
|       height: 80rpx; | ||||
|       border-radius: 40rpx; | ||||
|       background: var(--ui-BG); | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 		.cancel-btn { | ||||
| 			width: 710rpx; | ||||
| 			height: 80rpx; | ||||
| 			border-radius: 40rpx; | ||||
| 			background: var(--ui-BG); | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -1,165 +1,143 @@ | |||
| <!-- 收件地址列表 --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FFF' }" title="收货地址"> | ||||
|     <view v-if="state.list.length"> | ||||
|       <s-address-item | ||||
|         hasBorderBottom | ||||
|         v-for="item in state.list" | ||||
|         :key="item.id" | ||||
|         :item="item" | ||||
|         @tap="onSelect(item)" | ||||
|       /> | ||||
|     </view> | ||||
| 	<s-layout title="收货地址" :bgStyle="{ color: '#FFF' }"> | ||||
| 		<view v-if="state.list.length"> | ||||
| 			<s-address-item hasBorderBottom v-for="item in state.list" :key="item.id" :item="item" | ||||
|                       @tap="onSelect(item)" /> | ||||
| 		</view> | ||||
| 
 | ||||
|     <su-fixed bottom placeholder> | ||||
|       <view class="footer-box ss-flex ss-row-between ss-p-20"> | ||||
|         <!-- 微信小程序和微信H5 --> | ||||
|         <button | ||||
|           v-if="['WechatMiniProgram', 'WechatOfficialAccount'].includes(sheep.$platform.name)" | ||||
|           @tap="importWechatAddress" | ||||
|           class="border ss-reset-button sync-wxaddress ss-m-20 ss-flex ss-row-center ss-col-center" | ||||
|         > | ||||
|           <text class="cicon-weixin ss-p-r-10" style="color: #09bb07; font-size: 40rpx"></text> | ||||
|           导入微信地址 | ||||
|         </button> | ||||
|         <button | ||||
|           class="add-btn ss-reset-button ui-Shadow-Main" | ||||
|           @tap="sheep.$router.go('/pages/user/address/edit')" | ||||
|         > | ||||
|           新增收货地址 | ||||
|         </button> | ||||
|       </view> | ||||
|     </su-fixed> | ||||
|     <s-empty | ||||
|       v-if="state.list.length === 0 && !state.loading" | ||||
|       text="暂无收货地址" | ||||
|       icon="/static/data-empty.png" | ||||
|     /> | ||||
|   </s-layout> | ||||
| 		<su-fixed bottom placeholder> | ||||
| 			<view class="footer-box ss-flex ss-row-between ss-p-20"> | ||||
| 				<!-- 微信小程序和微信H5 --> | ||||
| 				<button v-if="['WechatMiniProgram', 'WechatOfficialAccount'].includes(sheep.$platform.name)" | ||||
| 					@tap="importWechatAddress" | ||||
| 					class="border ss-reset-button sync-wxaddress ss-m-20 ss-flex ss-row-center ss-col-center"> | ||||
| 					<text class="cicon-weixin ss-p-r-10" style="color: #09bb07; font-size: 40rpx"></text> | ||||
| 					导入微信地址 | ||||
| 				</button> | ||||
| 				<button class="add-btn ss-reset-button ui-Shadow-Main" | ||||
| 					@tap="sheep.$router.go('/pages/user/address/edit')"> | ||||
| 					新增收货地址 | ||||
| 				</button> | ||||
| 			</view> | ||||
| 		</su-fixed> | ||||
| 		<s-empty v-if="state.list.length === 0 && !state.loading" text="暂无收货地址" icon="/static/data-empty.png" /> | ||||
| 	</s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import { onBeforeMount, reactive } from 'vue'; | ||||
|   import { onLoad, onShow } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { isEmpty } from 'lodash-es'; | ||||
| 	import { reactive, onBeforeMount } from 'vue'; | ||||
| 	import { onShow } from '@dcloudio/uni-app'; | ||||
| 	import sheep from '@/sheep'; | ||||
| 	import { isEmpty } from 'lodash'; | ||||
|   import AreaApi from '@/sheep/api/system/area'; | ||||
|   import AddressApi from '@/sheep/api/member/address'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     list: [], // 地址列表 | ||||
|     loading: true, | ||||
|     openType: '', // 页面打开类型 | ||||
|   }); | ||||
| 	const state = reactive({ | ||||
| 		list: [], // 地址列表 | ||||
| 		loading: true, | ||||
| 	}); | ||||
| 
 | ||||
|   // 选择收货地址 | ||||
|   const onSelect = (addressInfo) => { | ||||
|     if (state.openType !== 'select'){ // 不作为选择组件时阻断操作 | ||||
|       return | ||||
|     } | ||||
|     uni.$emit('SELECT_ADDRESS', { | ||||
|       addressInfo, | ||||
|     }); | ||||
|     sheep.$router.back(); | ||||
|   }; | ||||
| 	// 选择收货地址 | ||||
| 	const onSelect = (addressInfo) => { | ||||
| 		uni.$emit('SELECT_ADDRESS', { | ||||
| 			addressInfo, | ||||
| 		}); | ||||
| 		sheep.$router.back(); | ||||
| 	}; | ||||
| 
 | ||||
|   // 导入微信地址 | ||||
|   function importWechatAddress() { | ||||
|     let wechatAddress = {}; | ||||
|     // #ifdef MP | ||||
|     uni.chooseAddress({ | ||||
|       success: (res) => { | ||||
|         wechatAddress = { | ||||
|           consignee: res.userName, | ||||
|           mobile: res.telNumber, | ||||
|           province_name: res.provinceName, | ||||
|           city_name: res.cityName, | ||||
|           district_name: res.countyName, | ||||
|           address: res.detailInfo, | ||||
|           region: '', | ||||
|           is_default: false, | ||||
|         }; | ||||
|         if (!isEmpty(wechatAddress)) { | ||||
|           sheep.$router.go('/pages/user/address/edit', { | ||||
|             data: JSON.stringify(wechatAddress), | ||||
|           }); | ||||
|         } | ||||
|       }, | ||||
|       fail: (err) => { | ||||
|         console.log('%cuni.chooseAddress,调用失败', 'color:green;background:yellow'); | ||||
|       }, | ||||
|     }); | ||||
|     // #endif | ||||
|     // #ifdef H5 | ||||
|     sheep.$platform.useProvider('wechat').jssdk.openAddress({ | ||||
|       success: (res) => { | ||||
|         wechatAddress = { | ||||
|           consignee: res.userName, | ||||
|           mobile: res.telNumber, | ||||
|           province_name: res.provinceName, | ||||
|           city_name: res.cityName, | ||||
|           district_name: res.countryName, | ||||
|           address: res.detailInfo, | ||||
|           region: '', | ||||
|           is_default: false, | ||||
|         }; | ||||
|         if (!isEmpty(wechatAddress)) { | ||||
|           sheep.$router.go('/pages/user/address/edit', { | ||||
|             data: JSON.stringify(wechatAddress), | ||||
|           }); | ||||
|         } | ||||
|       }, | ||||
|     }); | ||||
|     // #endif | ||||
|   } | ||||
| 	// 导入微信地址 | ||||
|   // TODO 芋艿:未测试 | ||||
| 	function importWechatAddress() { | ||||
| 		let wechatAddress = {}; | ||||
| 		// #ifdef MP | ||||
| 		uni.chooseAddress({ | ||||
| 			success: (res) => { | ||||
| 				wechatAddress = { | ||||
| 					consignee: res.userName, | ||||
| 					mobile: res.telNumber, | ||||
| 					province_name: res.provinceName, | ||||
| 					city_name: res.cityName, | ||||
| 					district_name: res.countyName, | ||||
| 					address: res.detailInfo, | ||||
| 					region: '', | ||||
| 					is_default: false, | ||||
| 				}; | ||||
| 				if (!isEmpty(wechatAddress)) { | ||||
| 					sheep.$router.go('/pages/user/address/edit', { | ||||
| 						data: JSON.stringify(wechatAddress), | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 			fail: (err) => { | ||||
| 				console.log('%cuni.chooseAddress,调用失败', 'color:green;background:yellow'); | ||||
| 			}, | ||||
| 		}); | ||||
| 		// #endif | ||||
| 		// #ifdef H5 | ||||
| 		sheep.$platform.useProvider('wechat').jssdk.openAddress({ | ||||
| 			success: (res) => { | ||||
| 				wechatAddress = { | ||||
| 					consignee: res.userName, | ||||
| 					mobile: res.telNumber, | ||||
| 					province_name: res.provinceName, | ||||
| 					city_name: res.cityName, | ||||
| 					district_name: res.countryName, | ||||
| 					address: res.detailInfo, | ||||
| 					region: '', | ||||
| 					is_default: false, | ||||
| 				}; | ||||
| 				if (!isEmpty(wechatAddress)) { | ||||
| 					sheep.$router.go('/pages/user/address/edit', { | ||||
| 						data: JSON.stringify(wechatAddress), | ||||
| 					}); | ||||
| 				} | ||||
| 			}, | ||||
| 		}); | ||||
| 		// #endif | ||||
| 	} | ||||
| 
 | ||||
|   onLoad((option) => { | ||||
|     if (option.type) { | ||||
|       state.openType = option.type; | ||||
|     } | ||||
|   }); | ||||
| 	onShow(async () => { | ||||
| 		state.list = (await AddressApi.getAddressList()).data; | ||||
| 		state.loading = false; | ||||
| 	}); | ||||
| 
 | ||||
|   onShow(async () => { | ||||
|     state.list = (await AddressApi.getAddressList()).data; | ||||
|     state.loading = false; | ||||
|   }); | ||||
| 
 | ||||
|   onBeforeMount(() => { | ||||
|     if (!!uni.getStorageSync('areaData')) { | ||||
|       return; | ||||
|     } | ||||
|     // 提前加载省市区数据 | ||||
| 	onBeforeMount(() => { | ||||
| 		if (!!uni.getStorageSync('areaData')) { | ||||
| 			return; | ||||
| 		} | ||||
| 		// 提前加载省市区数据 | ||||
|     AreaApi.getAreaTree().then((res) => { | ||||
|       if (res.code === 0) { | ||||
|         uni.setStorageSync('areaData', res.data); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| 			if (res.code === 0) { | ||||
| 				uni.setStorageSync('areaData', res.data); | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
|   .footer-box { | ||||
|     .add-btn { | ||||
|       flex: 1; | ||||
|       background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
|       border-radius: 80rpx; | ||||
|       font-size: 30rpx; | ||||
|       font-weight: 500; | ||||
|       line-height: 80rpx; | ||||
|       color: $white; | ||||
|       position: relative; | ||||
|       z-index: 1; | ||||
|     } | ||||
| 	.footer-box { | ||||
| 		.add-btn { | ||||
| 			flex: 1; | ||||
| 			background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)); | ||||
| 			border-radius: 80rpx; | ||||
| 			font-size: 30rpx; | ||||
| 			font-weight: 500; | ||||
| 			line-height: 80rpx; | ||||
| 			color: $white; | ||||
| 			position: relative; | ||||
| 			z-index: 1; | ||||
| 		} | ||||
| 
 | ||||
|     .sync-wxaddress { | ||||
|       flex: 1; | ||||
|       line-height: 80rpx; | ||||
|       background: $white; | ||||
|       border-radius: 80rpx; | ||||
|       font-size: 30rpx; | ||||
|       font-weight: 500; | ||||
|       color: $dark-6; | ||||
|       margin-right: 18rpx; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| 		.sync-wxaddress { | ||||
| 			flex: 1; | ||||
| 			line-height: 80rpx; | ||||
| 			background: $white; | ||||
| 			border-radius: 80rpx; | ||||
| 			font-size: 30rpx; | ||||
| 			font-weight: 500; | ||||
| 			color: $dark-6; | ||||
| 			margin-right: 18rpx; | ||||
| 		} | ||||
| 	} | ||||
| </style> | ||||
|  | @ -5,8 +5,7 @@ | |||
|       <!-- 头部 --> | ||||
|       <view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30"> | ||||
|         <view class="header-left ss-flex ss-col-center ss-font-26"> | ||||
|           共 | ||||
|           <text class="goods-number ui-TC-Main ss-flex">{{ state.pagination.total }}</text> 件商品 | ||||
|           共 <text class="goods-number ui-TC-Main ss-flex">{{ state.pagination.total }}</text> 件商品 | ||||
|         </view> | ||||
|         <view class="header-right"> | ||||
|           <button | ||||
|  | @ -78,8 +77,7 @@ | |||
|           <view class="footer-right"> | ||||
|             <button | ||||
|               class="ss-reset-button ui-BG-Main-Gradient pay-btn ss-font-28 ui-Shadow-Main" | ||||
|               @tap="onCancel" | ||||
|             > | ||||
|               @tap="onCancel"> | ||||
|               取消收藏 | ||||
|             </button> | ||||
|           </view> | ||||
|  | @ -102,9 +100,9 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import FavoriteApi from '@/sheep/api/product/favorite'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
| 
 | ||||
|   const sys_navBar = sheep.$platform.navbar; | ||||
| 
 | ||||
|  | @ -131,7 +129,7 @@ | |||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list); | ||||
|     state.pagination.list = _.concat(state.pagination.list, data.list) | ||||
|     state.pagination.total = data.total; | ||||
|     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore'; | ||||
|   } | ||||
|  | @ -176,7 +174,7 @@ | |||
|   // 加载更多 | ||||
|   function loadMore() { | ||||
|     if (state.loadStatus === 'noMore') { | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     state.pagination.pageNo++; | ||||
|     getData(); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <!-- 商品浏览记录  --> | ||||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#f2f2f2' }" title="我的足迹"> | ||||
|   <s-layout title="我的足迹" :bgStyle="{ color: '#f2f2f2' }"> | ||||
|     <view class="cart-box ss-flex ss-flex-col ss-row-between"> | ||||
|       <!-- 头部 --> | ||||
|       <view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30"> | ||||
|  | @ -81,13 +81,11 @@ | |||
|           </view> | ||||
|           <view class="footer-right ss-flex"> | ||||
|             <button | ||||
|               :class="[ | ||||
|                 'ss-reset-button  pay-btn ss-font-28 ', | ||||
|                 { | ||||
|                   'ui-BG-Main-Gradient': state.selectedSpuIdList.length > 0, | ||||
|                   'ui-Shadow-Main': state.selectedSpuIdList.length > 0, | ||||
|                 }, | ||||
|               ]" | ||||
|               :class="['ss-reset-button  pay-btn ss-font-28 ', | ||||
|                   { | ||||
|                     'ui-BG-Main-Gradient': state.selectedSpuIdList.length > 0, | ||||
|                     'ui-Shadow-Main': state.selectedSpuIdList.length > 0 | ||||
|                   }]" | ||||
|               @tap="onDelete" | ||||
|             > | ||||
|               删除足迹 | ||||
|  | @ -122,9 +120,9 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import { reactive } from 'vue'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import SpuHistoryApi from '@/sheep/api/product/history'; | ||||
|   import { cloneDeep } from '@/sheep/helper/utils'; | ||||
|   import _ from 'lodash'; | ||||
|   import SpuHistoryApi from "@/sheep/api/product/history"; | ||||
|   import {cloneDeep} from "@/sheep/helper/utils"; | ||||
| 
 | ||||
|   const sys_navBar = sheep.$platform.navbar; | ||||
|   const pagination = { | ||||
|  |  | |||
|  | @ -1,282 +0,0 @@ | |||
| <template> | ||||
|   <s-layout :bgStyle="{ color: '#FFF' }" title="选择自提门店"> | ||||
|     <view class="storeBox" ref="container"> | ||||
|       <view | ||||
|         class="storeBox-box" | ||||
|         v-for="(item, index) in state.storeList" | ||||
|         :key="index" | ||||
|         @tap="checked(item)" | ||||
|       > | ||||
|         <view class="store-img"> | ||||
|           <image :src="item.logo" class="img" /> | ||||
|         </view> | ||||
|         <view class="store-cent-left"> | ||||
|           <view class="store-name">{{ item.name }}</view> | ||||
|           <view class="store-address line1"> | ||||
|             {{ item.areaName }}{{ ', ' + item.detailAddress }} | ||||
|           </view> | ||||
|         </view> | ||||
|         <view class="row-right ss-flex-col ss-col-center"> | ||||
|           <view> | ||||
|             <!-- #ifdef H5 --> | ||||
|             <a class="store-phone" :href="'tel:' + item.phone"> | ||||
|               <view class="iconfont"> | ||||
|                 <view class="ss-rest-button"> | ||||
|                   <text class="_icon-forward" /> | ||||
|                 </view> | ||||
|               </view> | ||||
|             </a> | ||||
|             <!-- #endif --> | ||||
|             <!-- #ifdef MP --> | ||||
|             <view class="store-phone" @click="call(item.phone)"> | ||||
|               <view class="iconfont"> | ||||
|                 <view class="ss-rest-button"> | ||||
|                   <text class="_icon-forward" /> | ||||
|                 </view> | ||||
|               </view> | ||||
|             </view> | ||||
|             <!-- #endif --> | ||||
|           </view> | ||||
|           <view class="store-distance ss-flex ss-row-center" @tap.stop="showMaoLocation(item)"> | ||||
|             <text class="addressTxt" v-if="item.distance"> | ||||
|               距离{{ item.distance.toFixed(2) }}千米 | ||||
|             </text> | ||||
|             <text class="addressTxt" v-else>查看地图</text> | ||||
|             <view class="iconfont"> | ||||
|               <view class="ss-rest-button"> | ||||
|                 <text class="_icon-forward" /> | ||||
|               </view> | ||||
|             </view> | ||||
|           </view> | ||||
|         </view> | ||||
|       </view> | ||||
|     </view> | ||||
|   </s-layout> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
|   import DeliveryApi from '@/sheep/api/trade/delivery'; | ||||
|   import { onMounted, reactive } from 'vue'; | ||||
|   import { onLoad } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
| 
 | ||||
|   const LONGITUDE = 'user_longitude'; | ||||
|   const LATITUDE = 'user_latitude'; | ||||
|   const state = reactive({ | ||||
|     loaded: false, | ||||
|     loading: false, | ||||
|     storeList: [], | ||||
|     system_store: {}, | ||||
|     locationShow: false, | ||||
|     user_latitude: 0, | ||||
|     user_longitude: 0, | ||||
|   }); | ||||
| 
 | ||||
|   const call = (phone) => { | ||||
|     uni.makePhoneCall({ | ||||
|       phoneNumber: phone, | ||||
|     }); | ||||
|   }; | ||||
|   const selfLocation = () => { | ||||
|     // #ifdef H5 | ||||
|     const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk; | ||||
|     if (jsWxSdk.isWechat()) { | ||||
|       jsWxSdk.getLocation((res) => { | ||||
|         state.user_latitude = res.latitude; | ||||
|         state.user_longitude = res.longitude; | ||||
|         uni.setStorageSync(LATITUDE, res.latitude); | ||||
|         uni.setStorageSync(LONGITUDE, res.longitude); | ||||
|         getList(); | ||||
|       }); | ||||
|     } else { | ||||
|       // #endif | ||||
|       uni.getLocation({ | ||||
|         type: 'gcj02', | ||||
|         success: (res) => { | ||||
|           try { | ||||
|             state.user_latitude = res.latitude; | ||||
|             state.user_longitude = res.longitude; | ||||
|             uni.setStorageSync(LATITUDE, res.latitude); | ||||
|             uni.setStorageSync(LONGITUDE, res.longitude); | ||||
|           } catch (e) { | ||||
|             console.error(e); | ||||
|           } | ||||
|           getList(); | ||||
|         }, | ||||
|         complete: () => { | ||||
|           getList(); | ||||
|         }, | ||||
|       }); | ||||
|       // #ifdef H5 | ||||
|     } | ||||
|     // #endif | ||||
|   }; | ||||
|   const showMaoLocation = (e) => { | ||||
|     // #ifdef H5 | ||||
|     const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk; | ||||
|     if (jsWxSdk.isWechat()) { | ||||
|       jsWxSdk.openLocation({ | ||||
|         latitude: Number(e.latitude), | ||||
|         longitude: Number(e.longitude), | ||||
|         name: e.name, | ||||
|         address: `${e.areaName}-${e.detailAddress}`, | ||||
|       }); | ||||
|     } else { | ||||
|       // #endif | ||||
|       uni.openLocation({ | ||||
|         latitude: Number(e.latitude), | ||||
|         longitude: Number(e.longitude), | ||||
|         name: e.name, | ||||
|         address: `${e.areaName}-${e.detailAddress}`, | ||||
|         success: function () { | ||||
|           console.log('success'); | ||||
|         }, | ||||
|       }); | ||||
|       // #ifdef H5 | ||||
|     } | ||||
|     // #endif | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 选中门店 | ||||
|    */ | ||||
|   const checked = (addressInfo) => { | ||||
|     uni.$emit('SELECT_PICK_UP_INFO', { | ||||
|       addressInfo, | ||||
|     }); | ||||
|     sheep.$router.back(); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * 获取门店列表数据 | ||||
|    */ | ||||
|   const getList = async () => { | ||||
|     if (state.loading || state.loaded) { | ||||
|       return; | ||||
|     } | ||||
|     state.loading = true; | ||||
|     const { data, code } = await DeliveryApi.getDeliveryPickUpStoreList({ | ||||
|       latitude: state.user_latitude, | ||||
|       longitude: state.user_longitude, | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|     } | ||||
|     state.loading = false; | ||||
|     state.storeList = data; | ||||
|   }; | ||||
| 
 | ||||
|   onMounted(() => { | ||||
|     if (state.user_latitude && state.user_longitude) { | ||||
|       getList(); | ||||
|     } else { | ||||
|       selfLocation(); | ||||
|       getList(); | ||||
|     } | ||||
|   }); | ||||
|   onLoad(() => { | ||||
|     try { | ||||
|       state.user_latitude = uni.getStorageSync(LATITUDE); | ||||
|       state.user_longitude = uni.getStorageSync(LONGITUDE); | ||||
|     } catch (e) { | ||||
|       console.error(e); | ||||
|     } | ||||
|   }); | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
|   .line1 { | ||||
|     overflow: hidden; | ||||
|     text-overflow: ellipsis; | ||||
|     white-space: nowrap; | ||||
|   } | ||||
| 
 | ||||
|   .geoPage { | ||||
|     position: fixed; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     top: 0; | ||||
|     z-index: 10000; | ||||
|   } | ||||
| 
 | ||||
|   .storeBox { | ||||
|     width: 100%; | ||||
|     background-color: #fff; | ||||
|     padding: 0 30rpx; | ||||
|   } | ||||
| 
 | ||||
|   .storeBox-box { | ||||
|     width: 100%; | ||||
|     height: auto; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding: 23rpx 0; | ||||
|     justify-content: space-between; | ||||
|     border-bottom: 1px solid #eee; | ||||
|   } | ||||
| 
 | ||||
|   .store-cent { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     width: 80%; | ||||
|   } | ||||
| 
 | ||||
|   .store-cent-left { | ||||
|     //width: 45%; | ||||
|     flex: 2; | ||||
|   } | ||||
| 
 | ||||
|   .store-img { | ||||
|     flex: 1; | ||||
|     width: 120rpx; | ||||
|     height: 120rpx; | ||||
|     border-radius: 6rpx; | ||||
|     margin-right: 22rpx; | ||||
|   } | ||||
| 
 | ||||
|   .store-img .img { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|   } | ||||
| 
 | ||||
|   .store-name { | ||||
|     color: #282828; | ||||
|     font-size: 30rpx; | ||||
|     margin-bottom: 22rpx; | ||||
|     font-weight: 800; | ||||
|   } | ||||
| 
 | ||||
|   .store-address { | ||||
|     color: #666666; | ||||
|     font-size: 24rpx; | ||||
|   } | ||||
| 
 | ||||
|   .store-phone { | ||||
|     width: 50rpx; | ||||
|     height: 50rpx; | ||||
|     color: #fff; | ||||
|     border-radius: 50%; | ||||
|     display: block; | ||||
|     text-align: center; | ||||
|     line-height: 48rpx; | ||||
|     background-color: #e83323; | ||||
|     margin-bottom: 22rpx; | ||||
|     text-decoration: none; | ||||
|   } | ||||
| 
 | ||||
|   .store-distance { | ||||
|     font-size: 22rpx; | ||||
|     color: #e83323; | ||||
|   } | ||||
| 
 | ||||
|   .iconfont { | ||||
|     font-size: 20rpx; | ||||
|   } | ||||
| 
 | ||||
|   .row-right { | ||||
|     flex: 2; | ||||
|     //display: flex; | ||||
|     //flex-direction: column; | ||||
|     //align-items: flex-end; | ||||
|     //width: 33.5%; | ||||
|   } | ||||
| </style> | ||||
|  | @ -15,7 +15,7 @@ | |||
|             class="content-img" | ||||
|             isPreview | ||||
|             :current="0" | ||||
|             :src="state.model?.avatar || sheep.$url.static('/static/img/shop/default_avatar.png')" | ||||
|             :src="state.model?.avatar" | ||||
|             :height="160" | ||||
|             :width="160" | ||||
|             :radius="80" | ||||
|  | @ -26,8 +26,7 @@ | |||
|             <button | ||||
|               class="ss-reset-button avatar-action-btn" | ||||
|               open-type="chooseAvatar" | ||||
|               @chooseavatar="onChooseAvatar" | ||||
|             > | ||||
|               @chooseavatar="onChooseAvatar"> | ||||
|               修改 | ||||
|             </button> | ||||
|             <!-- #endif --> | ||||
|  | @ -155,7 +154,10 @@ | |||
|         </view> | ||||
|         <view class="ss-flex ss-col-center"> | ||||
|           <view class="info ss-flex ss-col-center" v-if="state.thirdInfo"> | ||||
|             <image class="avatar ss-m-r-20" :src="sheep.$url.cdn(state.thirdInfo.avatar)" /> | ||||
|             <image | ||||
|               class="avatar ss-m-r-20" | ||||
|               :src="sheep.$url.cdn(state.thirdInfo.avatar)" | ||||
|             /> | ||||
|             <text class="name">{{ state.thirdInfo.nickname }}</text> | ||||
|           </view> | ||||
|           <view class="bind-box ss-m-l-20"> | ||||
|  | @ -183,13 +185,10 @@ | |||
| <script setup> | ||||
|   import { computed, reactive, onBeforeMount } from 'vue'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import { clone } from 'lodash-es'; | ||||
|   import { clone } from 'lodash'; | ||||
|   import { showAuthModal } from '@/sheep/hooks/useModal'; | ||||
|   import FileApi from '@/sheep/api/infra/file'; | ||||
|   import UserApi from '@/sheep/api/member/user'; | ||||
|   import { | ||||
|     chooseAndUploadFile, | ||||
|     uploadFilesFromPath, | ||||
|   } from '@/sheep/components/s-uploader/choose-and-upload-file'; | ||||
| 
 | ||||
|   const state = reactive({ | ||||
|     model: {}, // 个人信息 | ||||
|  | @ -199,15 +198,14 @@ | |||
| 
 | ||||
|   const placeholderStyle = 'color:#BBBBBB;font-size:28rpx;line-height:normal'; | ||||
| 
 | ||||
|   const sexRadioMap = [ | ||||
|     { | ||||
|   const sexRadioMap = [{ | ||||
|       name: '男', | ||||
|       value: '1', | ||||
|     }, | ||||
|     { | ||||
|       name: '女', | ||||
|       value: '2', | ||||
|     }, | ||||
|     } | ||||
|   ]; | ||||
| 
 | ||||
|   const userInfo = computed(() => sheep.$store('user').userInfo); | ||||
|  | @ -223,22 +221,28 @@ | |||
|   }; | ||||
| 
 | ||||
|   // 选择微信的头像,进行上传 | ||||
|   async function onChooseAvatar(e) { | ||||
|     debugger; | ||||
|   function onChooseAvatar(e) { | ||||
|     const tempUrl = e.detail.avatarUrl || ''; | ||||
|     if (!tempUrl) return; | ||||
|     const files = await uploadFilesFromPath(tempUrl); | ||||
|     if (files.length > 0) { | ||||
|       state.model.avatar = files[0].url; | ||||
|     } | ||||
|     uploadAvatar(tempUrl); | ||||
|   } | ||||
| 
 | ||||
|   // 手动选择头像,进行上传 | ||||
|   async function onChangeAvatar() { | ||||
|     const files = await chooseAndUploadFile({ type: 'image' }); | ||||
|     if (files.length > 0) { | ||||
|       state.model.avatar = files[0].url; | ||||
|   function onChangeAvatar() { | ||||
|     uni.chooseImage({ | ||||
|       success: async (chooseImageRes) => { | ||||
|         const tempUrl = chooseImageRes.tempFilePaths[0]; | ||||
|         await uploadAvatar(tempUrl); | ||||
|       }, | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // 上传头像文件 | ||||
|   async function uploadAvatar(tempUrl) { | ||||
|     if (!tempUrl) { | ||||
|       return; | ||||
|     } | ||||
|     let { data } = await FileApi.uploadFile(tempUrl); | ||||
|     state.model.avatar = data; | ||||
|   } | ||||
| 
 | ||||
|   // 修改密码 | ||||
|  | @ -275,7 +279,7 @@ | |||
| 
 | ||||
|   // 保存信息 | ||||
|   async function onSubmit() { | ||||
|     const { code } = await UserApi.updateUser({ | ||||
| 	 const { code } = await UserApi.updateUser({ | ||||
|       avatar: state.model.avatar, | ||||
|       nickname: state.model.nickname, | ||||
|       sex: state.model.sex, | ||||
|  |  | |||
|  | @ -13,9 +13,7 @@ | |||
|           /> | ||||
|         </view> | ||||
|         <view class="ss-flex ss-row-between ss-col-center ss-m-t-64"> | ||||
|           <view class="money-num">{{ | ||||
|             state.showMoney ? fen2yuan(userWallet.balance) : '*****' | ||||
|           }}</view> | ||||
|           <view class="money-num">{{ state.showMoney ? fen2yuan(userWallet.balance) : '*****' }}</view> | ||||
|           <button class="ss-reset-button topup-btn" @tap="sheep.$router.go('/pages/pay/recharge')"> | ||||
|             充值 | ||||
|           </button> | ||||
|  | @ -26,12 +24,7 @@ | |||
|     <su-sticky> | ||||
|       <!-- 统计 --> | ||||
|       <view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between"> | ||||
|         <uni-datetime-picker | ||||
|           v-model="state.data" | ||||
|           type="daterange" | ||||
|           @change="onChangeTime" | ||||
|           :end="state.today" | ||||
|         > | ||||
|         <uni-datetime-picker v-model="state.data" type="daterange" @change="onChangeTime" :end="state.today"> | ||||
|           <button class="ss-reset-button date-btn"> | ||||
|             <text>{{ dateFilterText }}</text> | ||||
|             <text class="cicon-drop-down ss-seldate-icon"></text> | ||||
|  | @ -89,10 +82,10 @@ | |||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import sheep from '@/sheep'; | ||||
|   import dayjs from 'dayjs'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import PayWalletApi from '@/sheep/api/pay/wallet'; | ||||
|   import { fen2yuan } from '@/sheep/hooks/useGoods'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
| 
 | ||||
|   const headerBg = sheep.$url.css('/static/img/shop/user/wallet_card_bg.png'); | ||||
| 
 | ||||
|  | @ -105,7 +98,7 @@ | |||
|       list: [], | ||||
|       total: 0, | ||||
|       pageNo: 1, | ||||
|       pageSize: 8, | ||||
|       pageSize: 8 | ||||
|     }, | ||||
|     summary: { | ||||
|       totalIncome: 0, | ||||
|  | @ -162,7 +155,7 @@ | |||
|   // 获得钱包统计 | ||||
|   async function getSummary() { | ||||
|     const { data, code } = await PayWalletApi.getWalletTransactionSummary({ | ||||
|       createTime: [state.date[0] + ' 00:00:00', state.date[1] + ' 23:59:59'], | ||||
|       'createTime': [state.date[0] + ' 00:00:00', state.date[1] + ' 23:59:59'], | ||||
|     }); | ||||
|     if (code !== 0) { | ||||
|       return; | ||||
|  | @ -233,7 +226,8 @@ | |||
|         position: absolute; | ||||
|         top: 0; | ||||
|         left: 0; | ||||
|         background: v-bind(headerBg) no-repeat; | ||||
|         background: v-bind(headerBg) | ||||
|           no-repeat; | ||||
|         pointer-events: none; | ||||
|       } | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,24 +24,19 @@ | |||
|     <su-sticky :customNavHeight="sys_navBar"> | ||||
|       <!-- 统计 --> | ||||
|       <view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between"> | ||||
|         <uni-datetime-picker | ||||
|           v-model="state.date" | ||||
|           type="daterange" | ||||
|           @change="onChangeTime" | ||||
|           :end="state.today" | ||||
|         > | ||||
|           <button class="ss-reset-button date-btn"> | ||||
|             <text>{{ dateFilterText }}</text> | ||||
|             <text class="cicon-drop-down ss-seldate-icon"></text> | ||||
|           </button> | ||||
|         </uni-datetime-picker> | ||||
| 				<uni-datetime-picker v-model="state.date" type="daterange" @change="onChangeTime" :end="state.today"> | ||||
| 					<button class="ss-reset-button date-btn"> | ||||
| 						<text>{{ dateFilterText }}</text> | ||||
| 						<text class="cicon-drop-down ss-seldate-icon"></text> | ||||
| 					</button> | ||||
| 				</uni-datetime-picker> | ||||
| 
 | ||||
|         <!-- TODO 芋艿:【钱包-可优化】展示一下 --> | ||||
|         <!--				<view class="total-box">--> | ||||
|         <!--					<view class="ss-m-b-10">总收入¥{{ state.pagination.income }}</view>--> | ||||
|         <!--					<view>总支出¥{{ -state.pagination.expense }}</view>--> | ||||
|         <!--				</view>--> | ||||
|       </view> | ||||
|         <!-- TODO 芋艿:优化 --> | ||||
| <!--				<view class="total-box">--> | ||||
| <!--					<view class="ss-m-b-10">总收入¥{{ state.pagination.income }}</view>--> | ||||
| <!--					<view>总支出¥{{ -state.pagination.expense }}</view>--> | ||||
| <!--				</view>--> | ||||
| 			</view> | ||||
|       <su-tabs | ||||
|         :list="tabMaps" | ||||
|         @change="onChange" | ||||
|  | @ -88,10 +83,10 @@ | |||
|   import sheep from '@/sheep'; | ||||
|   import { onLoad, onReachBottom } from '@dcloudio/uni-app'; | ||||
|   import { computed, reactive } from 'vue'; | ||||
|   import _ from 'lodash-es'; | ||||
|   import _ from 'lodash'; | ||||
|   import dayjs from 'dayjs'; | ||||
|   import PointApi from '@/sheep/api/member/point'; | ||||
|   import { resetPagination } from '@/sheep/helper/utils'; | ||||
|   import { resetPagination } from '@/sheep/util'; | ||||
| 
 | ||||
|   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2; | ||||
|   const userInfo = computed(() => sheep.$store('user').userInfo); | ||||
|  | @ -279,4 +274,4 @@ | |||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| </style> | ||||
|  | @ -1,11 +0,0 @@ | |||
| // 目的:解决微信小程序的「代码质量」在「JS 文件」提示:主包内,不应该存在主包未使用的 JS 文件
 | ||||
| const files = import.meta.glob('./*/*.js', { eager: true }); | ||||
| let api = {}; | ||||
| Object.keys(files).forEach((key) => { | ||||
|   api = { | ||||
|     ...api, | ||||
|     [key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default, | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
| export default api; | ||||
|  | @ -1,9 +1,10 @@ | |||
| import { baseUrl, apiPath, tenantId } from '@/sheep/config'; | ||||
| import request, { getAccessToken } from '@/sheep/request'; | ||||
| import { baseUrl, apiPath } from '@/sheep/config'; | ||||
| 
 | ||||
| const FileApi = { | ||||
|   // 上传文件
 | ||||
|   uploadFile: (file, directory = '') => { | ||||
|   uploadFile: (file) => { | ||||
|     // TODO 芋艿:访问令牌的接入;
 | ||||
|     const token = uni.getStorageSync('token'); | ||||
|     uni.showLoading({ | ||||
|       title: '上传中', | ||||
|     }); | ||||
|  | @ -13,12 +14,10 @@ const FileApi = { | |||
|         filePath: file, | ||||
|         name: 'file', | ||||
|         header: { | ||||
|           Accept: '*/*', | ||||
|           'tenant-id': tenantId, | ||||
|           Authorization: 'Bearer ' + getAccessToken(), | ||||
|         }, | ||||
|         formData: { | ||||
|           directory, | ||||
|           // Accept: 'text/json',
 | ||||
|           Accept : '*/*', | ||||
|           'tenant-id' :'1', | ||||
|           // Authorization:  'Bearer test247',
 | ||||
|         }, | ||||
|         success: (uploadFileRes) => { | ||||
|           let result = JSON.parse(uploadFileRes.data); | ||||
|  | @ -41,27 +40,6 @@ const FileApi = { | |||
|       }); | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   // 获取文件预签名地址
 | ||||
|   getFilePresignedUrl: (name, directory) => { | ||||
|     return request({ | ||||
|       url: '/infra/file/presigned-url', | ||||
|       method: 'GET', | ||||
|       params: { | ||||
|         name, | ||||
|         directory, | ||||
|       }, | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   // 创建文件
 | ||||
|   createFile: (data) => { | ||||
|     return request({ | ||||
|       url: '/infra/file/create', // 请求的 URL
 | ||||
|       method: 'POST', // 请求方法
 | ||||
|       data: data, // 要发送的数据
 | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export default FileApi; | ||||
|  |  | |||
|  | @ -1,17 +0,0 @@ | |||
| import request from '@/sheep/request'; | ||||
| 
 | ||||
| /** | ||||
|  * 通过网站域名获取租户信息 | ||||
|  * @param {string} website - 网站域名 | ||||
|  * @returns {Promise<Object>} 租户信息 | ||||
|  */ | ||||
| export function getTenantByWebsite(website) { | ||||
|   return request({ | ||||
|     url: '/system/tenant/get-by-website', | ||||
|     method: 'GET', | ||||
|     params: { website }, | ||||
|     custom: { | ||||
|       isToken: false, // 避免登录情况下,跨租户访问被拦截
 | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | @ -56,10 +56,10 @@ const AuthUtil = { | |||
|       url: '/member/auth/refresh-token', | ||||
|       method: 'POST', | ||||
|       params: { | ||||
|         refreshToken, | ||||
|         refreshToken | ||||
|       }, | ||||
|       custom: { | ||||
|         showLoading: false, // 不用加载中
 | ||||
|         loading: false, // 不用加载中
 | ||||
|         showError: false, // 不展示错误提示
 | ||||
|       }, | ||||
|     }); | ||||
|  | @ -103,7 +103,7 @@ const AuthUtil = { | |||
|       data: { | ||||
|         phoneCode, | ||||
|         loginCode, | ||||
|         state, | ||||
|         state | ||||
|       }, | ||||
|       custom: { | ||||
|         showSuccess: true, | ||||
|  | @ -118,13 +118,13 @@ const AuthUtil = { | |||
|       url: '/member/auth/create-weixin-jsapi-signature', | ||||
|       method: 'POST', | ||||
|       params: { | ||||
|         url, | ||||
|         url | ||||
|       }, | ||||
|       custom: { | ||||
|         showError: false, | ||||
|         showLoading: false, | ||||
|       }, | ||||
|     }); | ||||
|     }) | ||||
|   }, | ||||
|   //
 | ||||
| }; | ||||
|  |  | |||
|  | @ -49,28 +49,6 @@ const SocialApi = { | |||
|       }, | ||||
|     }); | ||||
|   }, | ||||
|   // 获取订阅消息模板列表
 | ||||
|   getSubscribeTemplateList: () => | ||||
|     request({ | ||||
|       url: '/member/social-user/get-subscribe-template-list', | ||||
|       method: 'GET', | ||||
|       custom: { | ||||
|         showError: false, | ||||
|         showLoading: false, | ||||
|       }, | ||||
|     }), | ||||
|   // 获取微信小程序码
 | ||||
|   getWxaQrcode: async (path, query) => { | ||||
|     return await request({ | ||||
|       url: '/member/social-user/wxa-qrcode', | ||||
|       method: 'POST', | ||||
|       data: { | ||||
|         scene: query, | ||||
|         path, | ||||
|         checkPath: false, // TODO 开发环境暂不检查 path 是否存在
 | ||||
|       }, | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| export default SocialApi; | ||||
|  | @ -1,6 +1,6 @@ | |||
| import request from '@/sheep/request'; | ||||
| 
 | ||||
| // TODO 芋艿:【直播】小程序直播还不支持
 | ||||
| // TODO 芋艿:小程序直播还不支持
 | ||||
| export default { | ||||
|   //小程序直播
 | ||||
|   mplive: { | ||||
|  | @ -10,12 +10,12 @@ export default { | |||
|         method: 'GET', | ||||
|         params: { | ||||
|           ids: ids.join(','), | ||||
|         }, | ||||
|         } | ||||
|       }), | ||||
|     getMpLink: () => | ||||
|       request({ | ||||
|         url: 'app/mplive/getMpLink', | ||||
|         method: 'GET', | ||||
|         method: 'GET' | ||||
|       }), | ||||
|   }, | ||||
| }; | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| import request from '@/sheep/request'; | ||||
| 
 | ||||
| // TODO 芋艿:暂不支持 socket 聊天
 | ||||
| export default { | ||||
|   // 获取聊天token
 | ||||
|   unifiedToken: () => | ||||
|     request({ | ||||
|       url: 'unifiedToken', | ||||
|       custom: { | ||||
|         showError: false, | ||||
|         showLoading: false, | ||||
|       }, | ||||
|     }), | ||||
| }; | ||||
|  | @ -0,0 +1,10 @@ | |||
| const files = import.meta.globEager('./*.js'); | ||||
| let api = {}; | ||||
| Object.keys(files).forEach((key) => { | ||||
|   api = { | ||||
|     ...api, | ||||
|     [key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default, | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
| export default api; | ||||
|  | @ -1,6 +1,32 @@ | |||
| import request from '@/sheep/request'; | ||||
| import { baseUrl, apiPath } from '@/sheep/config'; | ||||
| 
 | ||||
| export default { | ||||
|   // 微信相关
 | ||||
|   wechat: { | ||||
|     // 小程序订阅消息
 | ||||
|     subscribeTemplate: (params) => | ||||
|       request({ | ||||
|         url: 'third/wechat/subscribeTemplate', | ||||
|         method: 'GET', | ||||
|         params: { | ||||
|           platform: 'miniProgram', | ||||
|         }, | ||||
|         custom: { | ||||
|           showError: false, | ||||
|           showLoading: false, | ||||
|         }, | ||||
|       }), | ||||
| 
 | ||||
|     // 获取微信小程序码
 | ||||
|     getWxacode: (path) => | ||||
|       `${baseUrl}${apiPath}third/wechat/wxacode?platform=miniProgram&payload=${encodeURIComponent( | ||||
|         JSON.stringify({ | ||||
|           path, | ||||
|         }), | ||||
|       )}`,
 | ||||
|   }, | ||||
| 
 | ||||
|   // 苹果相关
 | ||||
|   apple: { | ||||
|     // 第三方登录
 | ||||
|  |  | |||
|  | @ -2,11 +2,11 @@ import request from '@/sheep/request'; | |||
| 
 | ||||
| const PayOrderApi = { | ||||
|   // 获得支付订单
 | ||||
|   getOrder: (id, sync) => { | ||||
|   getOrder: (id) => { | ||||
|     return request({ | ||||
|       url: '/pay/order/get', | ||||
|       method: 'GET', | ||||
|       params: { id, sync }, | ||||
|       params: { id } | ||||
|     }); | ||||
|   }, | ||||
|   // 提交支付订单
 | ||||
|  | @ -14,9 +14,9 @@ const PayOrderApi = { | |||
|     return request({ | ||||
|       url: '/pay/order/submit', | ||||
|       method: 'POST', | ||||
|       data, | ||||
|       data | ||||
|     }); | ||||
|   }, | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export default PayOrderApi; | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue