zory 4 settimane fa
parent
commit
478c5c4294

+ 1 - 0
api/home.js

@@ -0,0 +1 @@
+import request from '@/static/js/request.js'

+ 10 - 8
pages.json

@@ -71,15 +71,17 @@
 			}
 		},
 		{
-			"path": "components/login/halfScreen",
+			"path": "pages/task/detail",
 			"style": {
-				"navigationBarTitleText": ""
-			}
-		},
-		{
-			"path": "pages/task/filter",
-			"style": {
-				"navigationBarTitleText": ""
+				"navigationBarTitleText": "",
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "black",
+				"backgroundColor": "#f8f8f8",
+				"backgroundColorTop": "#f8f8f8",
+				"backgroundColorBottom": "#f8f8f8",
+				"enablePullDownRefresh": false,
+				"disableScroll": true,
+				"usingComponents": true
 			}
 		}
 	],

+ 163 - 5
pages/index/index.vue

@@ -51,9 +51,6 @@
 					</view>
 				</view>
 				<view class="home-content">
-					<view class="banner" @click="test">
-						<image src="https://blue-data.oss-cn-guangzhou.aliyuncs.com/star/home-top-banner.png" mode="widthFix"></image>
-					</view>
 					<view class="quick-nav">
 						<view class="nav-item">
 							<view class="icon"><image src="/static/quick-nav/star-rank.png" mode="widthFix"></image></view>
@@ -68,6 +65,9 @@
 							<view class="name">达人之家</view>
 						</view>
 					</view>
+					<view class="banner" @click="test">
+						<image src="https://blue-data.oss-cn-guangzhou.aliyuncs.com/star/banner.png" mode="widthFix"></image>
+					</view>
 					<view class="new-star">
 						<view class="title">
 							<view class="name">最新入驻达人</view>
@@ -91,6 +91,11 @@
 						</view>
 						<view class="nav-item w50">
 							<image class="w100" src="/static/entry/task-square.png" mode="widthFix"></image>
+							<view class="new-task">
+								<image class="task-img-1" src="https://p9-dy.byteimg.com/obj/ecom-shop-material/jpeg_m_e3fa80e91300b16e0df15e2bcbd8c003_sx_426270_www800-800"></image>
+								<image class="task-img-2" src="https://p9-dy.byteimg.com/obj/ecom-shop-material/jpeg_m_e3fa80e91300b16e0df15e2bcbd8c003_sx_426270_www800-800"></image>
+								<image class="task-img-3" src="https://p9-dy.byteimg.com/obj/ecom-shop-material/jpeg_m_e3fa80e91300b16e0df15e2bcbd8c003_sx_426270_www800-800"></image>
+							</view>
 						</view>
 					</view>
 					<view class="rank-data">
@@ -98,6 +103,55 @@
 							<view class="name">收益排行榜</view>
 							<view class="more">查看全部 <text class="cuIcon-right"></text></view>
 						</view>
+						<view class="rank-top">
+							<view class="top-item rank-2">
+								<image src="/static/rank/podium-rank2-bg.png" class="bg" mode="scaleToFill"></image>
+								<view class="top-info">
+									<view class="avatar">
+										<image src="/static/rank/avatar.jpeg"></image>
+										<view class="top-num"><image src="/static/rank/top-rank2.png"></image></view>
+									</view>
+									<view class="name">浅**浅</view>
+									<view class="num"><text>¥</text>0.87w</view>
+								</view>
+							</view>
+							<view class="top-item rank-1">
+								<image src="/static/rank/podium-rank1-bg.png" class="bg" mode="scaleToFill"></image>
+								<view class="top-info">
+									<view class="avatar">
+										<image src="/static/rank/avatar.jpeg"></image>
+										<view class="top-num"><image src="/static/rank/top-rank1.png"></image></view>
+									</view>
+									<view class="name">浅**浅</view>
+									<view class="num"><text>¥</text>0.87w</view>
+								</view>
+							</view>
+							<view class="top-item rank-3">
+								<image src="/static/rank/podium-rank3-bg.png" class="bg" mode="scaleToFill"></image>
+								<view class="top-info">
+									<view class="avatar">
+										<image src="/static/rank/avatar.jpeg"></image>
+										<view class="top-num"><image src="/static/rank/top-rank3.png"></image></view>
+									</view>
+									<view class="name">浅**浅</view>
+									<view class="num"><text>¥</text>0.87w</view>
+								</view>
+							</view>
+						</view>
+						<view class="rank-list">
+							<view class="rank-item">
+								<view class="num">4</view>
+								<view class="avatar"><image src="/static/rank/avatar.jpeg"></image></view>
+								<view class="name">我是庄小周</view>
+								<view class="money"><text>¥</text>0.23w</view>
+							</view>
+							<view class="rank-item">
+								<view class="num">5</view>
+								<view class="avatar"><image src="/static/rank/avatar.jpeg"></image></view>
+								<view class="name">我是庄小周</view>
+								<view class="money"><text>¥</text>0.23w</view>
+							</view>
+						</view>
 					</view>
 					<view class="home-task">
 						<view class="title">
@@ -207,18 +261,62 @@ export default {
 <style lang="scss">
 page{background-color: #f8f8f8;}
 .banner-loading{height: 220rpx;}
+.rank-data .rank-top{padding: 20rpx;display: flex;align-items: flex-end;}
+.rank-data .rank-top .top-item{flex: 1;position: relative;min-height: 200rpx;}
+.rank-data .rank-top .top-item.rank-1{height: 260rpx;}
+.rank-data .rank-top .top-item.rank-2{height: 220rpx;}
+.rank-data .rank-top .top-item.rank-3{height: 220rpx;}
+.rank-data .rank-top .top-item .bg{width: 100%;position: absolute;left: 0;right: 0;bottom: 0;height: 86%;}
+.rank-data .rank-top .top-info {position: relative;z-index: 99;display: flex;flex-direction: column;align-items: center;justify-content: center;}
+.rank-data .rank-top .top-info .avatar{position: relative;width: 80rpx;height: 80rpx;margin-bottom: 40rpx;}
+.rank-data .rank-top .top-info .avatar image{width: 80rpx;height: 80rpx;border-radius: 100%;border: 4rpx solid #fff;}
+.rank-data .rank-top .top-info .avatar .top-num {position: absolute;right: -60rpx;top: 0;}
+.rank-data .rank-top .top-info .avatar .top-num image{width: 80rpx;height: 40rpx;border-radius: 0;border: 0;}
+
+.rank-data .rank-top .top-info .name{color: #666;font-size: 24rpx;margin-bottom: 10rpx;}
+.rank-data .rank-top .top-info .num{display: flex;align-items: baseline;font-weight: 800;color: #000;font-size: 28rpx;}
+.rank-data .rank-top .top-info .num text{font-weight: normal;color: #666;font-size: 24rpx;}
+
+.rank-data .rank-list{padding: 0 20rpx;}
+.rank-data .rank-list .rank-item{display: flex;align-items: center;padding: 10rpx 0;font-size: 24rpx;color: #000;}
+.rank-data .rank-list .rank-item .num{color: rgba(141, 141, 141, 1);}
+.rank-data .rank-list .rank-item .avatar {margin: 0 20rpx;}
+.rank-data .rank-list .rank-item .avatar image{width: 50rpx;height: 50rpx;border-radius: 100%;}
+.rank-data .rank-list .rank-item .money{
+	display: flex;
+	align-items: baseline;
+	flex-shrink: 0;
+	font-weight: 800;
+	font-size: 28rpx;
+	color: #000;
+	margin-left: auto;
+	margin-right: 0;
+}
+.rank-data .rank-list .rank-item .money text{
+	font-weight: normal;
+	color: #666;
+	font-size: 24rpx;
+}
+
 .home-search-loading{margin: 20rpx;height: 68rpx;}
 .home-search{display: flex;align-items: center;background-color: #fff;border-radius: 66rpx;margin: 20rpx;height: 68rpx;padding: 0 20rpx;gap: 20rpx;}
 .home-search image{width: 32rpx;height: 32rpx;}
 .home-search .search-input{font-size: 24rpx;width: 100%;border: 0;height: 100%;box-sizing: none;box-shadow: none;}
 .home-content {margin: 20rpx;}
-.home-content .banner{}
+.home-content .banner{margin: 20rpx 0;}
 .home-content .banner image{width: 100%;}
 .quick-nav{display: flex;align-items: center;margin: 20rpx 0;gap: 20rpx;}
-.quick-nav .nav-item{width: 33%;text-align: center;font-family: "bluevBold";font-size: 24rpx;}
+.quick-nav .nav-item{width: 33%;text-align: center;font-family: "bluevBold";font-size: 24rpx;position: relative;}
 .quick-nav .nav-item .icon image{width: 60%;}
 .quick-nav .nav-item.w50{width: 50%;}
 .quick-nav .nav-item .icon-loading{height: 180rpx;}
+.quick-nav .nav-item .new-task{position: absolute;bottom: 35rpx;display: flex;align-items: center;left: 30rpx;}
+.quick-nav .nav-item .new-task image{width: 60rpx;height: 60rpx;border-radius: 10rpx;border: 2rpx solid #fff;}
+.quick-nav .nav-item .new-task .task-img-1{transform: rotate(0deg);animation: entry-task-wobble-0 5.5s ease-in-out infinite both;}
+.quick-nav .nav-item .new-task .task-img-2{transform: rotate(17deg);margin-left: -10rpx;animation: entry-task-wobble-1 5.5s ease-in-out infinite both;}
+.quick-nav .nav-item .new-task .task-img-3{transform: rotate(0deg);margin-left: -10rpx;animation: entry-task-wobble-2 5.5s ease-in-out infinite both;}
+
+
 .new-star .title{display: flex;align-items: center;justify-content: space-between;line-height: 60rpx;}
 .new-star .title .name{font-family: "bluevBold";font-size: 32rpx;}
 .new-star .title .more{font-size: 24rpx;color: #666;}
@@ -279,4 +377,64 @@ page{background-color: #f8f8f8;}
 	max-height: 200rpx;
 	border-radius: 100%;
 }
+
+@keyframes entry-task-wobble-0 {
+	0%,
+	82%,
+	100% {
+		transform: rotate(0deg) translateY(0);
+	}
+	86% {
+		transform: rotate(5deg) translateY(-2rpx);
+	}
+	90% {
+		transform: rotate(-4deg) translateY(0);
+	}
+	94% {
+		transform: rotate(3deg) translateY(-1rpx);
+	}
+	97% {
+		transform: rotate(-2deg) translateY(0);
+	}
+}
+
+@keyframes entry-task-wobble-1 {
+	0%,
+	82%,
+	100% {
+		transform: rotate(17deg) translateY(0);
+	}
+	86% {
+		transform: rotate(22deg) translateY(-2rpx);
+	}
+	90% {
+		transform: rotate(13deg) translateY(0);
+	}
+	94% {
+		transform: rotate(20deg) translateY(-1rpx);
+	}
+	97% {
+		transform: rotate(15deg) translateY(0);
+	}
+}
+
+@keyframes entry-task-wobble-2 {
+	0%,
+	82%,
+	100% {
+		transform: rotate(0deg) translateY(0);
+	}
+	86% {
+		transform: rotate(-5deg) translateY(-2rpx);
+	}
+	90% {
+		transform: rotate(4deg) translateY(0);
+	}
+	94% {
+		transform: rotate(-3deg) translateY(-1rpx);
+	}
+	97% {
+		transform: rotate(2deg) translateY(0);
+	}
+}
 </style>

+ 191 - 0
pages/task/detail.vue

@@ -0,0 +1,191 @@
+<template>
+	<view>
+		<scroll-view scroll-y="true" class="scroll-content" :enhanced="true" :bounces="false" :show-scrollbar="false" @scroll="pageScroll">
+			<view class="detail-body">
+				<view :class="scrollHeight>20?'cu-bar fixed bg-bluev-head':'cu-bar fixed'" :style="barStyle">
+					<view class="action" @tap="BackPage">
+						<text class="cuIcon-back"></text>
+						<view class="backText">任务详情</view>
+					</view>
+				</view>
+				<view class="detail-banner">
+					<image src="https://p9-dy.byteimg.com/obj/ecom-shop-material/jpeg_m_e3fa80e91300b16e0df15e2bcbd8c003_sx_426270_www800-800" mode="widthFix"></image>
+				</view>
+				<view class="detail-content">
+					<view class="bg-title"></view>
+					<view class="bg"><image src="/static/task/detail-bg.png" mode="widthFix"></image></view>
+					<view class="detail-zindex">
+						<view class="detail-title">
+							【拍3盒赠1试用】妇炎洁熊果苷凝胶 <br>女性抑菌凝胶养护抑菌保湿滋润-星川任务-20-星川任务-20-星川任务-20-星川任务-20
+						</view>
+						<view class="detail-platform">
+							<view class="platform-item"><image src="/static/image/douyin.svg"></image>抖音</view>
+						</view>
+						<view class="detail-time">
+							<view class="time-item">
+								<view class="time-icon"><text class="cuIcon-timefill"></text>活动时间</view>
+								<view class="time-text">08.21-08.30</view>
+							</view>
+							<view class="time-item">
+								<view class="time-icon"><text class="cuIcon-group_fill"></text>参与人员</view>
+								<view class="time-text">100/200人</view>
+							</view>
+						</view>
+						<view class="user-total">
+							<view class="total-item">
+								<view class="num">102</view>
+								<view class="name">招募名额</view>
+							</view>
+							<view class="total-item">
+								<view class="num">102</view>
+								<view class="name">已报名</view>
+							</view>
+							<view class="total-item">
+								<view class="num">102</view>
+								<view class="name">售价(元)</view>
+							</view>
+							<view class="total-item">
+								<view class="num">10<text>%</text></view>
+								<view class="name">达人佣金</view>
+							</view>
+						</view>
+						
+						<view class="detail-info">
+							<view class="title">
+								<view class="name">
+									<text>任务信息</text>
+									<image src="https://blue-data.oss-cn-guangzhou.aliyuncs.com/star/tab-active-badge.png"></image>
+								</view>
+							</view>
+							<view class="info-item">
+								<view class="left">内容形式</view>
+								<view class="val">图文视频</view>
+							</view>
+							<view class="info-item">
+								<view class="left">内容要求</view>
+								<view class="val">15~60秒</view>
+							</view>
+							<view class="info-item">
+								<view class="left">内容形式</view>
+								<view class="val">分享使用感受,突出产品卖点,点赞达到500+,收藏数达到100+</view>
+							</view>
+							<view class="info-item">
+								<view class="left">发布平台</view>
+								<view class="val">小红书</view>
+							</view>
+							<view class="info-item">
+								<view class="left">内容要求</view>
+								<view class="val">原创内容,严禁抄袭</view>
+							</view>
+						</view>
+						<view class="detail-info">
+							<view class="title">
+								<view class="name">
+									<text>更多解读</text>
+									<image src="https://blue-data.oss-cn-guangzhou.aliyuncs.com/star/tab-active-badge.png"></image>
+								</view>
+							</view>
+							<view class="rich-content">
+								
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+		<view class="detail-footer">
+			<view class="collect">
+				<view class="icon">
+					<image src="/static/task/star.svg"></image>
+				</view>
+				<view class="name">收藏</view>
+			</view>
+			<view class="btn">
+				<button class="u-button full">立即报名</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+var app;
+export default {
+	data() {
+		return {
+			scrollHeight:0,
+			StatusBar: this.StatusBar,
+			CustomBar: this.CustomBar,
+			barStyle:""
+		}
+	},
+	onLoad() {
+		app = this;
+		var StatusBar= this.StatusBar;
+		var CustomBar= this.CustomBar;
+		var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+		app.barStyle = style;
+	},
+	methods: {
+		pageScroll(event) {
+			var scrollHeight = event.detail.scrollTop;
+			app.scrollHeight = scrollHeight;
+		},
+		BackPage() {
+			uni.navigateBack({
+				delta: 1
+			});
+		}
+	}
+}
+</script>
+
+<style>
+page{background-color: rgba(245, 245, 245, 1);}
+
+.detail-bar{opacity: 0;}
+
+.detail-footer{position: fixed;bottom: 0;left: 0;right: 0;background-color: #fff;padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));box-shadow: 0 -4rpx 24rpx rgba(21, 24, 29, 0.06);box-sizing: border-box;display: flex;align-items: center;gap: 20rpx;z-index: 999}
+.detail-footer .collect{display: flex;align-items: center;flex-direction: column;justify-content: center;font-size: 24rpx;color: #000;}
+.detail-footer .collect image{width: 50rpx;height: 50rpx;}
+.detail-footer .btn{flex: 1;margin-left: 20rpx;}
+.detail-footer .u-button{font-size: 28upx;height: 80upx;border-radius: 40upx;line-height: 80upx;color: #333;border: 2upx solid rgba(33, 34, 38, 1);background-color: #fff;width: 100%;}
+.detail-footer .u-button::after{border: 0;}
+.detail-footer .u-button.full{background-color: rgba(33, 34, 38, 1);color: #fff;}
+
+.cu-bar.fixed{box-shadow: none;}
+.cu-bar .backText{opacity: 0;}
+.cu-bar.bg-bluev-head .backText{opacity: 1;}
+.detail-banner image{width: 100%;}
+.detail-content{position: relative;margin-top: -20rpx;}
+.detail-content .bg {position: absolute;top: 0;left: 0;right: 0;}
+.detail-content .bg-title {position: absolute;top: 0;left: 0;right: 0;}
+.detail-content .bg image{width: 100%;}
+.detail-content .bg-title{background-color: #c3ebfb; height: 80rpx;}
+.detail-content .detail-zindex{position: relative;z-index: 999;padding: 20rpx;}
+.detail-content .detail-title{font-size: 32rpx;color: #000;font-weight: 800;}
+
+.detail-content .detail-info{background-color: #fff;border-radius: 20rpx;padding: 20rpx;margin: 20rpx 0;}
+.detail-content .detail-info .info-item{display: flex;align-items: baseline;gap: 20rpx;padding: 10rpx 0;}
+.detail-content .detail-info .info-item .left{color: rgba(0, 0, 0, 0.2);font-size: 28rpx;}
+.detail-content .detail-info .info-item .val{color: #212226;font-size: 28rpx;flex: 1;}
+.detail-content .detail-info .title{display: flex;align-items: center;justify-content: space-between;margin-bottom: 20rpx;}
+.detail-content .detail-info .name{font-size: 32rpx;color: #000;font-weight: 800;position: relative;}
+.detail-content .detail-info .name text{position: relative;z-index: 999;font-family: "bluevBold";}
+.detail-content .detail-info .name image{width: 82rpx;height: 52rpx;position: absolute;right: -20rpx;bottom: 0;z-index: 99;}
+
+.detail-content .detail-time{display: flex;align-items: center;justify-content: space-between;gap: 20rpx;}
+.detail-content .detail-time .time-item{display: flex;align-items: center;justify-content: space-between;gap: 20rpx;}
+.detail-content .detail-time .time-item .time-icon{color: #212226;font-size: 28rpx;font-weight: 800;display: flex;align-items: center;gap: 10rpx;}
+.detail-content .detail-time .time-item .time-icon text{margin-top: 2rpx;}
+.detail-content .detail-time .time-item .time-text{color: #8c8c8c;font-size: 28rpx;}
+
+
+.detail-content .detail-platform{display: flex;align-items: center;margin: 20rpx 0;}
+.detail-content .detail-platform .platform-item{display: flex;align-items: center;background-color: #fff;border-radius: 40rpx;font-size: 28rpx;color: #000;padding: 10rpx 30rpx;gap: 10rpx;}
+.detail-content .detail-platform .platform-item image{width: 30rpx;height: 30rpx;}
+
+.detail-content .user-total{display: flex;align-items: flex-start;justify-content: space-between;width: 100%;flex-direction: row;margin: 40rpx 0;}
+.detail-content .user-total .total-item{min-width: 0%;display: flex;flex-direction: column;align-items: center;justify-content: center;}
+.detail-content .user-total .total-item .num{color: #212226;font-size: 36rpx;font-weight: 800;line-height: 1.1;text-align: center;}
+.detail-content .user-total .total-item .name{color: rgba(21, 21, 21, 0.45);font-size: 22rpx;line-height: 1.2;text-align: center;margin-top: 10rpx;}
+</style>

+ 9 - 5
pages/task/task.vue

@@ -1,5 +1,6 @@
 <template>
-	<view class="content">
+	<view class="content">
+		<page-meta :page-style="'overflow:'+(show?'hidden':'visible')"></page-meta>
 		<scroll-view scroll-y="true" class="scroll-content" :enhanced="true" :bounces="false" :show-scrollbar="false" @scroll="pageScroll">
 			<view class="task-header">
 				<cu-custom :bgColor="scrollHeight>20?'bg-bluev-head':'none'">
@@ -7,16 +8,13 @@
 				</cu-custom>
 				<view class="sub-name">任务多,变现快!</view>
 				<view class="sub-logo" :style="style"><image src="https://blue-data.oss-cn-guangzhou.aliyuncs.com/star/task-star-small.png" mode="widthFix"></image></view>
-			</view>
-			<view class="fixed-filter" :style="fixedStyle" v-if="scrollHeight>20">
-				<filterMain :data="filterData" @success="searchFilter"></filterMain>
 			</view>
 			<view class="task-content">
 				<view class="home-search">
 					<image src="https://blue-data.oss-cn-guangzhou.aliyuncs.com/star/search-icon.png" mode="widthFix"></image>
 					<input type="text" class="search-input" placeholder="搜一搜最新任务" />
 				</view>
-				<view class="task-status" v-if="scrollHeight<20">
+				<view :class="scrollHeight<20?'task-status':'fixed-filter task-status'" :style="fixedStyle">
 					<filterMain :data="filterData" @success="searchFilter"></filterMain>
 				</view>
 				<view class="star-task-list">
@@ -67,6 +65,7 @@ export default {
 	},
 	data() {
 		return {
+			show:false,
 			style:"",
 			fixedStyle:"",
 			scrollHeight:0,
@@ -197,6 +196,11 @@ export default {
 		pageScroll(event) {
 			var scrollHeight = event.detail.scrollTop;
 			app.scrollHeight = scrollHeight;
+			if (scrollHeight > 20) {
+				app.show = true;
+			} else {
+				app.show = false;
+			}
 		},
 		searchFilter(){
 			

+ 33 - 12
pages/user/user.vue

@@ -11,12 +11,17 @@
 				<view class="user-detail-body">
 					<view class="avatar"><image src="/static/rank/avatar.jpeg" mode="widthFix"></image></view>
 					<view class="detail-info">
-						<view class="name">我是庄小周</view>
+						<view class="name">
+							<view class="text">我是庄小周</view>
+							<view class="level">
+								<image src="/static/mine/level.png"></image>Lv.1
+							</view>
+						</view>
 						<view class="desc">ID:234234234 <text class="cuIcon-copy"></text></view>
 					</view>
 					<view class="detail-status">
-						<!-- <view class="status-btn full"><text class="cuIcon-edit"></text>已签约</view> -->
-						<view class="status-btn"><text class="cuIcon-edit"></text>未签约</view>
+						<!-- <view class="status-btn full"><text class="cuIcon-edit"></text><view class="text">已签约</view></view> -->
+						<view class="status-btn"><text class="cuIcon-edit"></text><view class="text">未签约</view> </view>
 					</view>
 				</view>
 			</view>
@@ -58,7 +63,13 @@
 					<view class="title">我的服务</view>
 					<view class="service-body">
 						<view class="service-item">
-							<view class="icon"><image src="/static/mine/task.png" mode="widthFix"></image></view>
+							<view class="icon">
+								<image src="/static/mine/task.png" mode="widthFix"></image>
+								<view class="dot">
+									<image src="/static/mine/msg-dot.png"></image>
+									<view class="num">5</view>
+								</view>
+							</view>
 							<view class="name">我的任务</view>
 						</view>
 						<view class="service-item">
@@ -75,7 +86,7 @@
 						</view>
 					</view>
 				</view>
-				<view class="user-menu-normal" v-for="(item,indx) in 2" :key="indx">
+				<view class="user-menu-normal">
 					<view class="title">常用功能</view>
 					<view class="normal-body">
 						<view class="normal-item">
@@ -104,7 +115,7 @@
 							<view class="more"><text class="cuIcon-right"></text></view>
 						</view>
 						<view class="normal-item">
-							<view class="icon"><image src="/static/mine/setting.png" mode="widthFix"></image></view>
+							<view class="icon"><image src="/static/mine/common-setting.png" mode="widthFix"></image></view>
 							<view class="name">设置</view>
 							<view class="more"><text class="cuIcon-right"></text></view>
 						</view>
@@ -152,13 +163,18 @@ page{background-color: #f8f8f8;}
 }
 .user-header .header-bg image{width: 100%;}
 
-.user-detail-body{margin: 20rpx;display: flex;align-items: center;position: relative;z-index: 9999;}
+.user-detail-body{margin: 20rpx;display: flex;align-items: center;position: relative;z-index: 99;}
 .user-detail-body .avatar image{width: 100rpx;height: 100rpx;border-radius: 100%;}
 .user-detail-body .detail-info{margin-left: 20rpx;}
-.user-detail-body .detail-info .name{font-family: "bluevBold";color: #000;font-weight: 800;font-size: 28rpx;}
-.user-detail-body .detail-info .desc{color: #666;font-size: 22rpx;margin-top: 10rpx;}
+.user-detail-body .detail-info .name{display: flex;align-items: center;gap: 10rpx;}
+.user-detail-body .detail-info .name .text{font-family: "bluevBold";color: #000;font-weight: 800;font-size: 30rpx;}
+.user-detail-body .detail-info .name .level{display: flex;align-items: center;background: linear-gradient(270deg, #4C4C4C 0%, #7D7D7D 100%);font-size: 22rpx;color: #fff;border-radius: 20rpx;padding: 5rpx 10rpx;gap: 5rpx;}
+.user-detail-body .detail-info .name .level image{width: 30rpx;height: 30rpx;}
+.user-detail-body .detail-info .desc{color: #666;font-size: 24rpx;margin-top: 10rpx;}
+.user-detail-body .detail-info .desc text{font-size: 26rpx;}
 .user-detail-body .detail-status{margin-left: auto;margin-right: 0;} 
-.user-detail-body .detail-status .status-btn {font-size: 24rpx;padding: 10rpx 15rpx;border-radius: 40rpx;display: flex;align-items: center;gap: 10rpx;color: #7F8185;background-color: transparent;}
+.user-detail-body .detail-status .status-btn {font-size: 24rpx;padding: 10rpx 15rpx;border-radius: 40rpx;display: flex;align-items: center;gap: 10rpx;color: #7F8185;background-color: transparent;justify-content: center;}
+.user-detail-body .detail-status .status-btn text{font-size: 28rpx;margin-top: 2rpx;}
 .user-detail-body .detail-status .status-btn.full{background-color: #03BEFF;color: #fff;}
 
 .user-body{margin: 20rpx;}
@@ -182,8 +198,13 @@ page{background-color: #f8f8f8;}
 
 .user-body .user-menu-service{background-color: #fff;border-radius: 20rpx;padding: 28rpx;margin-bottom: 20rpx;}
 .user-body .user-menu-service .title{font-size: 32rpx;color: #000;font-weight: 800;line-height: 2;}
-.user-body .user-menu-service .service-body{display: flex;align-items: flex-start;justify-content: space-between;margin-top: 20rpx;}
-.user-body .user-menu-service .service-body .icon image{width: 72rpx;height: 72rpx;}
+.user-body .user-menu-service .service-body{display: flex;align-items: flex-start;justify-content: space-between;margin-top: 20rpx;}
+.user-body .user-menu-service .service-body .icon {position: relative;}
+.user-body .user-menu-service .service-body .icon image{width: 72rpx;height: 72rpx;}
+.user-body .user-menu-service .service-body .icon .dot{display: flex;align-items: center;width: 51rpx;height: 51rpx;position: absolute;top: -25rpx;right: -45rpx;z-index: 99;justify-content: center;}
+.user-body .user-menu-service .service-body .icon .dot image{width: 51rpx;height: 51rpx;position: absolute;top: 0;right: 0;z-index: 98;}
+.user-body .user-menu-service .service-body .icon .num{color: #fff;position: relative;z-index: 99;font-size: 22rpx;font-weight: 800;font-family: "bluevBold";}
+
 .user-body .user-menu-service .service-body .name{color: #212226;font-size: 28rpx;margin-top: 10rpx;}
 .user-body .user-menu-service .service-body .service-item{display: flex;align-items: center;flex-direction: column;}
 .user-body .user-level-body .level-foot{display: flex;align-items: center;justify-content: space-between;gap: 20rpx;}

+ 180 - 0
static/js/common.js

@@ -0,0 +1,180 @@
+export default {
+	shopId: function() {
+		return "10108575743725";
+	},
+	clearUri(url){
+		var userInfo = uni.getStorageSync("user_data");
+		if(!userInfo){
+			uni.showLoading({
+				title:"加载中..."
+			});
+			setTimeout(function(){
+				uni.hideLoading();
+				uni.$u.route({
+					url:'/pages/user/user',
+					type:"switchTab",
+					animationType:"slide-in-bottom"
+				});
+			},800);
+			return ;
+		}
+		uni.$u.route({
+			url:url,
+			animationType:"slide-in-bottom",
+			animationDuration:300
+		});
+	},
+	jumpUri(url,type){
+		var userInfo = uni.getStorageSync("user_token");
+		if(!userInfo){
+			setTimeout(function(){
+				uni.hideLoading();
+				uni.navigateTo({
+					url:'/pages/auth/login',
+					animationType:"slide-in-bottom"
+				});
+			},800);
+			return false;
+		}
+		if(type == 1 && !userInfo){
+			uni.showToast({
+				title:"请先登录",
+				icon:"none"
+			});
+			return true;
+		}
+		uni.navigateTo({
+			url:url
+		})
+	},
+	//判断是否登录
+	navTo: function(url,param) {
+		uni.$u.route(url,param);
+	},
+	//判断是否登录
+	isLogin: function() {
+		return uni.getStorageSync("user") ? true : false
+	},
+	
+	/**
+	 * 操作成功提示框
+	 * @param {*} msg 
+	 * @param {*} callback 
+	 * @param {*} icon 
+	 */
+	showSuccess(msg,icon,callback){
+		uni.showToast({
+			title: msg,
+			icon: icon?icon:'none',
+			mask: true,
+			duration: 1500,
+			success() {
+				callback && (setTimeout(function() {
+					callback();
+				}, 1500));
+			}
+		});
+	},
+
+	/**
+	 * 操作错误提示
+	 * @param {*} msg 
+	 * @param {*} showCancel 
+	 * @param {*} callback 
+	 * @param {*} title 
+	 */
+	showError(msg,callback,title = "友情提示",showCancel = true){
+		uni.showModal({
+			title: title,
+			content: msg,
+			showCancel: showCancel,
+			success(res) {
+				callback && callback(res);
+			}
+		});
+	},
+	
+	showConfirm(msg,callback,title,yes,cancel){
+		plus.nativeUI.confirm(msg, function(e){
+				callback && callback(e);
+			},
+			{"title":title,"buttons":[yes?yes:"确定",cancel?cancel:"取消"]}
+		);
+	},
+	
+	showSysAlert(msg,callback,title,showCancel){
+		plus.nativeUI.alert(msg,callback,title,showCancel);
+	},
+	showSysMsg(msg){
+		plus.nativeUI.toast(msg);
+	},
+	/**
+	 * loading
+	 * @param {Object} msg
+	 * @param {Object} callback
+	 */
+	loading(msg,callback){
+		uni.showLoading({
+			title:msg ?? '请求中...',
+			mask:true,
+			success(res) {
+				callback && callback(res);
+			}
+		})
+	},
+	hideLoad(){
+		uni.hideLoading();
+	},
+	formatDate(value) {
+		const data = new Date(value);
+		const month = data.getMonth() + 1;
+		const day = data.getDate();
+		const year = data.getFullYear();
+		const hours = data.getHours();
+		const minutes = data.getMinutes();
+		const seconds = data.getSeconds();
+		const formattedTime = `${year}-${month}-${day}`;
+		// const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+		return formattedTime;
+	},
+	formatMoney(num,double = false) {
+		if (num == 0) return "¥0.00";
+		if (!double) {
+			return "¥"+num.toFixed(2);
+		}
+		return "¥"+(num / 100).toFixed(2);
+		// return Intl.NumberFormat("zh-CN", {
+		// 	style: "currency",
+		// 	currency: "CNY",
+		// 	minimumFractionDigits: 2,
+		// 	maximumFractionDigits: 2,
+		// }).format(num / 100);
+	},
+	disFormat(num,num2) {
+		if (!num) return "0";
+		if (!num2) return "0";
+		var total = ((num*100)/(num2*100)*10)
+		return total.toFixed(1)
+	},
+	dateFormat(date, fmt='yyyy-MM-dd hh:mm:ss') {
+		date = new Date(date)
+		var o = {
+			"M+" : date.getMonth()+1,                 //月份
+			"d+" : date.getDate(),                    //日
+			"h+" : date.getHours(),                   //小时
+			"m+" : date.getMinutes(),                 //分
+			"s+" : date.getSeconds(),                 //秒
+			"q+" : Math.floor((date.getMonth()+3)/3), //季度
+			"S"  : date.getMilliseconds()             //毫秒
+		};
+		if(/(y+)/.test(fmt)) {
+			fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length));
+		}
+		for(var k in o) {
+			if(new RegExp("("+ k +")").test(fmt)){
+				fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
+			}
+		}
+		return fmt;
+	}
+}

+ 267 - 0
static/js/form.js

@@ -0,0 +1,267 @@
+/**
+ * 表单验证
+ * @author dingyong
+ * @version 1.4.0
+ **/
+
+const form = {
+	//当出现错误时返回错误消息,否则返回空即为验证通过
+	/*
+	 formData:Object 表单对象。{key:value,key:value},key==rules.name
+	 rules: Array [{name:name,rule:[],msg:[]},{name:name,rule:[],msg:[]}]
+			name:name 属性=> 元素的名称
+			rule:字符串数组 ["required","isMobile","isEmail","isCarNo","isIdCard","isAmount","isNum","isChinese","isEnglish",isEnAndNo","isSpecial","isEmoji",""isDate","isUrl","isSame:key","range:[1,9]","minLength:9","maxLength:Number"]
+			msg:数组 []。 与数组 rule 长度相同,对应的错误提示信息
+	*/
+	validation: function(formData, rules) {
+		for (let item of rules) {
+			let key = item.name;
+			let rule = item.rule;
+			let msgArr = item.msg;
+			if (!key || !rule || rule.length === 0 || !msgArr || msgArr.length === 0) {
+				continue;
+			}
+			for (let i = 0, length = rule.length; i < length; i++) {
+				let ruleItem = rule[i];
+				let msg = msgArr[i];
+				if (!ruleItem || !msg) {
+					continue;
+				}
+				//数据处理
+				let value = null;
+				if (~ruleItem.indexOf(":")) {
+					let temp = ruleItem.split(":");
+					ruleItem = temp[0];
+					value = temp[1];
+				}
+				let isError = false;
+				switch (ruleItem) {
+					case "required":
+						isError = form._isNullOrEmpty(formData[key]);
+						break;
+					case "isMobile":
+						isError = !form._isMobile(formData[key]);
+						break;
+					case "isEmail":
+						isError = !form._isEmail(formData[key]);
+						break;
+					case "isCarNo":
+						isError = !form._isCarNo(formData[key]);
+						break;
+					case "isIdCard":
+						isError = !form._isIdCard(formData[key]);
+						break;
+					case "isAmount":
+						isError = !form._isAmount(formData[key]);
+						break;
+					case "isNum":
+						isError = !form._isNum(formData[key]);
+						break;
+					case "isChinese":
+						isError = !form._isChinese(formData[key]);
+						break;
+					case "isEnglish":
+						isError = !form._isEnglish(formData[key]);
+						break;
+					case "isEnAndNo":
+						isError = !form._isEnAndNo(formData[key]);
+						break;
+					case "isEnOrNo":
+						isError = !form._isEnOrNo(formData[key]);
+						break;
+					case "isSpecial":
+						isError = form._isSpecial(formData[key]);
+						break;
+					case "isEmoji":
+						isError = form._isEmoji(formData[key]);
+						break;
+					case "isDate":
+						isError = !form._isDate(formData[key]);
+						break;
+					case "isUrl":
+						isError = !form._isUrl(formData[key]);
+						break;
+					case "isSame":
+						isError = !form._isSame(formData[key], formData[value]);
+						break;
+					case "range":
+						let range = null;
+						try {
+							range = JSON.parse(value);
+							if (range.length <= 1) {
+								throw new Error("range值传入有误!")
+							}
+						} catch (e) {
+							return "range值传入有误!"
+						}
+						isError = !form._isRange(formData[key], range[0], range[1])
+						break;
+					case "minLength":
+						isError = !form._minLength(formData[key], value)
+						break;
+					case "maxLength":
+						isError = !form._maxLength(formData[key], value)
+						break;
+					default:
+						break;
+				}
+				if (isError) {
+					return msg;
+				}
+			}
+		}
+		return "";
+	},
+	_isNullOrEmpty: function(value) {
+		return (value === null || value === '' || value === undefined) ? true : false;
+	},
+	_isMobile: function(value) {
+		return /^(?:13\d|14\d|15\d|16\d|17\d|18\d|19\d)\d{5}(\d{3}|\*{3})$/.test(value);
+	},
+	_isEmail: function(value) {
+		return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(value);
+	},
+	_isCarNo: function(value) {
+		// 新能源车牌
+		const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
+		// 旧车牌
+		const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
+		if (value.length === 7) {
+			return creg.test(value);
+		} else if (value.length === 8) {
+			return xreg.test(value);
+		} else {
+			return false;
+		}
+	},
+	_isIdCard: function(value) {
+		let idCard = value;
+		if (idCard.length == 15) {
+			return this.__isValidityBrithBy15IdCard;
+		} else if (idCard.length == 18) {
+			let arrIdCard = idCard.split("");
+			if (this.__isValidityBrithBy18IdCard(idCard) && this.__isTrueValidateCodeBy18IdCard(arrIdCard)) {
+				return true;
+			} else {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	},
+	__isTrueValidateCodeBy18IdCard: function(arrIdCard) {
+		let sum = 0;
+		let Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1];
+		let ValideCode = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2];
+		if (arrIdCard[17].toLowerCase() == 'x') {
+			arrIdCard[17] = 10;
+		}
+		for (let i = 0; i < 17; i++) {
+			sum += Wi[i] * arrIdCard[i];
+		}
+		let valCodePosition = sum % 11;
+		if (arrIdCard[17] == ValideCode[valCodePosition]) {
+			return true;
+		} else {
+			return false;
+		}
+	},
+	__isValidityBrithBy18IdCard: function(idCard18) {
+		let year = idCard18.substring(6, 10);
+		let month = idCard18.substring(10, 12);
+		let day = idCard18.substring(12, 14);
+		let temp_date = new Date(year, parseFloat(month) - 1, parseFloat(day));
+		if (temp_date.getFullYear() != parseFloat(year) || temp_date.getMonth() != parseFloat(month) - 1 || temp_date.getDate() !=
+			parseFloat(day)) {
+			return false;
+		} else {
+			return true;
+		}
+	},
+	__isValidityBrithBy15IdCard: function(idCard15) {
+		let year = idCard15.substring(6, 8);
+		let month = idCard15.substring(8, 10);
+		let day = idCard15.substring(10, 12);
+		let temp_date = new Date(year, parseFloat(month) - 1, parseFloat(day));
+
+		if (temp_date.getYear() != parseFloat(year) || temp_date.getMonth() != parseFloat(month) - 1 || temp_date.getDate() !=
+			parseFloat(day)) {
+			return false;
+		} else {
+			return true;
+		}
+	},
+	_isAmount: function(value) {
+		//金额,只允许保留两位小数
+		return /^([0-9]*[.]?[0-9])[0-9]{0,1}$/.test(value);
+	},
+	_isNum: function(value) {
+		//只能为数字
+		return /^[0-9]+$/.test(value);
+	},
+	_isChinese: function(value) {
+		let reg = /.*[\u4e00-\u9fa5]+.*$/;
+		return value !== "" && reg.test(value) && !form._isSpecial(value) && !form._isEmoji(value)
+	},
+	_isEnglish: function(value) {
+		return /^[a-zA-Z]*$/.test(value)
+	},
+	_isEnAndNo: function(value) {
+		//8~20位数字和字母组合
+		return /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/.test(value);
+	},
+	_isEnOrNo: function(value) {
+		//英文或者数字
+		let reg = /.*[\u4e00-\u9fa5]+.*$/;
+		let result = true;
+		if (reg.test(value) || form._isSpecial(value) || form._isEmoji(value)) {
+			result = false
+		}
+		return result
+	},
+	_isSpecial: function(value) {
+		//是否包含特殊字符
+		let regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im,
+			regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]]/im;
+		if (regEn.test(value) || regCn.test(value)) {
+			return true;
+		}
+		return false;
+	},
+	_isEmoji: function(value) {
+		//是否包含表情
+		return /\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g.test(value);
+	},
+	_isDate: function(value) {
+		//2019-10-12
+		const reg =
+			/^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
+		return reg.test(value);
+	},
+	_isUrl: function(value) {
+		return /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(value);
+	},
+	_isSame: function(value1, value2) {
+		return value1 === value2
+	},
+	_isRange: function(value, range1, range2) {
+		if ((!range1 && range1 != 0) && (!range2 && range2 != 0)) {
+			return true;
+		} else if (!range1 && range1 != 0) {
+			return value <= range2
+		} else if (!range2 && range2 != 0) {
+			return value >= range1
+		} else {
+			return value >= range1 && value <= range2
+		}
+	},
+	_minLength: function(value, min) {
+		return value.length >= Number(min)
+	},
+	_maxLength: function(value, max) {
+		return value.length <= Number(max)
+	}
+};
+module.exports = {
+	validation: form.validation
+};

File diff suppressed because it is too large
+ 1 - 0
static/js/pako.es5.min.js


+ 574 - 0
static/js/push.js

@@ -0,0 +1,574 @@
+// tiktok-push.js
+// 抖音小程序 WebSocket 推送模块(完整修复版)
+// 支持频道订阅、心跳保活、自动重连、私有频道认证
+
+// ==================== 辅助函数 ====================
+function getGlobalObject() {
+  if (typeof global !== 'undefined') return global;
+  if (typeof window !== 'undefined') return window;
+  return this;
+}
+
+// ==================== CallbackRegistry ====================
+function CallbackRegistry() {
+  this._callbacks = {};
+}
+
+function prefix(name) {
+  return '_' + name;
+}
+
+CallbackRegistry.prototype.get = function (name) {
+  var key = prefix(name);
+  return this._callbacks[key] || [];
+};
+
+CallbackRegistry.prototype.add = function (name, callback, context) {
+  var key = prefix(name);
+  if (!this._callbacks[key]) {
+    this._callbacks[key] = [];
+  }
+  this._callbacks[key].push({
+    fn: callback,
+    context: context
+  });
+};
+
+CallbackRegistry.prototype.remove = function (name, callback, context) {
+  if (!name && !callback && !context) {
+    this._callbacks = {};
+    return;
+  }
+  var names = name ? [prefix(name)] : Object.keys(this._callbacks);
+  for (var i = 0; i < names.length; i++) {
+    var key = names[i];
+    if (!this._callbacks[key]) continue;
+    if (callback || context) {
+      var filtered = [];
+      for (var j = 0; j < this._callbacks[key].length; j++) {
+        var cb = this._callbacks[key][j];
+        if ((callback && callback !== cb.fn) || (context && context !== cb.context)) {
+          filtered.push(cb);
+        }
+      }
+      if (filtered.length === 0) {
+        delete this._callbacks[key];
+      } else {
+        this._callbacks[key] = filtered;
+      }
+    } else {
+      delete this._callbacks[key];
+    }
+  }
+};
+
+// ==================== Dispatcher ====================
+function Dispatcher(failThrough) {
+  this.callbacks = new CallbackRegistry();
+  this.global_callbacks = [];
+  this.failThrough = failThrough;
+}
+
+Dispatcher.prototype.on = function (eventName, callback, context) {
+  this.callbacks.add(eventName, callback, context);
+  return this;
+};
+
+Dispatcher.prototype.on_global = function (callback) {
+  if (typeof callback === 'function') {
+    this.global_callbacks.push(callback);
+  }
+  return this;
+};
+
+Dispatcher.prototype.off = function (eventName, callback, context) {
+  this.callbacks.remove(eventName, callback, context);
+  return this;
+};
+
+Dispatcher.prototype.emit = function (eventName, data) {
+  // 全局回调
+  if (Array.isArray(this.global_callbacks)) {
+    for (var i = 0; i < this.global_callbacks.length; i++) {
+      var gc = this.global_callbacks[i];
+      if (typeof gc === 'function') {
+        try {
+          gc(eventName, data);
+        } catch (e) {
+          console.error('global callback error:', e);
+        }
+      }
+    }
+  }
+
+  // 获取事件回调列表
+  var callbacks = null;
+  try {
+    callbacks = this.callbacks && this.callbacks.get ? this.callbacks.get(eventName) : null;
+  } catch (e) {
+    console.warn('get callbacks error:', e);
+  }
+
+  if (callbacks && Array.isArray(callbacks) && callbacks.length > 0) {
+    for (var j = 0; j < callbacks.length; j++) {
+      var item = callbacks[j];
+      if (item && typeof item.fn === 'function') {
+        var ctx = item.context || getGlobalObject();
+        try {
+          item.fn.call(ctx, data);
+        } catch (e) {
+          console.error('callback error for event "' + eventName + '":', e);
+        }
+      }
+    }
+  } else if (this.failThrough && typeof this.failThrough === 'function') {
+    this.failThrough(eventName, data);
+  }
+
+  return this;
+};
+
+// ==================== Channel ====================
+function Channel(connection, channel_name) {
+  this.subscribed = false;
+  this.dispatcher = new Dispatcher();
+  this.connection = connection;
+  this.channelName = channel_name;
+  this.subscribeCb = null;
+  this.queue = [];
+
+  // 绑定 dispatcher 方法,确保 this 指向 dispatcher
+  this.on = this.dispatcher.on.bind(this.dispatcher);
+  this.off = this.dispatcher.off.bind(this.dispatcher);
+  this.emit = this.dispatcher.emit.bind(this.dispatcher);
+  this.on_global = this.dispatcher.on_global.bind(this.dispatcher);
+}
+
+Channel.prototype.processSubscribe = function () {
+  if (this.connection.state !== 'connected') {
+    return;
+  }
+  if (this.subscribeCb) {
+    this.subscribeCb();
+  }
+};
+
+Channel.prototype.processQueue = function () {
+  if (this.connection.state !== 'connected' || !this.subscribed) {
+    return;
+  }
+  for (var i = 0; i < this.queue.length; i++) {
+    try {
+      this.queue[i]();
+    } catch (e) {
+      console.error('processQueue error:', e);
+    }
+  }
+  this.queue = [];
+};
+
+Channel.prototype.trigger = function (event, data) {
+  if (event.indexOf('client-') !== 0) {
+    throw new Error("Event '" + event + "' should start with 'client-'");
+  }
+  var self = this;
+  this.queue.push(function () {
+    self.connection.send(JSON.stringify({ event: event, data: data, channel: self.channelName }));
+  });
+  this.processQueue();
+};
+
+// ==================== Connection ====================
+function Connection(options) {
+  this.dispatcher = new Dispatcher();
+
+  // 绑定 dispatcher 方法
+  this.on = this.dispatcher.on.bind(this.dispatcher);
+  this.off = this.dispatcher.off.bind(this.dispatcher);
+  this.emit = this.dispatcher.emit.bind(this.dispatcher);
+  this.on_global = this.dispatcher.on_global.bind(this.dispatcher);
+
+  this.options = options;
+  this.state = 'initialized';
+  this.doNotConnect = 0;
+  this.reconnectInterval = 1000;
+  this.socketTask = null;
+  this.reconnectTimer = 0;
+  this.socket_id = null;
+
+  this.connect();
+}
+
+Connection.prototype.updateNetworkState = function (state) {
+  var old_state = this.state;
+  this.state = state;
+  if (old_state !== state) {
+    this.emit('state_change', { previous: old_state, current: state });
+  }
+};
+
+Connection.prototype.connect = function () {
+  var self = this;
+  this.doNotConnect = 0;
+
+  if (this.state === 'connected') {
+    console.log('networkState is "' + this.state + '" , no need to connect');
+    return;
+  }
+
+  if (this.reconnectTimer) {
+    clearTimeout(this.reconnectTimer);
+    this.reconnectTimer = 0;
+  }
+
+  this.closeAndClean();
+
+  var url = this.options.url;
+  var app_key = this.options.app_key;
+  var fullUrl = url + '/app/' + app_key;
+
+  this.socketTask = tt.connectSocket({
+    url: fullUrl,
+    success: function (res) {
+      console.log('WebSocket connect success, socketTaskId:', res.socketTaskId);
+    },
+    fail: function (err) {
+      console.error('WebSocket connect fail:', err);
+      self.updateNetworkState('disconnected');
+      if (!self.doNotConnect) {
+        self.waitReconnect();
+      }
+      if (self.options.onError) {
+        self.options.onError(err);
+      }
+    }
+  });
+
+  this.socketTask.onOpen(function (res) {
+    self.reconnectInterval = 1000;
+    if (self.doNotConnect) {
+      self.updateNetworkState('disconnected');
+      self.socketTask.close();
+      return;
+    }
+    self.updateNetworkState('connected');
+    if (self.options.onOpen) {
+      self.options.onOpen(res);
+    }
+  });
+
+  this.socketTask.onMessage(function (res) {
+    if (self.options.onMessage) {
+      self.options.onMessage(res);
+    }
+  });
+
+  this.socketTask.onClose(function (res) {
+	  console.log('WebSocket closed, code:', res);
+    self.updateNetworkState('disconnected');
+    if (!self.doNotConnect) {
+      self.waitReconnect();
+    }
+    if (self.options.onClose) {
+      self.options.onClose(res);
+    }
+  });
+
+  this.socketTask.onError(function (res) {
+	  console.log('WebSocket error, code:', res);
+    self.updateNetworkState('disconnected');
+    if (!self.doNotConnect) {
+      self.waitReconnect();
+    }
+    if (self.options.onError) {
+      self.options.onError(res);
+    }
+  });
+
+  this.updateNetworkState('connecting');
+};
+
+Connection.prototype.closeAndClean = function () {
+  if (this.socketTask) {
+    try {
+      this.socketTask.close();
+    } catch (e) {
+      console.warn('closeAndClean error:', e);
+    }
+    this.socketTask = null;
+  }
+  this.updateNetworkState('disconnected');
+};
+
+Connection.prototype.waitReconnect = function () {
+  if (this.state === 'connected' || this.state === 'connecting') {
+    return;
+  }
+  if (!this.doNotConnect) {
+    this.updateNetworkState('connecting');
+    var self = this;
+    if (this.reconnectTimer) {
+      clearTimeout(this.reconnectTimer);
+    }
+    this.reconnectTimer = setTimeout(function () {
+      self.connect();
+    }, this.reconnectInterval);
+    if (this.reconnectInterval < 1000) {
+      this.reconnectInterval = 1000;
+    } else {
+      this.reconnectInterval = Math.min(this.reconnectInterval * 2, 2000);
+    }
+  }
+};
+
+Connection.prototype.send = function (data) {
+  if (this.state !== 'connected') {
+    console.warn('state is "' + this.state + '", cannot send:', data);
+    return;
+  }
+  this.socketTask.send({
+    data: data,
+    fail: function (err) {
+      console.error('send fail:', err);
+    }
+  });
+};
+
+Connection.prototype.close = function () {
+  this.doNotConnect = 1;
+  this.updateNetworkState('disconnected');
+  if (this.socketTask) {
+    this.socketTask.close();
+  }
+};
+
+// ==================== Push 客户端 ====================
+function Push(options) {
+  this.doNotConnect = 0;
+  options = options || {};
+  options.heartbeat = options.heartbeat || 25000;
+  options.pingTimeout = options.pingTimeout || 10000;
+  this.config = options;
+  this.uid = 0;
+  this.channels = {};
+  this.connection = null;
+  this.pingTimeoutTimer = 0;
+  Push.instances.push(this);
+  this.createConnection();
+}
+
+Push.instances = [];
+
+Push.prototype.checkoutPing = function () {
+  var self = this;
+  setTimeout(function () {
+    if (self.connection.state === 'connected') {
+      self.connection.send('hi');
+      if (self.pingTimeoutTimer) {
+        clearTimeout(self.pingTimeoutTimer);
+        self.pingTimeoutTimer = 0;
+      }
+      self.pingTimeoutTimer = setTimeout(function () {
+        self.connection.closeAndClean();
+        if (!self.connection.doNotConnect) {
+          self.connection.waitReconnect();
+        }
+      }, self.config.pingTimeout);
+    }
+  }, this.config.heartbeat);
+};
+
+Push.prototype.channel = function (name) {
+  return this.channels[name];
+};
+
+Push.prototype.allChannels = function () {
+  return this.channels;
+};
+
+Push.prototype.createConnection = function () {
+  if (this.connection) {
+    throw new Error('Connection already exists');
+  }
+
+  var self = this;
+  var url = this.config.url;
+  var app_key = this.config.app_key;
+
+  function updateSubscribed() {
+    for (var i in self.channels) {
+      if (self.channels[i]) {
+        self.channels[i].subscribed = false;
+      }
+    }
+  }
+
+  this.connection = new Connection({
+    url: url,
+    app_key: app_key,
+    onOpen: function () {
+      self.connection.state = 'connected';
+      self.checkoutPing();
+    },
+    onMessage: function (res) {
+      if (self.pingTimeoutTimer) {
+        clearTimeout(self.pingTimeoutTimer);
+        self.pingTimeoutTimer = 0;
+      }
+
+      var params;
+      try {
+        params = JSON.parse(res.data);
+      } catch (e) {
+        console.warn('parse message error:', e, res.data);
+        return;
+      }
+
+      var event = params.event;
+      var channel_name = params.channel;
+
+      if (event === 'pusher:pong') {
+        self.checkoutPing();
+        return;
+      }
+      if (event === 'pusher:error') {
+        throw new Error(params.data && params.data.message);
+      }
+
+      var data = params.data;
+      var channel;
+
+      if (event === 'pusher_internal:subscription_succeeded') {
+        channel = self.channels[channel_name];
+        if (channel) {
+          channel.subscribed = true;
+          channel.processQueue();
+          channel.emit('pusher:subscription_succeeded');
+        }
+        return;
+      }
+
+      if (event === 'pusher:connection_established') {
+        self.connection.socket_id = data.socket_id;
+        self.connection.state = 'connected';
+        self.subscribeAll();
+        return;
+      }
+
+      if (event && event.indexOf('pusher_internal') !== -1) {
+        console.log('Event "' + event + '" not implemented');
+        return;
+      }
+
+      channel = self.channels[channel_name];
+      if (channel) {
+        if (typeof data === 'string') {
+          try {
+            data = JSON.parse(data);
+          } catch (e) {}
+        }
+        channel.emit(event, data);
+      }
+    },
+    onClose: function () {
+      updateSubscribed();
+    },
+    onError: function () {
+      updateSubscribed();
+    }
+  });
+};
+
+Push.prototype.disconnect = function () {
+  this.connection.doNotConnect = 1;
+  this.connection.close();
+};
+
+Push.prototype.subscribeAll = function () {
+  if (this.connection.state !== 'connected') {
+    return;
+  }
+  for (var channel_name in this.channels) {
+    if (this.channels[channel_name]) {
+      this.channels[channel_name].processSubscribe();
+    }
+  }
+};
+
+Push.prototype.unsubscribe = function (channel_name) {
+  if (this.channels[channel_name]) {
+    delete this.channels[channel_name];
+    if (this.connection.state === 'connected') {
+      this.connection.send(JSON.stringify({ event: 'pusher:unsubscribe', data: { channel: channel_name } }));
+    }
+  }
+};
+
+Push.prototype.unsubscribeAll = function () {
+  for (var channel_name in this.channels) {
+    this.unsubscribe(channel_name);
+  }
+  this.channels = {};
+};
+
+Push.prototype.subscribe = function (channel_name) {
+  if (this.channels[channel_name]) {
+    return this.channels[channel_name];
+  }
+  if (channel_name.indexOf('private-') === 0) {
+    return createPrivateChannel(channel_name, this);
+  }
+  if (channel_name.indexOf('presence-') === 0) {
+    return createPresenceChannel(channel_name, this);
+  }
+  return createChannel(channel_name, this);
+};
+
+// ==================== 频道创建辅助函数 ====================
+function createChannel(channel_name, push) {
+  var channel = new Channel(push.connection, channel_name);
+  push.channels[channel_name] = channel;
+  channel.subscribeCb = function () {
+    push.connection.send(JSON.stringify({ event: 'pusher:subscribe', data: { channel: channel_name } }));
+  };
+  channel.processSubscribe();
+  return channel;
+}
+
+function createPrivateChannel(channel_name, push) {
+  var channel = new Channel(push.connection, channel_name);
+  push.channels[channel_name] = channel;
+  channel.subscribeCb = function () {
+    tt.request({
+      url: push.config.auth,
+      method: 'POST',
+      data: { channel_name: channel_name, socket_id: push.connection.socket_id },
+      success: function (res) {
+        var data;
+        try {
+          data = res.data;
+        } catch (e) {
+          console.error('parse auth response error:', e);
+          return;
+        }
+        data.channel = channel_name;
+        push.connection.send(JSON.stringify({ event: 'pusher:subscribe', data: data }));
+      },
+      fail: function (err) {
+        console.error('auth request fail:', err);
+        throw new Error(err.errMsg);
+      }
+    });
+  };
+  channel.processSubscribe();
+  return channel;
+}
+
+function createPresenceChannel(channel_name, push) {
+  return createPrivateChannel(channel_name, push);
+}
+
+// ==================== 导出 ====================
+module.exports = {
+  Push: Push
+};

+ 245 - 0
static/js/request.js

@@ -0,0 +1,245 @@
+/**
+ * 常用方法封装 请求,文件上传等
+ * @author echo. 
+ **/
+ 
+ import pako from './pako.es5.min.js'
+
+const axg = {
+	//接口地址
+	interfaceUrl: function() {
+		// #ifdef H5
+		return '/api';
+		// #endif
+		// #ifdef MP
+		return 'http://192.168.2.183:18900/v2'
+		// #endif
+	},
+	toast: function(text, duration, success) { 
+		// #ifdef APP-PLUS
+		plus.nativeUI.toast(text || "出错啦~");
+		// #endif
+		// #ifndef MP
+		uni.showToast({
+			title: text || "出错啦~",
+			icon: success ? 'success' : 'none',
+			duration: duration || 2000
+		})
+		// #endif
+	},
+	modal: function(title, content, showCancel, callback, confirmColor, confirmText) {
+		uni.showModal({
+			title: title || '提示',
+			content: content,
+			showCancel: showCancel,
+			cancelColor: "#555",
+			confirmColor: confirmColor || "#5677fc",
+			confirmText: confirmText || "确定",
+			success(res) {
+				if (res.confirm) {
+					callback && callback(true)
+				} else {
+					callback && callback(false)
+				}
+			}
+		})
+	},
+	delayed: null,
+	decompress(str) {
+		try {
+			return pako.inflateRaw(axg.base64ToUint8Array(str), {
+				to: 'string'
+			});
+		} catch (error) {
+			console.error('解压数据失败:', error);
+			throw error;
+		}
+	},
+	base64ToUint8Array(base64String) {
+	  let padding = '='.repeat((4 - base64String.length % 4) % 4);
+	  let base64 = (base64String + padding)
+	    .replace(/\-/g, '+')
+	    .replace(/_/g, '/');
+	  let rawData = axg.atob(base64);
+	  let outputArray = new Uint8Array(rawData.length);
+	  for (let i = 0; i < rawData.length; ++i) {
+	    outputArray[i] = rawData.charCodeAt(i);
+	  }
+	  return outputArray;
+	},
+	atob(input) {
+	  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+	  let str = input.replace(/=+$/, '');
+	  let output = '';
+	  if (str.length % 4 === 1) {
+	    throw new Error('InvalidLengthError');
+	  }
+	  for (let i = 0, len = str.length; i < len; i += 4) {
+	    const a = chars.indexOf(str.charAt(i));
+	    const b = chars.indexOf(str.charAt(i + 1));
+	    const c = chars.indexOf(str.charAt(i + 2));
+	    const d = chars.indexOf(str.charAt(i + 3));
+	    const sum = (a << 18) | (b << 12) | (c << 6) | d;
+	    output += String.fromCharCode((sum >> 16) & 0xFF, (sum >> 8) & 0xFF, sum & 0xFF);
+	  }
+	 
+	  return output;
+	},
+	/**
+	 * 请求数据处理
+	 * @param string url 请求地址
+	 * @param string method 请求方式
+	 *  GET or POST
+	 * @param {*} postData 请求参数
+	 * @param bool isDelay 是否延迟显示loading
+	 * @param bool isForm 数据格式
+	 *  true: 'application/x-www-form-urlencoded'
+	 *  false:'application/json'
+	 * @param bool hideLoading 是否隐藏loading
+	 *  true: 隐藏
+	 *  false:显示
+	 */
+	request: function(url, method, postData, isDelay, isForm, hideLoading) {
+		//接口请求
+		let loadding = false;
+		axg.delayed && uni.hideLoading();
+		clearTimeout(axg.delayed);
+		axg.delayed = null;
+		if (!hideLoading) {
+			axg.delayed = setTimeout(() => {
+				uni.showLoading({
+					mask: true,
+					title: '请求中...',
+					success(res) {
+						loadding = true
+					}
+				})
+			}, isDelay ? 1000 : 0)
+		}
+		var header = {
+			'Content-Type': isForm ? 'application/x-www-form-urlencoded' : 'application/json',
+			'authorization': "Bearer "+axg.getToken(),
+			'api-type': "mini"
+		}
+		return new Promise((resolve, reject) => {
+			uni.request({
+				url: axg.interfaceUrl() + url,
+				data: postData,
+				header: header,
+				method: method, //'GET','POST'
+				dataType: 'json',
+				success: (res) => {
+					clearTimeout(axg.delayed)
+					axg.delayed = null;
+					uni.hideLoading()
+					if (loadding && !hideLoading) {
+						uni.hideLoading()
+					}
+					if (res.data && res.data.code == 401) {
+						uni.showToast({
+							title: res.data.msg,
+							icon: 'none',
+							mask: true,
+							duration: 1500,
+							success() {
+								uni.clearStorageSync();
+								uni.navigateTo({
+									url:"/pages/auth/login"
+								});
+							}
+						});
+						return ;
+					}
+					if (res.data && res.data.encode) {
+						try
+						{
+							res.data = JSON.parse(axg.decompress(res.data.data));
+						}catch(e){
+							res.data = axg.decompress(decodeURI(res.data.data));
+						}
+					}
+					resolve(res.data)
+				},
+				fail: (res) => {
+					clearTimeout(axg.delayed)
+					axg.delayed = null;
+					axg.toast("网络不给力,请稍后再试~")
+					reject(res)
+				}
+			})
+		})
+	},
+	/**
+	 * 上传文件
+	 * @param string url 请求地址
+	 * @param string src 文件路径
+	 */
+	uploadFile: function(url, src, param) {
+		return new Promise((resolve, reject) => {
+			const uploadTask = uni.uploadFile({
+				url: axg.interfaceUrl() + url,
+				filePath: src,
+				name: 'file',
+				header: {
+					'authorization': "Bearer "+axg.getToken(),
+					'platform': "mini"
+				},
+				formData:param,
+				success: function(res) {
+					uni.hideLoading()
+					console.log(res)
+					let d = JSON.parse(res.data.replace(/\ufeff/g, "") || "{}")
+					if (res.statusCode == 200) {
+						//返回图片地址
+						let fileObj = d.data;
+						resolve(res.data)
+					} else {
+						axg.toast("系统响应错误");
+					}
+				},
+				fail: function(res) {
+					console.log('上传错误:',res)
+					reject(res)
+					axg.toast(res.msg);
+				}
+			})
+		})
+	},
+	tuiJsonp: function(url, callback, callbackname) {
+		// #ifdef H5
+		window[callbackname] = callback;
+		let tuiScript = document.createElement("script");
+		tuiScript.src = url;
+		tuiScript.type = "text/javascript";
+		document.head.appendChild(tuiScript);
+		document.head.removeChild(tuiScript);
+		// #endif
+	},
+	//设置用户信息
+	setUserInfo: function(mobile, token) {
+		//uni.setStorageSync("thorui_token", token)
+		uni.setStorageSync("user_token", mobile)
+	},
+	//获取token
+	getToken() {
+		return uni.getStorageSync("user_token") || 'None'
+	},
+	//判断是否登录
+	isLogin: function() {
+		return uni.getStorageSync("user_token") ? true : false
+	},
+	//跳转页面,校验登录状态
+	href(url, isVerify) {
+		if (isVerify && !axg.isLogin()) {
+			uni.navigateTo({
+				url: '/pages/user/login/login'
+			})
+		} else {
+			uni.navigateTo({
+				url: url
+			});
+		}
+	}
+}
+
+export default axg

BIN
static/mine/level.png


BIN
static/mine/msg-dot.png


BIN
static/task/detail-bg.png


+ 1 - 0
static/task/star.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1783508854180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3655" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M212.16 449.568l161.696 138.048-47.072 216.448 186.08-110.336 186.144 110.272-47.2-216.384 161.568-138.08-205.824-23.68-94.72-197.792-94.72 197.824-205.92 23.68zM512 95.968c3.808 0 7.584 2.24 9.024 6.592l129.6 270.784 288.8 33.184c9.184 0 12.992 11.744 5.568 17.152L720 616l65.184 299.52a9.504 9.504 0 0 1-14.592 10.656L512 772.896l-258.592 153.28a9.536 9.536 0 0 1-14.592-10.624L304 616l-224.992-192.32a9.472 9.472 0 0 1 5.6-17.152l288.736-33.184 129.6-270.784A9.344 9.344 0 0 1 512 96z" fill="#181818" p-id="3656"></path></svg>

+ 1 - 0
static/task/star_filled.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1783508850719" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3490" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M512 96c3.808 0 7.584 2.176 8.992 6.56l129.664 270.784 288.736 33.184c9.216 0 13.024 11.744 5.6 17.152L720 616l65.184 299.52a9.536 9.536 0 0 1-9.056 12.48 9.344 9.344 0 0 1-5.536-1.856L512 772.928l-258.592 153.216a9.344 9.344 0 0 1-5.536 1.856 9.536 9.536 0 0 1-9.056-12.48L304 616l-224.992-192.32a9.472 9.472 0 0 1 5.6-17.152l288.736-33.184 129.632-270.784A9.312 9.312 0 0 1 512 96" fill="#181818" p-id="3491"></path></svg>

+ 0 - 1
utils/css/main.css

@@ -3989,7 +3989,6 @@ scroll-view.cu-steps .cu-item {
 	font-weight: normal;
 	color: #666;
 	font-size: 24rpx;
-	margin-right: 5rpx;
 }
 .star-task-list .task-item .item-info .item-tags{
 	display: flex;

Some files were not shown because too many files changed in this diff