wzx 1 year ago
parent
commit
2dba5cf984
41 changed files with 10771 additions and 35 deletions
  1. 4 0
      card/App.vue
  2. 16 4
      card/common/api.js
  3. 311 0
      card/common/cardfunc.js
  4. 8 6
      card/common/tools.js
  5. 7 5
      card/components/my-pathList/my-pathList.vue
  6. 3 4
      card/components/my-tab/my-tab.vue
  7. 19 2
      card/components/my-topbar/my-topbar.vue
  8. 15 0
      card/main.js
  9. 2 2
      card/manifest.json
  10. 88 4
      card/pages.json
  11. 1 1
      card/pages/achievement/ecert/ecert.vue
  12. 3 0
      card/pages/bm/style1/signup.vue
  13. 4 1
      card/pages/bm/style2/signup.vue
  14. 4 1
      card/pages/bm/style3/signup.vue
  15. 4 4
      card/pages/bm/style4/index.vue
  16. 4 1
      card/pages/bm/style4/signup.vue
  17. 254 0
      card/pages/exchange/style1/cardconfig/test.js
  18. 466 0
      card/pages/exchange/style1/goodsDetail.vue
  19. 457 0
      card/pages/exchange/style1/goodsList.vue
  20. 219 0
      card/pages/tpl/style1/cardconfig/test.js
  21. 396 0
      card/pages/tpl/style1/index.vue
  22. 650 0
      card/pages/tpl/style1/rankList.vue
  23. 515 0
      card/pages/tpl/style1/rankOverview.vue
  24. 552 0
      card/pages/tpl/style1/signup.vue
  25. 263 0
      card/pages/tpl/style2/cardconfig/test.js
  26. 402 0
      card/pages/tpl/style2/index.vue
  27. 922 0
      card/pages/tpl/style2/rankList.vue
  28. 910 0
      card/pages/tpl/style2/rankOverview.vue
  29. 706 0
      card/pages/tpl/style2/signup.vue
  30. 259 0
      card/pages/tpl/style3/cardconfig/test.js
  31. 402 0
      card/pages/tpl/style3/index.vue
  32. 922 0
      card/pages/tpl/style3/rankList.vue
  33. 910 0
      card/pages/tpl/style3/rankOverview.vue
  34. 706 0
      card/pages/tpl/style3/signup.vue
  35. BIN
      card/static/exchange/score.png
  36. BIN
      card/static/exchange/score2.png
  37. BIN
      card/static/exchange/top_right.png
  38. 39 0
      card/uni_modules/uni-number-box/changelog.md
  39. 232 0
      card/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue
  40. 83 0
      card/uni_modules/uni-number-box/package.json
  41. 13 0
      card/uni_modules/uni-number-box/readme.md

+ 4 - 0
card/App.vue

@@ -79,6 +79,10 @@
 		align-items: flex-end;
 	}
 	
+	.uni-aibl {
+		align-items: baseline;
+	}
+	
 	.uni-hidden {
 		visibility: hidden;
 	}

+ 16 - 4
card/common/api.js

@@ -3,8 +3,8 @@ export const apiServer = process.env.API_BASE_URL;
 // console.log("ossUrl", ossUrl);
 // console.log("apiServer", apiServer);
 
-export const token = '';
-// export const token = '96ba3c924394934f7d30fa869a94ce0d';
+// export const token = '';
+export const token = '96ba3c924394934f7d30fa869a94ce0d';
 // export const token = 'd4dd6b57a15b4abaccf6cb6adcd4fd44';
 
 // 卡片基本信息查询
@@ -76,12 +76,24 @@ export const apiUserBaseQueryInCertificate = apiServer + 'UserBaseQueryInCertifi
 // 根据成就信息确认生成电子证书
 export const apiCertificateCreateByUserAi = apiServer + 'CertificateCreateByUserAi';
 
+// 卡片内可用积分查询
+export const apiOnlineScoreQuery = apiServer + 'OnlineScoreQuery';
+
+// 积分可兑换商品列表查询
+export const apiCanExchangeGoodsList = apiServer + 'CanExchangeGoodsList';
+
+// 积分可兑换商品详情
+export const apiCanExchangeGoodsDetail = apiServer + 'CanExchangeGoodsDetail';
+
+// 积分兑换商品
+export const apiScoreExchangeGoods = apiServer + 'ScoreExchangeGoods';
+
 
 
 import tools from '/common/tools';
 
 // 检测request的返回值
-export function checkResCode(res) {
+export function checkResCode(res, failLabel='') {
 	if (res.data.code == 0) {
 		return true;
 	} else if (res.statusCode == 401) { // 未登录
@@ -96,7 +108,7 @@ export function checkResCode(res) {
 		return false;
 	} else {
 		uni.showToast({
-			title: `${res.data.message}`,
+			title: `${failLabel}${res.data.message}`,
 			icon: 'none',
 			duration: 3000
 		});

+ 311 - 0
card/common/cardfunc.js

@@ -0,0 +1,311 @@
+import tools from '/common/tools';
+import {
+	apiCardConfigQuery,
+	apiWarnMessageQuery,
+	apiUnReadMessageQuery,
+	checkResCode
+} from '/common/api';
+
+import {
+	defaultPopUpDataList,
+	defaultPopUpDataList2
+} from '/common/define';
+
+var cardfunc = {
+	caller: null,
+	token: "",
+	ecId: 0, // 卡片id
+	
+	cardConfigData: {
+		tabActiveColor: "#81cd00",
+
+		popupRuleConfig: {}, // 规则弹窗配置
+		popupRuleList: [], // 规则弹窗数据
+
+		popupExchgConfig: {}, // 兑换地址弹窗配置
+		popupExchgList: [], // 兑换地址弹窗数据
+
+		popupHelpConfig: {}, // 帮助弹窗配置
+		popupHelpList: [],
+
+		popupMessageConfig: {}, // 通知弹窗配置
+		popupMessageList: [], // 通知弹窗数据
+
+		popupWarnConfig: {}, // 警告弹窗配置
+		popupWarnList: [], // 警告弹窗数据
+	},
+	
+	init(caller, token, ecId) {
+		this.caller = caller;
+		this.token = token;
+		this.ecId = ecId;
+		this.removeCss();
+	},
+			
+	// 清除css
+	removeCss() {
+		tools.removeCssCode("css-common");
+		tools.removeCssCode("css-custom");
+	},
+
+	getCardConfig(loadConfig, testconfig) {
+		const cardconfigType = getApp().$cardconfigType;
+		// console.log("[getConfig] cardconfigType:", cardconfigType);
+
+		if (cardconfigType == "local") {
+			loadConfig(testconfig);
+			// this.testCardConfig(testconfig, loadConfig);
+		} else {
+			this.cardConfigQuery(loadConfig);
+		}
+	},
+
+	parseCardConfig(cardconfig) {
+		// console.log("[parseCardConfig] cardconfig:", cardconfig);
+		if (cardconfig == undefined || cardconfig == "") {
+			return;
+		}
+		
+		if (typeof cardconfig == "string") {
+			cardconfig = cardconfig.replace(/[\r|\n|\t]/g, "");
+			const config = JSON.parse(cardconfig);
+			// console.log("[parseCardConfig] config:", config);
+			return config;
+		} else {
+			return cardconfig;
+		}
+	},
+
+	// 加载卡片通用配置
+	loadCardCommonConfig(config_common) {
+		// console.log("[loadCardCommonConfig] config_common:", config_common);
+		config_common = this.parseCardConfig(config_common);
+		if (config_common == undefined || config_common == "") {
+			return;
+		}
+
+		if (config_common.css != undefined && config_common.css.length > 0) {
+			tools.loadCssCode(config_common.css, "css-common");
+		}
+
+		if (config_common.tabActiveColor != undefined && config_common.tabActiveColor.length > 0) {
+			this.cardConfigData.tabActiveColor = config_common.tabActiveColor;
+		}
+
+		// 加载规则弹窗配置
+		if (config_common.popupRuleConfig != undefined) {
+			this.cardConfigData.popupRuleConfig = config_common.popupRuleConfig;
+		}
+
+		// 加载帮助弹窗配置
+		if (config_common.popupHelpConfig != undefined) {
+			this.cardConfigData.popupHelpConfig = config_common.popupHelpConfig;
+		}
+
+		// 加载警告弹窗配置
+		if (config_common.popupWarnConfig != undefined) {
+			this.cardConfigData.popupWarnConfig = config_common.popupWarnConfig;
+		}
+
+		// 加载兑换地址弹窗配置
+		if (config_common.popupExchgConfig != undefined) {
+			this.cardConfigData.popupExchgConfig = config_common.popupExchgConfig;
+		}
+
+		// 加载通知弹窗配置
+		if (config_common.popupMessageConfig != undefined) {
+			this.cardConfigData.popupMessageConfig = config_common.popupMessageConfig;
+		}
+
+		// 加载弹窗(规则)数据
+		const popupRuleList = config_common.popupRuleList;
+		// console.log("[loadCardCommonConfig] popupRuleList:", popupRuleList);
+		if (popupRuleList != undefined && popupRuleList.length > 0) {
+			this.cardConfigData.popupRuleList.length = 0;
+			for (var i = 0; i < popupRuleList.length; i++) {
+				// console.log("[loadCardCommonConfig] popupRuleList", i, popupRuleList[i]);
+				if (popupRuleList[i] == 'default') {
+					for (var j = 0; j < defaultPopUpDataList.length; j++) {
+						this.cardConfigData.popupRuleList.push(defaultPopUpDataList[j]);
+					}
+				} else if (popupRuleList[i] == 'default2') {
+					for (var j = 0; j < defaultPopUpDataList2.length; j++) {
+						this.cardConfigData.popupRuleList.push(defaultPopUpDataList2[j]);
+					}
+				} else {
+					this.cardConfigData.popupRuleList.push(popupRuleList[i]);
+				}
+			}
+		} else {
+			this.cardConfigData.popupRuleList = defaultPopUpDataList2;
+			// console.log("[loadCardCommonConfig] popupRuleList 加载默认列表");
+		}
+
+		// 加载弹窗(兑换地址)数据
+		const popupExchgList = config_common.popupExchgList;
+		if (popupExchgList != undefined && popupExchgList.length > 0) {
+			this.cardConfigData.popupExchgList.length = 0;
+			for (var i = 0; i < popupExchgList.length; i++) {
+				// console.log("[loadCardCommonConfig] popupExchgList", i, popupExchgList[i]);
+				this.cardConfigData.popupExchgList.push(popupExchgList[i]);
+			}
+		}
+
+		// 加载弹窗(帮助)数据
+		const popupHelpList = config_common.popupHelpList;
+		if (popupHelpList != undefined && popupHelpList.length > 0) {
+			this.cardConfigData.popupHelpList.length = 0;
+			for (var i = 0; i < popupHelpList.length; i++) {
+				// console.log("[loadCardCommonConfig] popupHelpList", i, popupHelpList[i]);
+				this.cardConfigData.popupHelpList.push(popupHelpList[i]);
+			}
+		}
+		// console.log("[loadCardCommonConfig] cardConfigData:", this.cardConfigData);
+	},
+
+	// 卡片配置信息查询
+	cardConfigQuery(callback) {
+		uni.request({
+			url: apiCardConfigQuery,
+			header: {
+				"Content-Type": "application/x-www-form-urlencoded",
+				"token": this.token,
+			},
+			method: "POST",
+			data: {
+				ecId: this.ecId,
+				pageName: "all"
+			},
+			success: (res) => {
+				// console.log("[cardConfigQuery]", res);
+				const data = res.data.data;
+				const config = data.configJson;
+				// console.log("[cardConfigQuery] config", config);
+				callback(config);
+			},
+			fail: (err) => {
+				console.log("[cardConfigQuery] err", err)
+			},
+		});
+	},
+
+	// 警告列表查询
+	warnMessageQuery(callback=null) {
+		uni.request({
+			url: apiWarnMessageQuery,
+			header: {
+				"Content-Type": "application/x-www-form-urlencoded",
+				"token": this.token,
+			},
+			method: "POST",
+			data: {
+				ecId: this.ecId
+			},
+			success: (res) => {
+				// console.log("warnMessageQuery", res);
+				if (checkResCode(res)) {
+					const warnRs = res.data.data;
+					this.cardConfigData.popupWarnList.length = 0;
+					for (var i = 0; i < warnRs.length; i++) {
+						let popupData = {
+							type: 9, // 9: 警告
+							data: {}
+						};
+						popupData.data.warnType = warnRs[i].warnType;
+						popupData.data.title = warnRs[i].warnTitle;
+						popupData.data.iconUrl = warnRs[i].iconUrl;
+						popupData.data.iconNum = warnRs[i].iconNum;
+						popupData.data.message = warnRs[i].warnMessage;
+						popupData.data.qrCodeUrl = warnRs[i].qrCodeUrl;
+						this.cardConfigData.popupWarnList.push(popupData);
+					}
+
+					/* this.cardConfigData.popupWarnList.push(
+						{
+							type: 9, // 9: 警告
+							data: {
+								warnType: 1,
+								title: "黄牌",
+								iconUrl: "/static/common/card_yellows.png",
+								iconNum: 1,
+								message: `亲爱的参赛者:
+  	收到此黄牌,说明您的比赛数据被系统判定为存在异常,此次比赛(活动)为徒步定向校园文化主题活动,请自觉遵守规则,如果您收到的黄牌数量过多<span style='color: red'>(超过2张)</span>,您的成绩将影响到您的院系/单位成绩,同时您的个人成绩也有可能根据规则被取消。如果您坚持您的比赛数据没有问题,请联系我们的客服人员,谢谢!
+  让我们一起创造文明、和谐的校园生活,感谢您的支持!`,
+								qrCodeUrl: "https://orienteering.beswell.com/shanda/%E8%AD%A6%E5%91%8A%E4%BA%8C%E7%BB%B4%E7%A0%81%402x.png"
+								}
+						}
+					); */
+					// console.log("popupWarnList", this.cardConfigData.popupWarnList);
+					// console.log("caller.popupWarnList length:", this.caller.cardConfigData.popupWarnList.length);
+					
+					if (callback != null) {
+						callback(this.cardConfigData.popupWarnList);
+					}
+				}
+			},
+			fail: (err) => {
+				console.log("warnMessageQuery err", err)
+			},
+		});
+	},
+	
+	// 未读消息列表查询
+	unReadMessageQuery(callback=null) {
+		uni.request({
+			url: apiUnReadMessageQuery,
+			header: {
+				"Content-Type": "application/x-www-form-urlencoded",
+				"token": this.token,
+			},
+			method: "POST",
+			data: {
+				relationType: 2, // 类型 1 成就 2 卡片
+				relationId: this.ecId
+			},
+			success: (res) => {
+				// console.log("getUnReadMessageQuery", res);
+				if (checkResCode(res)) {
+					const unReadMessageRs = res.data.data;
+					this.cardConfigData.popupMessageList.length = 0;
+					let mqIdListStr = "";
+					for (var i = 0; i < unReadMessageRs.length; i++) {
+						let popupData = {
+							type: 6, // 6: 通知
+							data: {}
+						};
+						mqIdListStr += "-" + unReadMessageRs[i].mqId;
+						popupData.data.mqType = unReadMessageRs[i].mqType;
+						popupData.data.title = unReadMessageRs[i].mqTitle;
+						popupData.data.message = unReadMessageRs[i].mqMessage;
+						this.cardConfigData.popupMessageList.push(popupData);
+					}
+					
+					/* this.cardConfigData.popupMessageList.push(
+						{
+							type: 6,	// 6: 通知
+							data: {
+								mqType: 3,
+								title: "特别提醒",
+								message: `本次比赛的目的为体验校园文化,提升身体素质,让大家感受和熟悉定向运动魅力及技巧。<br>
+								在此特别提醒:<br>
+								无论是驰骋校园,还是漫步秋色,<b>“勿以轮带步,唯愿步量途”。让我们用脚步来丈量这片共同热爱的家园</b>,见证山大123周年的辉煌时刻!<br>
+								<div style='text-align: right;'>山东大学体育委员会</div>`
+								}
+						}
+					); */
+					// console.log("popupMessageList", this.cardConfigData.popupMessageList);
+	
+					if (callback != null) {
+						callback(this.cardConfigData.popupMessageList, mqIdListStr);
+					}
+				}
+			},
+			fail: (err) => {
+				console.log("getUnReadMessageQuery err", err);
+			},
+		});
+	},
+
+}
+
+export default cardfunc;

+ 8 - 6
card/common/tools.js

@@ -36,6 +36,8 @@ var tools = {
 		
 		if (url.indexOf('http') !== -1) {	// http 或 https 开头的网址
 			window.location.href = this.urlAddVer(url);
+		} else if (url == "reload") {
+			window.location.reload();
 		} else if (actType == "uni.navigateTo") {
 			uni.navigateTo({
 				url: this.urlAddVer(url)
@@ -120,10 +122,10 @@ var tools = {
 	},
 
 	// 动态创建<style>标签,将CSS代码插入到文档中
-	loadCssCode(cssCode) {
-		this.removeCssCode();
+	loadCssCode(cssCode, styleId="css-custom") {
+		this.removeCssCode(styleId);
 		
-		const styleId = "css-custom";
+		// const styleId = "css-custom";
 		var style = window.document.createElement("style");
 		style.type = "text/css";
 		style.id = styleId;
@@ -140,13 +142,13 @@ var tools = {
 	},
 	
 	// 删除之前动态创建的<style>标签
-	removeCssCode() {
-		const styleId = "css-custom";
+	removeCssCode(styleId="css-custom") {
+		// const styleId = "css-custom";
 		var oldCss = document.getElementById(styleId);
 		// console.log("oldCss:", oldCss);
 		if (oldCss != null) {
 			document.getElementsByTagName("head")[0].removeChild(oldCss);
-			console.log("oldCss 已移除");
+			console.log(styleId + " 已移除");
 		}
 	},
 

+ 7 - 5
card/components/my-pathList/my-pathList.vue

@@ -12,7 +12,7 @@
 				<view class="uni-column">
 					<image mode="aspectFit" class="navimg" :src="item.navImg" @click="onPathImgClick(item, 'nav')">
 					</image>
-					<text class="navtext">(导航)</text>
+					<text class="navtext">导航前往</text>
 				</view>
 			</view>
 			<view v-if="item.type == 4" class="path-nav uni-column">
@@ -24,7 +24,7 @@
 				<view class="uni-column">
 					<image mode="aspectFit" class="navimg2" :src="item.navImg" @click="onPathImgClick(item, 'nav')">
 					</image>
-					<text class="navtext">(导航)</text>
+					<text class="navtext">导航前往</text>
 				</view>
 			</view>
 		</template>
@@ -153,8 +153,8 @@
 	}
 	
 	.navimg {
-		width: 25px;
-		height: 25px;
+		width: 30px;
+		height: 30px;
 	}
 
 	.navimg2 {
@@ -165,9 +165,11 @@
 	
 	.navtext {
 		font-weight: 500;
-		color: #aaaaaa;
+		/* color: #aaaaaa; */
+		color: #CE0202;
 		font-size: 10px;
 		font-family: Source Han Sans CN;
+		white-space: nowrap;
 	}
 
 	.main-path {

+ 3 - 4
card/components/my-tab/my-tab.vue

@@ -187,7 +187,9 @@
 	}
 	
 	/deep/ .e-select-input-text {
+		width: 90% !important;
 		color: inherit !important;
+		font-size: 14px !important;
 	}
 	
 	/deep/ .e-select-selector-item {
@@ -204,13 +206,10 @@
 	
 	/deep/ .e-select-input-placeholder {
 		color: inherit !important;
+		font-size: 14px !important;
 		line-height: 60rpx !important;
 	}
 	
-	/deep/ .e-select-input-text {
-		width: 90% !important;
-	}
-	
 	/deep/ .e-select-icon {
 		/* width: 26px !important; */
 		width: 10% !important;

+ 19 - 2
card/components/my-topbar/my-topbar.vue

@@ -4,10 +4,11 @@
 			<uni-icons type="left" class="topbar-ico topbar-ico-back" @click="btnBack"></uni-icons>
 			<text v-if="showMessage" class="topbar-rule"></text>
 		</view>
-		<text class="mcName" v-html="mcName"></text>
+		<text v-if="mcName.length > 0" class="mcName" v-html="mcName"></text>
+		<text v-if="exchangeTitle.length > 0" class="exchangeTitle" v-html="exchangeTitle"></text>
 		<view class="topbar-left-right uni-row uni-jce">
 			<uni-icons v-if="showMessage" type="notification-filled" class="topbar-ico topbar-ico-message" @click="btnMessage"></uni-icons>
-			<text v-if="showRule" class="topbar-rule" @click="btnInfo">说明</text>
+			<text v-if="showRule" class="topbar-rule" @click="btnInfo" v-html="ruleLable"></text>
 		</view>
 	</view>
 </template>
@@ -20,6 +21,14 @@
 				type: String,
 				default: ""
 			},
+			exchangeTitle: {
+				type: String,
+				default: ""
+			},
+			ruleLable: {
+				type: String,
+				default: "说明"
+			},
 			showMessage: {
 				type: Boolean,
 				default: false
@@ -95,5 +104,13 @@
 		overflow: hidden;
 		/* text-overflow: ellipsis; */
 	}
+	
+	.exchangeTitle {
+		font-size: 22px;
+		font-weight: 550;
+		color: #A65600;
+		white-space: nowrap;
+		overflow: hidden;
+	}
 	
 </style>

+ 15 - 0
card/main.js

@@ -4,8 +4,15 @@ import App from './App'
 // #ifndef VUE3
 import Vue from 'vue'
 import './uni.promisify.adaptor'
+
 Vue.config.productionTip = false
+
+// 卡片配置来源 server:服务器获取 local:本地获取
+// Vue.prototype.$cardconfigType = "server";
+Vue.prototype.$cardconfigType = "local";
+
 // Vue.prototype.$audio = audio;
+
 App.mpType = 'app'
 const app = new Vue({
   ...App
@@ -13,11 +20,19 @@ const app = new Vue({
 app.$mount()
 // #endif
 
+
 // #ifdef VUE3
 import { createSSRApp } from 'vue'
+
 export function createApp() {
   const app = createSSRApp(App)
+  
+  // 卡片配置来源 server:服务器获取 local:本地获取
+  app.config.globalProperties.$cardconfigType = "server";
+  // app.config.globalProperties.$cardconfigType = "local";
+  
   // app.config.globalProperties.$audio = audio;
+  
   return {
     app
   }

+ 2 - 2
card/manifest.json

@@ -2,8 +2,8 @@
     "name" : "card",
     "appid" : "__UNI__A61F96B",
     "description" : "",
-    "versionName" : "1.8.9",
-    "versionCode" : 189,
+    "versionName" : "2.0.0",
+    "versionCode" : 200,
     "transformPx" : false,
     /* 5+App特有相关 */
     "app-plus" : {

+ 88 - 4
card/pages.json

@@ -18,6 +18,25 @@
 				"navigationBarTitleText": "成就 v2"
 			}
 		},
+		{
+			"path" : "pages/achievement/ecert/ecert",
+			"style" : 
+			{
+				"navigationBarTitleText" : "电子证书"
+			}
+		},
+		{
+			"path": "pages/exchange/style1/goodsList",
+			"style": {
+				"navigationBarTitleText": "[商品兑换] 样式1 - 商品列表"
+			}
+		},
+		{
+			"path": "pages/exchange/style1/goodsDetail",
+			"style": {
+				"navigationBarTitleText": "[商品兑换] 样式1 - 商品详情"
+			}
+		},
 		{
 			"path": "pages/mytz/index",
 			"style": {
@@ -133,10 +152,75 @@
 			}
 		},
 		{
-			"path" : "pages/achievement/ecert/ecert",
-			"style" : 
-			{
-				"navigationBarTitleText" : "电子证书"
+			"path": "pages/tpl/style1/index",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式1"
+			}
+		},
+		{
+			"path": "pages/tpl/style1/signup",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式1 - 报名"
+			}
+		},
+		{
+			"path": "pages/tpl/style1/rankList",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式1 - 排名列表"
+			}
+		},
+		{
+			"path": "pages/tpl/style1/rankOverview",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式1 - 排名总览"
+			}
+		},
+		{
+			"path": "pages/tpl/style2/index",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式2"
+			}
+		},
+		{
+			"path": "pages/tpl/style2/signup",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式2 - 报名"
+			}
+		},
+		{
+			"path": "pages/tpl/style2/rankList",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式2 - 排名列表"
+			}
+		},
+		{
+			"path": "pages/tpl/style2/rankOverview",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式2 - 排名总览"
+			}
+		},
+		{
+			"path": "pages/tpl/style3/index",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式3"
+			}
+		},
+		{
+			"path": "pages/tpl/style3/signup",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式3 - 报名"
+			}
+		},
+		{
+			"path": "pages/tpl/style3/rankList",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式3 - 排名列表"
+			}
+		},
+		{
+			"path": "pages/tpl/style3/rankOverview",
+			"style": {
+				"navigationBarTitleText": "[模板] 样式3 - 排名总览"
 			}
 		}
 	],

+ 1 - 1
card/pages/achievement/ecert/ecert.vue

@@ -539,7 +539,7 @@ https://oss-mbh5.colormaprun.com/card/#/pages/achievement/ecert/ecert
 	}
 </script>
 
-<style>
+<style scoped>
 	.main {
 		width: 100vw;
 		height: 100vh;

+ 3 - 0
card/pages/bm/style1/signup.vue

@@ -530,6 +530,9 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style1/signup
 				}
 
 			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
 			btnSignup() {
 				if (!checkToken(this.token)) {
 					return;

+ 4 - 1
card/pages/bm/style2/signup.vue

@@ -16,7 +16,7 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style2/signup
 					<text class="acttime">{{acttime}}</text>
 				</view>
 
-				<input class="uni-input" maxlength="12" placeholder="请填写昵称或姓名" v-model="nickName" />
+				<input class="uni-input" maxlength="12" placeholder="请填写昵称或姓名" v-model="nickName" @click="nickNameClick" />
 
 				<view class="introduce uni-column">
 					<text class="introduce-title">{{introduce.title}}</text>
@@ -516,6 +516,9 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style2/signup
 			btnInfo() {
 				this.$refs.mypopup.popupOpen();
 			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
 			btnSignup() {
 				if (!checkToken(this.token)) {
 					return;

+ 4 - 1
card/pages/bm/style3/signup.vue

@@ -16,7 +16,7 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style3/signup
 					<text class="acttime">{{acttime}}</text>
 				</view>
 
-				<input class="uni-input" maxlength="12" placeholder="请填写昵称或姓名" v-model="nickName" />
+				<input class="uni-input" maxlength="12" placeholder="请填写昵称或姓名" v-model="nickName" @click="nickNameClick" />
 
 				<view class="introduce uni-column">
 					<text class="introduce-title">{{introduce.title}}</text>
@@ -580,6 +580,9 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style3/signup
 			btnInfo() {
 				this.$refs.mypopup.popupOpen();
 			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
 			btnSignup() {
 				if (!checkToken(this.token)) {
 					return;

+ 4 - 4
card/pages/bm/style4/index.vue

@@ -6,13 +6,13 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style4/index
 <template>
 	<view class="body body-radius">
 		<view v-if="pageReady" class="content" :class="cssContentBg" @click="btnClick">
-			<view class="top uni-row">
+			<view class="card-top uni-row">
 				<view class="top-right uni-row">
 					<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
 					<view class="countdown">{{countdown}}</view>
 				</view>
 			</view>
-			<view class="main uni-column">
+			<view class="card-main uni-column">
 				<!-- <image mode="aspectFit" :class="cssLogo" :src="logoSrc"></image> -->
 				<view :class="cssLogo"></view>
 				<view class="uni-row" style="position: relative;">
@@ -392,7 +392,7 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style4/index
 		/* background: linear-gradient(180deg, #7aedff 0%, #8d2219 100%); */
 	}
 	
-	.top {
+	.card-top {
 		width: 100%;
 		justify-content: flex-end;
 	}
@@ -423,7 +423,7 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style4/index
 		/* letter-spacing: 2rpx; */
 	}
 
-	.main {
+	.card-main {
 		width: 100%;
 		/* height: 700rpx; */
 		height: 660rpx;

+ 4 - 1
card/pages/bm/style4/signup.vue

@@ -23,7 +23,7 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style4/signup
 			</view>
 
 			<view class="main uni-column uni-jct">
-				<input class="uni-input" maxlength="12" :placeholder="'请填写'+configParam.labelName" placeholder-style="font-size: 14px;" v-model="nickName" />
+				<input class="uni-input" maxlength="12" :placeholder="'请填写'+configParam.labelName" placeholder-style="font-size: 14px;" v-model="nickName" @click="nickNameClick" />
 				<e-select v-model="coiId" :options="coiRs" :props="eSelectionProps" clearable
 					maxHeight="40vh" :placeholder="'请选择'+configParam.labelOrg+'(可输入关键字)'" @getText="getESelectText"
 					@change="eSelectChange"></e-select>
@@ -674,6 +674,9 @@ https://oss-mbh5.colormaprun.com/card/#/pages/bm/style4/signup
 			btnInfo() {
 				this.$refs.mypopup.popupOpen();
 			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
 			btnSignup() {
 				if (!checkToken(this.token)) {
 					return;

+ 254 - 0
card/pages/exchange/style1/cardconfig/test.js

@@ -0,0 +1,254 @@
+export const localCardConfig = `{
+	"common": {
+		"css": "
+			.color-main {
+				color: #ff870e !important;
+			}
+			.bgcolor-main {
+				background-color: #ff870e !important;
+			}
+			.swiper-item-button {
+				background-color: #ff870e !important;
+			}
+			.uni-swiper-dot-active {
+				background: #ff870e !important;
+			}
+			.topbar-color {
+				color: #ffffff !important;
+			}
+			.topbar-rule {
+				color: #FFFFFF;
+				border-radius: 4px;
+				background: #FF870D;
+			}
+			.page-top {
+				height: 220px !important;
+				background-image: url('static/backgroud/top_bg_sddx.png') !important;
+			}
+		",
+		"tabActiveColor": "#FF870D",
+		"popupRuleConfig": {
+			"height": "530px"
+		},
+		"popupRuleList": [{
+				"type": 1,
+				"data": {
+					"title": "小飞龙定向赛",
+					"img": "/static/common/egg.png",
+					"content": "济南奥体中心“一场三馆”包括体育场、体育馆、网球馆和游泳馆,呈现出“东荷西柳”的总体布局。 体育场以济南的“市树”柳树为母题,将垂柳柔美飘逸的形态固化为建筑语言。"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "规则",
+					"img": "/static/common/guize.png"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "奖励",
+					"img": "/static/common/jiangli.png"
+				}
+			},
+			"default2"
+		],
+		"popupExchgConfig": {
+			"height": "460px"
+		},
+		"popupExchgList": [
+			{
+				"type": 5,
+				"data": {
+					"title": "兑换地点1",
+					"img": "/static/common/gtgwcs.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "冠同购物超市"
+					}
+				}
+			},
+			{
+				"type": 5,
+				"data": {
+					"title": "兑换地点2",
+					"img": "/static/common/wslgwcs2.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "万盛隆购物超市"
+					}
+				}
+			}
+		],
+		"popupMessageConfig": {
+			"height": "500px"
+		},
+		"popupWarnConfig": {
+			"height": "550px"
+		},
+		"popupHelpConfig": {
+			"height": "539px"
+		},
+		"popupHelpList": [
+		]
+	},
+	"goodsList": {
+		"css": "
+			.topbar-rule {
+				color: #000000;
+				background: none;
+			}
+		"
+	},
+	"goodsDetail": {
+		"css": "
+			.topbar-rule {
+				color: #000000;
+				background: none;
+			}
+		"
+	},
+	"index": {
+		"css": "
+			.content-bg{
+				background: url('static/cardbg/xfl5.png') !important;
+				background-size: cover !important;
+			}
+			.logo{
+				width: 40vw !important;
+				height: 40vw !important;
+				background: url('static/logo/xfl2.png') no-repeat center !important;
+				background-size: contain !important;
+			}
+			.mod-text{
+				color: #000000 !important;
+			}
+			.mod-button{
+				color: #ffffff !important;
+				background-color: #9d4f00 !important;
+			}
+		"
+	},
+	"signup": {
+		"css": "
+			.page-top {
+				height: 200px !important;
+			}
+		",
+		"introduce": {
+			"title": "介绍:",
+			"content": " · 小飞龙定向赛再次来袭!这次有五个场地哟~<br> · 神秘“蛋叔”闪亮登场~<br> · 蛋叔放大招,百味豆换鸡蛋!<br> · 时不可待!冲鸭!<br><br> · 能不能把蛋叔整郁闷,就看你的啦~<br> · 还等啥?火速报名吧!"
+		},
+		"activityRules": {
+			"title": "活动规则:",
+			"content": "<li>随时参赛、不限完赛次数、起点任选、实时排名 <li>起点 -各途经点 -结束点完整打卡为一次有效完赛"
+		}
+	},
+	"rankList": {
+		"css": "
+			.topbtm-name{
+				background-color: #c77f34 !important;
+				color: #ffffff !important;
+			}
+			.topbtm-egg{
+				background-color: #c6690a !important;
+				color: #ffffff !important;
+			}
+			.main-bar{
+				background-color: #FFEDDB !important;
+				color: #ff870d !important;
+			}
+		"
+	},
+	"rankOverview": {
+		"css": "
+			.page-top {
+				height: 230px !important;
+			}
+			.mid {
+				margin-top: -50px !important;
+			}
+		",
+		"pathList": {
+			"row1": [{
+					"type": 3,
+					"pathImg": "/static/common/lingxiucheng.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "领秀城起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/quanchenggongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "泉城公园起始点"
+					}
+				}
+			],
+			"row2": [{
+					"type": 3,
+					"pathImg": "/static/common/baihuagongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "百花公园起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/aotizhongxin.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "奥体中心起始点"
+					}
+				}
+			],
+			"row3": [{
+				"type": 3,
+				"pathImg": "/static/common/muniushan.png",
+				"path": {
+					"ocaId": 1,
+					"mcType": 3
+				},
+				"navImg": "/static/common/nav.png",
+				"point": {
+					"longitude": 117.022194,
+					"latitude": 36.661612,
+					"name": "牧牛山起始点"
+				}
+			}]
+		},
+		"pathListStyle" : {
+			"showLine" : true,
+			"style": "justify-content: flex-start;"
+		}
+	}
+}`;

+ 466 - 0
card/pages/exchange/style1/goodsDetail.vue

@@ -0,0 +1,466 @@
+<!-- 
+[商品兑换] 样式1 - 商品详情
+http://localhost:5173/card/#/pages/exchange/style1/goodsDetail
+https://oss-mbh5.colormaprun.com/card/#/pages/exchange/style1/goodsDetail
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column exchg-top">
+				<my-topbar :exchangeTitle="exchangeTitle" ruleLable="兑换地点" class="exchgbar-color" @btnBackClick="btnBack"
+					@btnInfoClick="btnInfo"></my-topbar>
+
+				<view class="top-content">
+					<view class="scoreBox uni-row uni-jcsb">
+						<view class="scoreBox-score uni-row">
+							<view class="scoreBox-title">剩余积分</view>
+							<image class="scoreBox-score-ico" mode="aspectFit" src="/static/exchange/score.png"></image>
+							<view class="scoreBox-score-value">{{scoreRs.score}}</view>
+						</view>
+						<view class="scoreBox-expire">{{fmtTime(scoreRs.extTime)}} 到期</view>
+					</view>
+				</view>
+			</view>
+
+			<view class="main uni-column">
+				<view class="goodsBox uni-column">
+					<view class="goods-title">{{goodsRs.goodsName}}</view>
+					<image class="goods-pic" mode="aspectFit" :src="goodsRs.goodsPic"></image>
+					
+					<view class="goods-priceBox uni-row uni-jcsb">
+						<view class="goods-priceBox-text uni-row">库存 {{goodsRs.goodsLeftNum}}<view v-if="goodsRs.goodsUnit.length > 0">{{goodsRs.goodsUnit}}</view></view>
+						<view class="uni-row">
+							<image class="goods-scoreIco" mode="aspectFit" src="/static/exchange/score2.png"></image>
+							<view class="goods-priceBox-text uni-row">{{goodsRs.corrScore}}积分<view v-if="goodsRs.goodsUnit.length > 0">/{{goodsRs.goodsUnit}}</view></view>
+						</view>
+					</view>
+					
+					<view class="goods-exchNumBox">
+						<uni-number-box :min="1" :max="exchMaxNum" v-model="exchNum"></uni-number-box>
+					</view>
+					
+					<view class="goods-introduce uni-column uni-ais">
+						<view class="goods-introduce-title">商品介绍:</view>
+						<view class="goods-introduce-content" v-html="goodsRs.goodsDesc"></view>
+					</view>
+					
+					<view class="goods-exchDesc" v-html="goodsRs.exchDesc"></view>
+				</view>
+				
+				<button class="btnExchange" @click="btnExchange">兑 换</button>
+			</view>
+			
+			<my-popup ref="mypopupExchg" :config="cardConfigData.popupExchgConfig" :dataList="cardConfigData.popupExchgList"></my-popup>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import {
+		localCardConfig
+	} from "./cardconfig/test.js";
+	import {
+		token,
+		apiOnlineScoreQuery,
+		apiCanExchangeGoodsDetail,
+		apiScoreExchangeGoods,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "goodsDetail",
+				firstEnterKey: 'firstEnter-exchange-style1',
+				queryObj: {},
+				queryString: "",
+				token: "",
+				exchangeTitle: "商品详情",
+
+				ecId: 0, // 卡片id
+				goodsId: 0, // 商品id
+				exchNum: 0, // 兑换数量
+				exchMaxNum: 0, // 个人积分所能兑换的最大数量
+				scoreRs: {
+					score: 260, // 可用积分
+					extTime: 0, // 积分到期时间戳
+				},
+				/* goodsRs: {
+					goodsName: "", // 商品名称
+					goodsPic: "", // 商品图片
+					goodsLeftNum: 0, // 商品剩余数量
+					corrScore: 0, // 兑换所需积分
+					goodsUnit: "", // 商品单位
+					goodsDesc: "", // 商品介绍
+					exchDesc: "", // 兑换说明
+					exchLimit: 0, // 个人兑换数量上限,0无限制
+
+				} */
+				goodsRs: {
+					goodsName: "正宗农家土鸡蛋1枚", // 商品名称
+					goodsPic: "/static/common/jidanquan.png", // 商品图片
+					goodsLeftNum: 9, // 商品剩余数量
+					corrScore: 20, // 兑换所需积分
+					goodsUnit: "个", // 商品单位
+					goodsDesc: "还记得小时候,奶奶亲手做的荷包蛋吗?那金黄的蛋黄,香浓的味道,至今让人回味无穷。我们的土鸡蛋,延续了传统的养殖方式,每一枚鸡蛋都承载着浓浓的乡愁。让您重温儿时的美好记忆。", // 商品介绍
+					exchDesc: "兑换说明:<br>只限本次赛事积分在指定商超兑换。<br>有效期说明:<br>赛事结束后30天内可以兑换,过期失效!", // 兑换说明
+					exchLimit: 3, // 个人兑换数量上限,0无限制
+				}
+			}
+		},
+		computed: {
+		},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			this.goodsId = query["goodsId"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.onlineScoreQuery();
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {},
+		onShow() {},
+		onUnload() {},
+		methods: {
+			// 计算个人积分所能兑换的最大数量
+			computeExchMaxNum() {
+				if(!(this.scoreRs.score > 0 && this.goodsRs.corrScore > 0)) {
+					return;
+				}
+				let maxNum = Math.floor(this.scoreRs.score / this.goodsRs.corrScore);
+				if (this.goodsRs.exchLimit > 0 && maxNum > this.goodsRs.exchLimit) {
+					maxNum = this.goodsRs.exchLimit;
+				}
+				if (maxNum > 0 && this.exchNum == 0) {
+					this.exchNum = 1;
+				}
+				this.exchMaxNum = parseInt(maxNum);
+			},
+			fmtTime(timestamp, type = 3) {
+				return tools.timestampToTime(timestamp * 1000, type);
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				// setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+
+				// -------- 加载当前页面的配置 --------
+
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
+				if (config == undefined || config == null) {
+					this.pageReady = true;
+					return;
+				}
+
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				this.pageReady = true;
+			},
+			// 卡片内可用积分查询
+			onlineScoreQuery() {
+				uni.request({
+					url: apiOnlineScoreQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("onlineScoreQuery", res);
+						if (checkResCode(res)) {
+							this.scoreRs = res.data.data;
+							this.canExchangeGoodsDetail();
+						}
+					},
+					fail: (err) => {
+						console.log("onlineScoreQuery err", err);
+					},
+				});
+			},
+			// 积分可兑换商品详情
+			canExchangeGoodsDetail() {
+				uni.request({
+					url: apiCanExchangeGoodsDetail,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						goodsId: this.goodsId
+					},
+					success: (res) => {
+						console.log("canExchangeGoodsDetail", res);
+						this.goodsRs = res.data.data;
+						this.computeExchMaxNum();
+					},
+					fail: (err) => {
+						console.log("canExchangeGoodsDetail err", err);
+					},
+				});
+			},
+			// 积分兑换商品
+			scoreExchangeGoods() {
+				uni.request({
+					url: apiScoreExchangeGoods,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						goodsId: this.goodsId,
+						exchNum: this.exchNum
+					},
+					success: (res) => {
+						// console.log("scoreExchangeGoods", res);
+						if (checkResCode(res, "兑换失败: ")) {
+							uni.showToast({
+								title: '商品兑换成功!',
+								icon: 'none',
+								duration: 3000
+							});
+							
+							this.onlineScoreQuery();
+						
+							/* setTimeout(() => {
+								// const url = "/pages/exchange/style1/goodsList?" + this.queryString;
+								// tools.appAction(url, "uni.navigateTo");
+								tools.appAction("reload");
+							}, 2000); */
+						}
+					},
+					fail: (err) => {
+						console.log("scoreExchangeGoods err", err);
+					}
+				});
+			},
+			btnExchange() {
+				if (!(this.exchNum > 0)) {
+					uni.showToast({
+						title: '请输入兑换数量',
+						icon: 'none',
+						duration: 2000
+					});
+					return;
+				}
+				
+				let that = this;
+				uni.showModal({
+					title: '兑换',
+					content: `兑换数量:${this.exchNum}   消耗积分:${this.exchNum * this.goodsRs.corrScore}\r\n\r\n您确定要兑换吗?`,
+					confirmText: '确定', //确定文本的文字
+					cancelText: '取消', //确定文本的文字
+					showCancel: true, //没有取消按钮的弹框
+					success: function(res) {
+						if (res.confirm) {
+							that.scoreExchangeGoods();
+						} else if (res.cancel) {
+						}
+					}
+				})
+			},
+			btnBack() {
+				const url = "/pages/exchange/style1/goodsList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnInfo() {
+				this.$refs.mypopupExchg.popupOpen();
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+		background: linear-gradient(119.37deg, #FFEF9E 0%, #FFEEDB 31.6%, #FFF3C2 100%);
+	}
+
+	.exchg-top {
+		width: 100%;
+		/* height: 170px; */
+		padding-top: 36px;
+		justify-content: space-between;
+	}
+	
+	.exchgbar-color {
+		/* color: #000000; */
+	}
+	
+	.main {
+		width: 100%;
+		flex-grow: 1;
+		margin-top: 10px;
+		padding-bottom: 20px;
+		border-radius: 19px 19px 0px 0px;
+		border: 1px solid #FFFFFF;
+		background: #F6F6F6;
+	}
+
+	.top-content {
+		position: relative;
+		width: 90%;
+		height: 30px;
+		margin-top: 10px;
+	}
+
+	.scoreBox {
+		width: 100%;
+		height: 80%;
+		margin-top: 10px;
+		/* background-color: #A65600; */
+	}
+
+	.scoreBox-score {
+		font-size: 12px;
+		font-weight: 500;
+		color: #A65600;
+	}
+
+	.scoreBox-score-ico {
+		width: 19px;
+		height: 15px;
+	}
+
+	.scoreBox-title {
+		margin-right: 10px;
+		font-size: 12px;
+		font-weight: 550;
+		color: #000000;
+	}
+
+	.scoreBox-score-value {
+		margin: 0 6px;
+		font-size: 18px;
+		font-weight: 700;
+	}
+
+	.scoreBox-score-unite {
+		margin-top: 12px;
+	}
+
+	.scoreBox-expire {
+		font-size: 12px;
+		font-weight: 500;
+		color: #A6A6A6;
+	}
+	
+	.goodsBox {
+		width: 90%;
+		margin-top: 20px;
+		/* background-color: #FFEF9E; */
+	}
+
+	.goods-title {
+		font-size: 16px;
+		font-weight: 500;
+	}
+	
+	.goods-pic {
+		width: 90%;
+		height: 160px;
+		margin: 20px 0;
+	}
+	
+	.goods-priceBox {
+		width: 200px;
+		height: 25px;
+		padding: 2px 12px;
+		border-radius: 3px;
+		background: #FFC300;
+	}
+	
+	.goods-scoreIco {
+		width: 22px;
+		height: 14px;
+		margin-right: 3px;
+	}
+	
+	.goods-priceBox-text {
+		font-size: 14px;
+		white-space: nowrap;
+	}
+	
+	.goods-exchNumBox {
+		margin: 20px;
+	}
+	
+	::v-deep .uni-numbox-btns {
+		width: 10px;
+		border: 1px solid #A6A6A6;
+	}
+	
+	.goods-introduce {
+		width: 90%;
+		font-size: 14px;
+		font-weight: 400;
+		line-height: 20px;
+	}
+	
+	.goods-introduce-title {
+		width: 100%;
+		margin-bottom: 10px;
+		text-align: left;
+		font-weight: 550;
+	}
+	
+	.goods-exchDesc {
+		width: 90%;
+		margin-top: 20px;
+		padding: 10px;
+		border-radius: 5px;
+		background: #FFF8E3;
+		font-size: 12px;
+		font-weight: 400;
+		line-height: 20px;
+	}
+	
+	.btnExchange {
+		width: 289px;
+		height: 53px;
+		margin-top: 30px;
+		margin-bottom: 20px;
+		border-radius: 27px;
+		background: linear-gradient(117.53deg, #FFCD29 0%, #FFE694 42.36%, #FFC508 100%);
+		
+		font-size: 18px;
+		font-weight: 550;
+		line-height: 53px;
+		color: #A65600;
+	}
+	
+</style>

+ 457 - 0
card/pages/exchange/style1/goodsList.vue

@@ -0,0 +1,457 @@
+<!-- 
+[商品兑换] 样式1 - 商品列表
+http://localhost:5173/card/#/pages/exchange/style1/goodsList
+https://oss-mbh5.colormaprun.com/card/#/pages/exchange/style1/goodsList
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column exchg-top">
+				<my-topbar :exchangeTitle="exchangeTitle" ruleLable="兑换地点" class="exchgbar-color" @btnBackClick="btnBack"
+					@btnInfoClick="btnInfo"></my-topbar>
+
+				<view class="top-content">
+					<view class="scoreBox-cornerMark">仅限本赛事</view>
+					<view class="scoreBox uni-column uni-ais uni-jcse">
+						<view class="scoreBox-title">剩余积分</view>
+						<view class="scoreBox-score uni-row">
+							<image class="scoreBox-score-ico" mode="aspectFit" src="/static/exchange/score.png"></image>
+							<view class="scoreBox-score-value">{{scoreRs.score}}</view>
+							<!-- <view class="scoreBox-score-unite">分</view> -->
+						</view>
+						<view class="scoreBox-expire">{{fmtTime(scoreRs.extTime)}} 到期</view>
+					</view>
+				</view>
+			</view>
+
+			<view class="main uni-column">
+				<view class="gl-title">兑换商品列表</view>
+				<view class="norecord" v-if="goodsList == null || goodsList.length == 0">暂无记录</view>
+				<view class="list uni-row" v-else>
+					<view class="item uni-column" v-for="(item, index) in goodsList" :key="index"
+						@click="showDetail(item)">
+						<view class="item-picbox">
+							<view class="item-cornerMark">库存 {{fmtStockQty(item.goodsLeftNum)}}</view>
+							<image class="item-pic" mode="aspectFit" :src="item.goodsPic"></image>
+						</view>
+						<view class="item-introduce uni-column uni-jcse uni-ais">
+							<view class="item-introduce-name" style="margin-top: 3rpx;">{{item.goodsName}}</view>
+							<view class="uni-row">
+								<image class="item-introduce-ico" mode="aspectFit" src="/static/exchange/score2.png">
+								</image>
+								<view class="item-introduce-score">-{{item.corrScore}}</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<my-popup ref="mypopupExchg" :config="cardConfigData.popupExchgConfig" :dataList="cardConfigData.popupExchgList"></my-popup>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import {
+		localCardConfig
+	} from "./cardconfig/test.js";
+	import {
+		token,
+		apiOnlineScoreQuery,
+		apiCanExchangeGoodsList,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "goodsList",
+				firstEnterKey: 'firstEnter-exchange-style1',
+				queryObj: {},
+				queryString: "",
+				from: "", // 来源页面
+				token: "",
+				exchangeTitle: "兑换商品",
+
+				ecId: 0, // 卡片id
+				scoreRs: {
+					score: 260, // 可用积分
+					extTime: 0, // 积分到期时间戳
+				},
+				goodsList: [{
+					goodsId: 0, // 商品id
+					goodsName: "", // 商品名称
+					goodsPic: "", // 商品图片
+					goodsLeftNum: 0, // 商品剩余数量
+					corrScore: 0, // 兑换所需积分
+				}],
+				/* goodsList: [
+					{
+						goodsId: 0, // 商品id
+						goodsName: "正宗农家土鸡蛋1枚", // 商品名称
+						goodsPic: "/static/common/jidanquan.png", // 商品图片
+						goodsLeftNum: 99999, // 商品剩余数量
+						corrScore: 20, // 兑换所需积分
+					},
+					{
+						goodsId: 0, // 商品id
+						goodsName: "正宗农家土鸡蛋2222枚", // 商品名称
+						goodsPic: "/static/common/lingxiucheng.png", // 商品图片
+						goodsLeftNum: 60, // 商品剩余数量
+						corrScore: 20, // 兑换所需积分
+					},
+					{
+						goodsId: 0, // 商品id
+						goodsName: "正宗农家土鸡蛋 1 枚", // 商品名称
+						goodsPic: "/static/common/baihuagongyuan.png", // 商品图片
+						goodsLeftNum: 60, // 商品剩余数量
+						corrScore: 20, // 兑换所需积分
+					},
+					{
+						goodsId: 0, // 商品id
+						goodsName: "正宗农家土鸡蛋 正宗农家土鸡蛋 正宗农家土鸡蛋", // 商品名称
+						goodsPic: "/static/common/sdzxxq2.png", // 商品图片
+						goodsLeftNum: 60, // 商品剩余数量
+						corrScore: 20, // 兑换所需积分
+					},
+					{
+						goodsId: 0, // 商品id
+						goodsName: "正宗农家土鸡蛋 1 枚", // 商品名称
+						goodsPic: "/static/common/baihuagongyuan.png", // 商品图片
+						goodsLeftNum: 60, // 商品剩余数量
+						corrScore: 20, // 兑换所需积分
+					},
+					{
+						goodsId: 0, // 商品id
+						goodsName: "正宗农家土鸡蛋 正宗农家土鸡蛋 正宗农家土鸡蛋", // 商品名称
+						goodsPic: "/static/common/sdzxxq2.png", // 商品图片
+						goodsLeftNum: 60, // 商品剩余数量
+						corrScore: 20, // 兑换所需积分
+					},
+				] */
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.from = query["from"] ?? "";
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+
+			this.onlineScoreQuery();
+			this.canExchangeGoodsList();
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {},
+		onShow() {},
+		onUnload() {},
+		methods: {
+			fmtTime(timestamp, type = 3) {
+				return tools.timestampToTime(timestamp * 1000, type);
+			},
+			// 格式化库存数量
+			fmtStockQty(num) {
+				let fmtNum = num;
+				if (num > 99) {
+					fmtNum = "99+";
+				}
+				return fmtNum;
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				// setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+
+				// -------- 加载当前页面的配置 --------
+
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
+				if (config == undefined || config == null) {
+					this.pageReady = true;
+					return;
+				}
+
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				this.pageReady = true;
+			},
+			// 卡片内可用积分查询
+			onlineScoreQuery() {
+				uni.request({
+					url: apiOnlineScoreQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("onlineScoreQuery", res);
+						if (checkResCode(res)) {
+							this.scoreRs = res.data.data;
+						}
+					},
+					fail: (err) => {
+						console.log("onlineScoreQuery err", err);
+					},
+				});
+			},
+			// 积分可兑换商品列表查询
+			canExchangeGoodsList() {
+				uni.request({
+					url: apiCanExchangeGoodsList,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("canExchangeGoodsList", res);
+						this.goodsList = res.data.data;
+					},
+					fail: (err) => {
+						console.log("canExchangeGoodsList err", err);
+					},
+				});
+			},
+			btnBack() {
+				if (this.from != '') {
+					const url = this.from + '?' + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {
+					const url = `action://to_home/`;
+					tools.appAction(url);
+				}
+				// window.history.back();
+			},
+			btnInfo() {
+				this.$refs.mypopupExchg.popupOpen();
+			},
+			showDetail(data) {
+				console.log("showDetail data:", data);
+				this.queryObj.goodsId = data.goodsId;
+				this.queryString = tools.objectToQueryString(this.queryObj);
+				const url = '/pages/exchange/style1/goodsDetail?' + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+		background: linear-gradient(119.37deg, #FFEF9E 0%, #FFEEDB 31.6%, #FFF3C2 100%);
+	}
+
+	.exchg-top {
+		width: 100%;
+		/* height: 170px; */
+		padding-top: 36px;
+		justify-content: space-between;
+	}
+
+	.exchgbar-color {
+		/* color: #000000; */
+	}
+	
+	.main {
+		width: 100%;
+		flex-grow: 1;
+		margin-top: 10px;
+		padding-bottom: 20px;
+		border-radius: 19px 19px 0px 0px;
+		border: 1px solid #FFFFFF;
+		background: #F6F6F6;
+	}
+
+	.top-content {
+		position: relative;
+		width: 90%;
+		height: 90px;
+		margin-top: 10px;
+		/* align-content: center; */
+		border-radius: 8px;
+		background: url('/static/exchange/top_right.png'), linear-gradient(117.53deg, #FFCD29 0%, #FFE694 42.36%, #FFC508 100%);
+		background-repeat: no-repeat;
+		background-position-x: 90%, center;
+		background-position-y: 80%, center;
+		background-size: 89px 53px, 100%;
+	}
+
+	.scoreBox {
+		height: 80%;
+		margin-left: 20px;
+		margin-top: 10px;
+	}
+
+	.scoreBox-cornerMark {
+		position: absolute;
+		right: 0%;
+		width: 60px;
+		height: 17px;
+		opacity: 1;
+		border-radius: 0px 8px 0px 8px;
+		background: #FFFFFF;
+
+		font-size: 10px;
+		font-weight: 500;
+		color: #A65600;
+		text-align: center;
+		line-height: 17px;
+	}
+
+	.scoreBox-title {
+		font-size: 12px;
+		font-weight: 550;
+		color: #000000;
+	}
+
+	.scoreBox-score {
+		font-size: 12px;
+		font-weight: 500;
+		color: #A65600;
+	}
+
+	.scoreBox-score-ico {
+		width: 23px;
+		height: 19px;
+	}
+
+	.scoreBox-score-value {
+		margin: 0 6px;
+		font-size: 32px;
+		font-weight: 700;
+	}
+
+	.scoreBox-score-unite {
+		margin-top: 12px;
+	}
+
+	.scoreBox-expire {
+		font-size: 10px;
+		font-weight: 500;
+		color: #A65600;
+	}
+
+	.gl-title {
+		width: 90%;
+		padding-top: 10px;
+		padding-left: 20px;
+		font-size: 15px;
+		font-weight: 550;
+		text-align: left;
+	}
+
+	.norecord {
+		font-weight: 500;
+		color: #818181;
+		font-size: 14px;
+		text-align: center;
+		line-height: 58vh;
+	}
+
+	.list {
+		width: 90%;
+		flex-wrap: wrap;
+		justify-content: flex-start;
+		/* background-color: #FFCD29; */
+	}
+
+	.item {
+		width: 43.8%;
+		margin: 20rpx 20rpx;
+		border-radius: 6px;
+		background-color: #FFFFFF;
+	}
+
+	.item-picbox {
+		position: relative;
+		width: 100%;
+		background: linear-gradient(180deg, #DBF1FF 0%, #FFFFDB 100%);
+		border-radius: 6px 6px 0px 0px;
+	}
+
+	.item-cornerMark {
+		position: absolute;
+		left: 0%;
+		width: 63px;
+		height: 20px;
+		opacity: 1;
+		background: linear-gradient(90deg, rgb(255, 238, 184, 1) 0%, rgb(255, 239, 186, 0.3) 100%);
+		border-radius: 6px 0px 6px 0px;
+
+		font-size: 12px;
+		font-weight: 400;
+		color: #383838;
+		text-align: center;
+		line-height: 20px;
+		white-space: nowrap;
+	}
+
+	.item-pic {
+		width: 100%;
+		height: 25vw;
+		padding-top: 22px;
+		padding-bottom: 10px;
+	}
+
+	.item-introduce {
+		width: 86%;
+		height: 60px;
+		/* margin-top: 10rpx; */
+	}
+
+	.item-introduce-name {
+		width: 100%;
+		font-size: 14px;
+		font-weight: 500;
+		color: #000;
+		white-space: nowrap;
+		overflow: hidden;
+		/* text-overflow: ellipsis; */
+	}
+
+	.item-introduce-ico {
+		width: 30px;
+		height: 20px;
+	}
+
+	.item-introduce-score {
+		margin: 0 3px;
+		font-size: 16px;
+		font-weight: 500;
+		color: #FF8D1A;
+	}
+</style>

+ 219 - 0
card/pages/tpl/style1/cardconfig/test.js

@@ -0,0 +1,219 @@
+export const localCardConfig = `{
+	"common": {
+		"css": "
+			.color-main {
+				color: #ff870e !important;
+			}
+			.bgcolor-main {
+				background-color: #ff870e !important;
+			}
+			.swiper-item-button {
+				background-color: #ff870e !important;
+			}
+			.uni-swiper-dot-active {
+				background: #ff870e !important;
+			}
+			.topbar-color {
+				color: #ffffff !important;
+			}
+			.topbar-rule {
+				color: #FFFFFF;
+				border-radius: 4px;
+				background: #FF870D;
+			}
+			.page-top {
+				height: 220px !important;
+				background-image: url('static/backgroud/top_bg_sddx.png') !important;
+			}
+		",
+		"tabActiveColor": "#FF870D",
+		"popupRuleConfig": {
+			"height": "530px"
+		},
+		"popupRuleList": [{
+				"type": 1,
+				"data": {
+					"title": "小飞龙定向赛",
+					"img": "/static/common/egg.png",
+					"content": "济南奥体中心“一场三馆”包括体育场、体育馆、网球馆和游泳馆,呈现出“东荷西柳”的总体布局。 体育场以济南的“市树”柳树为母题,将垂柳柔美飘逸的形态固化为建筑语言。"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "规则",
+					"img": "/static/common/guize.png"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "奖励",
+					"img": "/static/common/jiangli.png"
+				}
+			},
+			"default2"
+		],
+		"popupExchgConfig": {
+			"height": "460px"
+		},
+		"popupExchgList": [
+		],
+		"popupMessageConfig": {
+			"height": "500px"
+		},
+		"popupWarnConfig": {
+			"height": "550px"
+		},
+		"popupHelpConfig": {
+			"height": "539px"
+		},
+		"popupHelpList": [
+		]
+	},
+	"index": {
+		"css": "
+			.content-bg{
+				background: url('static/cardbg/xfl5.png') !important;
+				background-size: cover !important;
+			}
+			.logo{
+				width: 40vw !important;
+				height: 40vw !important;
+				background: url('static/logo/xfl2.png') no-repeat center !important;
+				background-size: contain !important;
+			}
+			.mod-text{
+				color: #000000 !important;
+			}
+			.mod-button{
+				color: #ffffff !important;
+				background-color: #9d4f00 !important;
+			}
+		"
+	},
+	"signup": {
+		"css": "
+			.page-top {
+				height: 200px !important;
+			}
+		",
+		"introduce": {
+			"title": "介绍:",
+			"content": " · 小飞龙定向赛再次来袭!这次有五个场地哟~<br> · 神秘“蛋叔”闪亮登场~<br> · 蛋叔放大招,百味豆换鸡蛋!<br> · 时不可待!冲鸭!<br><br> · 能不能把蛋叔整郁闷,就看你的啦~<br> · 还等啥?火速报名吧!"
+		},
+		"activityRules": {
+			"title": "活动规则:",
+			"content": "<li>随时参赛、不限完赛次数、起点任选、实时排名 <li>起点 -各途经点 -结束点完整打卡为一次有效完赛"
+		}
+	},
+	"rankList": {
+		"css": "
+			.topbtm-name{
+				background-color: #c77f34 !important;
+				color: #ffffff !important;
+			}
+			.topbtm-egg{
+				background-color: #c6690a !important;
+				color: #ffffff !important;
+			}
+			.main-bar{
+				background-color: #FFEDDB !important;
+				color: #ff870d !important;
+			}
+		",
+		"param": {
+			"labelTicketName": "蛋叔券",
+			"labelAwardAddress": "兑换地址",
+			"labelGoodsList": ""
+		}
+	},
+	"rankOverview": {
+		"css": "
+			.page-top {
+				height: 230px !important;
+			}
+			.mid {
+				margin-top: -50px !important;
+			}
+		",
+		"pathList": {
+			"row1": [{
+					"type": 3,
+					"pathImg": "/static/common/lingxiucheng.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "领秀城起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/quanchenggongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "泉城公园起始点"
+					}
+				}
+			],
+			"row2": [{
+					"type": 3,
+					"pathImg": "/static/common/baihuagongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "百花公园起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/aotizhongxin.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "奥体中心起始点"
+					}
+				}
+			],
+			"row3": [{
+				"type": 3,
+				"pathImg": "/static/common/muniushan.png",
+				"path": {
+					"ocaId": 1,
+					"mcType": 3
+				},
+				"navImg": "/static/common/nav.png",
+				"point": {
+					"longitude": 117.022194,
+					"latitude": 36.661612,
+					"name": "牧牛山起始点"
+				}
+			}]
+		},
+		"pathListStyle" : {
+			"showLine" : true,
+			"style": "justify-content: flex-start;"
+		}
+	}
+}`;

+ 396 - 0
card/pages/tpl/style1/index.vue

@@ -0,0 +1,396 @@
+<!-- 
+[模板] 样式1 - 卡片页
+http://localhost:5173/card/#/pages/tpl/style1/index
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style1/index
+ -->
+<template>
+	<view class="body body-radius">
+		<view v-if="pageReady" class="content content-bg" @click="btnClick">
+			<view class="card-top uni-row">
+				<view class="top-right uni-row">
+					<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
+					<view class="countdown">{{countdown}}</view>
+				</view>
+			</view>
+			<view class="card-main uni-column">
+				<view class="logo"></view>
+				<view class="uni-row" style="position: relative;">
+					<image v-if="notice" mode="aspectFit" src="/static/common/notice.png" class="notice"></image>
+					<text class="type mod-text">{{type}}</text>
+				</view>
+				<view class="name mod-text">{{ecName}}</view>
+				<button class="button mod-button">{{btnText}}</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '../../../common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		ossUrl,
+		apiCardBaseQuery,
+		apiUserJoinCardQuery,
+		apiMatchRsDetailQuery
+	} from '../../../common/api';
+	
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "index",
+				rankKey: "rank-tpl-style1",
+				queryObj: {},
+				queryString: "",
+				token: "",
+				
+				ecId: 0, // 卡片id
+				ecName: '', // 卡片名称
+				ecDesc: '', // 卡片简介
+				beginSecond: null, // 卡片开始时间戳,单位秒
+				endSecond: null, // 卡片结束时间戳,单位秒
+				secondCardName: '', // 跳转页面名称
+				
+				isJoin: null, // 是否报名
+				isFinished: false, // 赛事是否结束
+				
+				// mcId: 0, // 赛事id
+				// mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				// mcName: "", // 赛事名称
+				
+				countdown: "", // 倒计时
+				interval: null,
+				
+				type: "",
+				btnText: "",
+				notice: false,	// 是否显示(小红点)通知图标
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			
+			this.type = query["type"] ?? "锦标赛";
+			this.btnText = query["btnText"] ?? "开始比赛";
+			
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+			
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.getCardBaseQuery();
+			this.matchRsDetailQuery();
+		},
+		onShow() {
+			this.getUserJoinCardQuery();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealNotice]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							that.notice = true;
+							// that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						that.notice = true;
+						// that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
+				if (config == undefined || config == null) {
+					this.pageReady = true;
+					return;
+				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+				
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = "距结束 " + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "已结束";
+						this.isFinished = true;
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 卡片基本信息查询
+			getCardBaseQuery() {
+				uni.request({
+					url: apiCardBaseQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						pageName: this.pageName
+					},
+					success: (res) => {
+						// console.log("getCardBaseQuery", res)
+						const data = res.data.data;
+						
+						this.ecName = data.ecName;
+						this.ecDesc = data.ecDesc;
+						this.beginSecond = data.beginSecond;
+						this.endSecond = data.endSecond;
+						this.secondCardName = data.secondCardName;
+						
+						this.getCountdown();
+						
+						this.clear();
+						this.interval = setInterval(this.getCountdown, 60000);
+					},
+					fail: (err) => {
+						console.log("getCardBaseQuery err", err)
+					},
+				});
+			},
+			// 用户是否已经报名卡片对应赛事查询
+			getUserJoinCardQuery() {
+				uni.request({
+					url: apiUserJoinCardQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getUserJoinCardQuery", res)
+						const code = res.data.code;
+						const data = res.data.data;
+						if (code == 0) {
+							this.isJoin = data.isJoin;
+							/*if (this.isJoin) {
+								this.btnText = "已报名";
+							} else {
+								this.btnText = "未报名";
+							} */
+						}
+					},
+					fail: (err) => {
+						console.log("getUserJoinCardQuery err", err)
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			btnClick() {
+				if (this.isJoin) {	// 已报名
+					const url = `${ossUrl}#/pages/tpl/style1/rankList?${this.queryString}&full=true`;
+					tools.appAction(url);
+				}
+				else {	// 未报名
+					if (!this.isFinished) {	// 赛事未结束
+						if (this.secondCardName == 'rankList') {
+							const url = `${ossUrl}#/pages/tpl/style1/rankList?${this.queryString}&full=true`;
+							tools.appAction(url);
+						} else {
+							const url = `${ossUrl}#/pages/tpl/style1/signup?${this.queryString}&full=true`;
+							tools.appAction(url);
+						}
+					}
+					else {	// 赛事已结束
+						const url = `${ossUrl}#/pages/tpl/style1/rankList?${this.queryString}&full=true`;
+						tools.appAction(url);
+					}
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+	}
+
+	.content-bg {
+		background: linear-gradient(180deg, #7aedff 0%, #047200 100%);
+		/* background: linear-gradient(180deg, #178bff 0%, #004d9b 100%); */
+		/* background: linear-gradient(180deg, #7aedff 0%, #8d2219 100%); */
+	}
+	
+	.card-top {
+		width: 100%;
+		justify-content: flex-end;
+	}
+
+	.top-right {
+		min-width: 180rpx;
+		height: 80rpx;
+		margin-top: 30rpx;
+		margin-right: 30rpx;
+		padding-left: 16rpx;
+		padding-right: 16rpx;
+		background-color: rgba(0, 0, 0, 0.3);
+		border-radius: 12px;
+	}
+
+	.clock {
+		width: 50rpx;
+		height: 50rpx;
+		margin-right: 12rpx;
+	}
+
+	.countdown {
+		min-width: 120rpx;
+		text-align: center;
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 46rpx;
+		/* letter-spacing: 2rpx; */
+	}
+
+	.card-main {
+		width: 100%;
+		/* height: 700rpx; */
+		height: 660rpx;
+		margin-top: 20rpx;
+		justify-content: space-evenly;
+	}
+
+	.logo {
+		width: 50vw;
+		height: 50vw;
+		background-image: url('/static/logo/jbs.png');
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.notice {
+		width: 30rpx;
+		height: 30rpx;
+		/* margin-right: 30rpx; */
+		position: absolute;
+		left: -60rpx;
+	}
+	
+	.type {
+		opacity: 60%;
+		/* line-height: 25px; */
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 40rpx;
+		text-align: center;
+	}
+
+	.name {
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 50rpx;
+		text-align: center;
+	}
+
+	.button {
+		width: 320rpx;
+		height: 86rpx;
+		margin-top: 30rpx;
+		color: #000000;
+		background: #ffffff;
+		border-radius: 56rpx;
+		font-size: 46rpx;
+		line-height: 80rpx;
+	}
+	
+</style>

+ 650 - 0
card/pages/tpl/style1/rankList.vue

@@ -0,0 +1,650 @@
+<!-- 
+[模板] 样式1 - 排名列表
+http://localhost:5173/card/#/pages/tpl/style1/rankList
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style1/rankList
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color" :showMessage="cardConfigData.popupMessageList.length > 0" 
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo" @btnMessageClick="btnMessage"></my-topbar>
+				
+				<view class="topbtm uni-row">
+					<!-- <view class="topbtm-egg bgcolor-main" @click="btnMyEgg"></view> -->
+					<view class="topbtm-egg" @click="btnMyEgg">{{configParam.labelTicketName}}</view>
+					<text class="topbtm-name bgcolor-main">{{nickName}}</text>
+					<!-- <view class="topbtm-egg bgcolor-main" @click="btnExchg">兑换地址</view> -->
+					<view class="topbtm-egg" v-if="configParam.labelGoodsList && configParam.labelGoodsList.length > 0" @click="btnGoodsList">{{configParam.labelGoodsList}}</view>
+					<view class="topbtm-egg" v-else @click="btnExchg">{{configParam.labelAwardAddress}}</view>
+				</view>
+			</view>
+			<view class="main uni-column">
+				<view class="main-bar uni-row uni-jcse">
+					<text>总里程:{{fmtDistanct(all_totalDistance)}}km</text>
+					<text>总打点数:{{all_totalCp}}个</text>
+					<text>总百味豆数:{{all_totalSysPoint}}个</text>
+				</view>
+				
+				<uni-segmented-control class="main-tab" :current="tabCurrent" :values="tabItems"
+					@clickItem="onClickTabItem" styleType="button" :activeColor="cardConfigData.tabActiveColor"></uni-segmented-control>
+				<view class="tab-view uni-column">
+					<!-- 里程 -->
+					<my-ranklist v-show="tabCurrent === 0" :rankRs="rankList.totalDistanceRs" rank-type="totalDistance"></my-ranklist>
+
+					<!-- 打点数 -->
+					<my-ranklist v-show="tabCurrent === 1" :rankRs="rankList.totalCpRs" rank-type="totalCp"></my-ranklist>
+					
+					<!-- 百味豆 -->
+					<my-ranklist v-show="tabCurrent === 2" :rankRs="rankList.totalSysPointRs" rank-type="totalSysPoint"></my-ranklist>
+					
+					<!-- 配速 -->
+					<my-ranklist v-show="tabCurrent === 3" :rankRs="rankList.fastPaceRs" rank-type="fastPace"></my-ranklist>
+				</view>
+
+				<button class="btnBack bgcolor-main" @click="btnStartGame">{{btnStartGameText}}</button>
+			</view>
+			
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+			<my-popup ref="mypopupExchg" :config="cardConfigData.popupExchgConfig" :dataList="cardConfigData.popupExchgList"></my-popup>
+			<my-popup ref="mypopupMessage" :config="cardConfigData.popupMessageConfig" :dataList="cardConfigData.popupMessageList" @noMoreRemindersClick="onNoMoreRemindersClick"></my-popup>
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import { teamName } from '/common/define';
+	import {
+		token,
+		apiMatchRsDetailQuery,
+		apiCardRankDetailQuery,
+		apiCompStatisticQuery,
+		apiIsAllowMcSignUp,
+		apiUserJoinCardQuery,
+		checkResCode
+	} from '/common/api';
+	
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "rankList",
+				firstEnterKey: 'firstEnter-tpl-style1',
+				rankKey: "rank-tpl-style1",
+				messageKey: "message-tpl-style1",
+				queryObj: {},
+				queryString: "",
+				token: "",
+				ovtype: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				ocaId: 0,	// 关联id,带入到App活动详情页面
+				nickName: "", // 昵称
+				totalNum: null, // 总场次
+				totalDistanct: null, // 总距离,单位米
+				totalDistanctRankNum: null, // 总距离排名
+				totalCp: null, // 总打点数
+				totalCpRankNum: null, // 总打点数排名
+				totalSysPoint: null, // 总百味豆
+				totalSysPointRankNum: null, // 总百味豆排名
+				fastPace: null, // 个人最快配速
+				fastPaceRankNum: null, // 个人最快配速排名
+				// ocaRs: [], // 卡片对应活动集合
+				
+				isJoin: null, // 是否报名
+				btnStartGameText: "",
+				
+				all_totalDistance: 0, // 赛事所有人累计里程,单位米
+				all_totalRightAnswerNum: 0, // 赛事所有人正确答题数(校园文化输出)
+				all_totalAnswerNum: 0, // 赛事所有人答题数(校园文化输出)
+				all_totalCp: 0, // 赛事中所有人打点数
+				all_totalSysPoint: 0, // 赛事中所有人百味豆
+				
+				mcState: 0 ,	// 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				allowMcSignUp: false,	// 是否允许重新分组
+				countdown: "", // 倒计时
+				rankList: { // 排名列表
+					totalDistanceRs: [],
+					totalCpRs: [],
+					totalSysPointRs: [],
+					fastPaceRs: []
+				},
+				interval: null,
+				
+				teamType: 0, // 队伍类型
+				dispArrStr: "totalDistance,totalCp,totalSysPoint,fastPace", // 要显示的集合范围
+				tabItems: ["里程", "打点数", "百味豆", "配速"],
+				tabCurrent: 0,
+				tabActiveColor: "#81cd00",
+				
+				configParam: {
+					labelTicketName: "我的奖券",
+					labelAwardAddress: "兑奖地址",
+					// labelGoodsList: "兑换商品",
+				}
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			this.ovtype = query["ovtype"] ?? "";
+			
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+			
+			this.messageKey += "-" + this.ecId;
+			console.log("messageKey:", this.messageKey);
+			
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.dealOvtype();
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+			// this.$refs.mypopupMessage.popupOpen();
+		},
+		onShow() {
+			this.getUserJoinCardQuery();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealOvtype() {
+				if (this.ovtype == "totalDistance") {
+					this.tabCurrent = 0;
+				} else if (this.ovtype == "totalCp") {
+					this.tabCurrent = 1;
+				} else if (this.ovtype == "totalSysPoint") {
+					this.tabCurrent = 2;
+				} else if (this.ovtype == "fastPace") {
+					this.tabCurrent = 3;
+				}
+				console.log(`dealOvtype: ${this.ovtype} tabCurrent: ${this.tabCurrent}`);
+			},
+			dealNotice(rank) {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				cardfunc.unReadMessageQuery(this.unReadMessageQueryCallback);
+				this.matchRsDetailQuery();
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			unReadMessageQueryCallback(unReadMessage, mqIdListStr) {
+				// console.log("[unReadMessageQueryCallback] unReadMessage", unReadMessage);
+				if (unReadMessage.length > 0) {
+					this.messageKey += mqIdListStr;
+					// console.log("[unReadMessageQueryCallback] messageKey:", this.messageKey);
+					const messageValue = uni.getStorageSync(this.messageKey);
+					// console.log("[unReadMessageQueryCallback] messageValue:", messageValue);
+					if (!messageValue) {
+						this.$refs.mypopupMessage.popupOpen();
+						// uni.setStorageSync(this.messageKey, true);
+					}
+				}
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+				
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.labelTicketName != undefined && param.labelTicketName.length > 0) {
+						this.configParam.labelTicketName = param.labelTicketName;
+					}
+					if (param.labelAwardAddress != undefined && param.labelAwardAddress.length > 0) {
+						this.configParam.labelAwardAddress = param.labelAwardAddress;
+					}
+					if (param.labelGoodsList != undefined && param.labelGoodsList.length > 0) {
+						this.configParam.labelGoodsList = param.labelGoodsList;
+					}
+				}
+				
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = '距结束 ' + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "活动已结束";
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 格式化 距离
+			fmtDistanct(val) {
+				return Math.round(val * 100 / 1000) / 100;
+				/* if (val < 10000)
+					return Math.round(val * 10 / 1000) / 10;
+				else
+					return Math.round(val / 1000); */
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取活动时间
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+				// console.log("acttime", this.acttime);
+			},
+			getTeamName(teamType, teamIndex) {
+				return teamName[teamType][teamIndex];
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (checkResCode(res)) {
+							const data = res.data.data;
+							this.mcType = data.mcType;
+							this.mcId = data.mcId;
+							this.mcName = data.mcName;
+							this.beginSecond = data.beginSecond;
+							this.endSecond = data.endSecond;
+							this.nickName = data.nickName;
+							this.totalNum = data.totalNum;
+							this.totalDistanct = data.totalDistanct;
+							this.totalDistanctRankNum = data.totalDistanctRankNum;
+							this.totalCp = data.totalCp;
+							this.totalCpRankNum = data.totalCpRankNum;
+							this.totalSysPoint = data.totalSysPoint;
+							this.totalSysPointRankNum = data.totalSysPointRankNum;
+							this.fastPace = data.fastPace;
+							this.fastPaceRankNum = data.fastPaceRankNum;
+							// this.ocaRs = data.ocaRs;
+				
+							this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+				
+							this.getCountdown();
+							this.getActtime();
+							this.compStatisticQuery();
+							this.getCardRankDetailQuery();
+				
+							this.clear();
+							this.interval = setInterval(this.getCountdown, 60000);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 排名查询
+			getCardRankDetailQuery() {
+				uni.request({
+					url: apiCardRankDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcIdListStr: this.mcId,
+						mcType: this.mcType,
+						dispArrStr: this.dispArrStr
+					},
+					success: (res) => {
+						// console.log("getCardRankDetailQuery", res);
+						const rankdata = res.data.data;
+						this.rankList.totalDistanceRs = rankdata.totalDistanceRs;
+						this.rankList.totalCpRs = rankdata.totalCpRs;
+						this.rankList.totalSysPointRs = rankdata.totalSysPointRs;
+						this.rankList.fastPaceRs = rankdata.fastPaceRs;
+					},
+					fail: (err) => {
+						console.log("getCardRankDetailQuery err", err);
+					},
+				});
+			},
+			// 赛事总成绩统计查询
+			compStatisticQuery() {
+				uni.request({
+					url: apiCompStatisticQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("compStatisticQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.all_totalDistance = data.totalDistance;
+							this.all_totalRightAnswerNum = data.totalRightAnswerNum;
+							this.all_totalAnswerNum = data.totalAnswerNum;
+							this.all_totalCp = data.totalCp;
+							this.all_totalSysPoint = data.totalSysPoint;
+						}
+					},
+					fail: (err) => {
+						console.log("compStatisticQuery err", err);
+					},
+				});
+			},
+			// 是否允许重新分组(报名)
+			isAllowMcSignUp() {
+				uni.request({
+					url: apiIsAllowMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("isAllowMcSignUp", res)
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.allowMcSignUp = data.allowSignUp;
+						}
+					},
+					fail: (err) => {
+						console.log("isAllowMcSignUp err", err)
+					},
+				});
+			},
+			// 用户是否已经报名卡片对应赛事查询
+			getUserJoinCardQuery() {
+				uni.request({
+					url: apiUserJoinCardQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getUserJoinCardQuery", res)
+						const code = res.data.code;
+						const data = res.data.data;
+						if (code == 0) {
+							this.isJoin = data.isJoin;
+							if (this.isJoin) { // 已报名
+								// this.btnStartGameText = "我要比赛";
+								this.btnStartGameText = "选择场地";
+							} else {	// 未报名
+								this.btnStartGameText = "我要报名";
+							}
+						}
+					},
+					fail: (err) => {
+						console.log("getUserJoinCardQuery err", err)
+					},
+				});
+			},
+			onNoMoreRemindersClick() {
+				this.$refs.mypopupMessage.popupClose();
+				uni.setStorageSync(this.messageKey, true);
+			},
+			btnBack() {
+				const url = `action://to_home/`;
+				tools.appAction(url);
+			},
+			btnStartGame() {
+				if (this.isJoin) {	// 已报名
+					const url = "/pages/tpl/style1/rankOverview?" + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {	// 未报名
+					const url = "/pages/tpl/style1/signup?" + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				}
+			},
+			btnInfo() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopup.popupOpen();
+			},
+			btnMessage() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopupMessage.popupOpen();
+			},
+			btnMyEgg() {
+				const url = "/pages/achievement/index2?tabCurrent=2&" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnExchg() {
+				this.$refs.mypopupExchg.popupOpen();
+			},
+			btnGoodsList() {
+				this.queryObj.from = "/pages/tpl/style1/rankList";
+				this.queryString = tools.objectToQueryString(this.queryObj);
+				const url = "/pages/exchange/style1/goodsList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			onClickTabItem(e) {
+				if (this.tabCurrent != e.currentIndex) {
+					this.tabCurrent = e.currentIndex;
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+	}
+
+	.page-top {
+		width: 100%;
+		height: 170px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url("/static/backgroud/top_bg2.png");
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: cover;
+	}
+
+	.topbar-color {
+		color: #5b9100;
+	}
+	
+	.topbtm {
+		width: 100%;
+		margin-bottom: 5px;
+		justify-content: space-around;
+	}
+	
+	.topbtm-name {
+		max-width: 300rpx;
+		padding: 3px 12px;
+		background-color: #9fda39;
+		border-radius: 5px;
+		text-align: center;
+		font-weight: 500;
+		color: #497400;
+		font-size: 14px;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+	
+	.topbtm-egg {
+		width: 60px;
+		padding: 3px 12px;
+		background-color: #9fda39;
+		border-radius: 50px;
+		text-align: center;
+		color: #497400;
+		font-size: 14px;
+	}
+	
+	.topbtm-null {
+		width: 60px;
+		padding: 3px 12px;
+	}
+	
+	.cal {
+		width: 46rpx;
+		height: 46rpx;
+		margin-right: 20rpx;
+	}
+	
+	.main {
+		width: 100%;
+		flex-grow: 1;
+		justify-content: space-around;
+	}
+	
+	.main-bar {
+		width: 100%;
+		height: 21px;
+		background-color: #d8e8c6;
+		
+		font-size: 10px;
+		font-weight: 500;
+		color: #3d6706;
+	}
+
+	.main-tab {
+		width: 90%;
+		margin-top: 20rpx;
+	}
+
+	.tab-view {
+		width: 100%;
+		flex-grow: 1;
+	}
+
+	.btnBack {
+		width: 70%;
+		height: 80rpx;
+		margin-bottom: 20rpx;
+		color: white;
+		font-size: 32rpx;
+		line-height: 80rpx;
+		border-radius: 27px;
+		background-color: #81cd00;
+	}
+	
+</style>

+ 515 - 0
card/pages/tpl/style1/rankOverview.vue

@@ -0,0 +1,515 @@
+<!-- 
+[模板] 样式1 - 排名总览
+http://localhost:5173/card/#/pages/tpl/style1/rankOverview
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style1/rankOverview
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color"
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo"></my-topbar>
+			</view>
+			
+			<view class="mid uni-column">
+				<view class="mid-1 uni-row uni-jcsb">
+					<text class="mid-1-name">{{nickName}}</text>
+					<text>总场次:{{totalNum}}</text>
+				</view>
+				<view class="mid-2 uni-row uni-jcsa">
+					<view class="uni-column">
+						<text class="mid-2-value" style="color: #ff0045;">{{totalSysPoint}}</text>
+						<text class="mid-2-text">百味豆(个)</text>
+					</view>
+
+					<view class="mid-line"></view>
+
+					<view class="uni-column">
+						<text class="mid-2-value">{{fmtDistanct(totalDistanct)}}</text>
+						<text class="mid-2-text">总里程km</text>
+					</view>
+
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{totalCp}}</text>
+						<text class="mid-2-text">打点数(个)</text>
+					</view>
+
+					<view class="mid-line"></view>
+
+					<view class="uni-column">
+						<text class="mid-2-value">{{fmtPace(fastPace)}}</text>
+						<text class="mid-2-text">最快配速</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="main uni-column">
+				<text class="main-title">选择比赛路线</text>
+				<my-pathList :style="pathListStyle.style" :pathList="pathList" :mcState="mcState" :showLine="pathListStyle.showLine"></my-pathList>
+			</view>
+
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+			<!-- <my-popup-map ref="mypopupmap" :point="navPoint"></my-popup-map> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		apiMatchRsDetailQuery,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				// audioSrc: "/static/audio/2.mp3",
+				// audioSrc: "https://oss-mbh5.colormaprun.com/card/static/audio/2.mp3",
+				// audioSrc: "http://t-oss-mbh5.colormaprun.com/card/static/audio/2.mp3",
+				pageName: "rankOverview",
+				firstEnterKey: 'firstEnter-tpl-style1',
+				rankKey: "rank-tpl-style1",
+				queryObj: {},
+				queryString: "",
+				token: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				nickName: "", // 昵称
+				totalNum: null, // 总场次
+				totalDistanct: null, // 总距离,单位米
+				totalDistanctRankNum: null, // 总距离排名
+				totalCp: null, // 总打点数
+				totalCpRankNum: null, // 总打点数排名
+				totalSysPoint: null, // 总百味豆
+				totalSysPointRankNum: null, // 总百味豆排名
+				fastPace: null, // 个人最快配速
+				fastPaceRankNum: null, // 个人最快配速排名
+				ocaRs: [], // 卡片对应活动集合
+
+				interval: null,
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				pathList: {},
+				pathListStyle: {},
+				navPoint: {},
+			}
+		},
+		computed: {
+			pathListLen() {
+				return Object.keys(this.pathList).length;
+			}
+		},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log("query:", query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+		},
+		onShow() {
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				this.matchRsDetailQuery();
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载比赛路线数据
+				const pathList = config.pathList;
+				// console.log("[loadConfig] pathList:", pathList);
+				if (pathList != undefined) {
+					this.pathList = pathList;
+				}
+				
+				// 加载比赛路线样式
+				const pathListStyle = config.pathListStyle;
+				// console.log("[loadConfig] pathList:", pathList);
+				if (pathListStyle != undefined) {
+					this.pathListStyle = pathListStyle;
+				}
+
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = '距结束 ' + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "活动已结束";
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 格式化 距离
+			fmtDistanct(val) {
+				if (val < 1000)
+					return Math.round(val * 10 / 1000) / 10;
+				else
+					return Math.round(val / 1000);
+			},
+			// 格式化 配速
+			fmtPace(val) {
+				return tools.convertSecondsToHMS(val, 2);
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取活动时间
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (checkResCode(res)) {
+							const data = res.data.data;
+							this.mcType = data.mcType;
+							this.mcId = data.mcId;
+							this.mcName = data.mcName;
+							this.beginSecond = data.beginSecond;
+							this.endSecond = data.endSecond;
+							this.nickName = data.nickName;
+							this.totalNum = data.totalNum;
+							this.totalDistanct = data.totalDistanct;
+							this.totalDistanctRankNum = data.totalDistanctRankNum;
+							this.totalCp = data.totalCp;
+							this.totalCpRankNum = data.totalCpRankNum;
+							this.totalSysPoint = data.totalSysPoint;
+							this.totalSysPointRankNum = data.totalSysPointRankNum;
+							this.fastPace = data.fastPace;
+							this.fastPaceRankNum = data.fastPaceRankNum;
+							this.ocaRs = data.ocaRs;
+
+							this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+
+							this.getCountdown();
+							this.getActtime();
+
+							this.clear();
+							this.interval = setInterval(this.getCountdown, 60000);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			btnBack() {
+				const url = "/pages/tpl/style1/rankList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnInfo() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopup.popupOpen();
+			},
+			onOverviewClick(ovtype) {
+				this.queryObj.ovtype = ovtype;
+				this.queryString = tools.objectToQueryString(this.queryObj);
+				const url = "/pages/tpl/style1/rankList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		/* height: 100vh; */
+		overflow-x: scroll;
+	}
+
+	.page-top {
+		position: relative;
+		z-index: 10;
+		width: 100%;
+		height: 270px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url("/static/backgroud/top_bg_egg2.png");
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		/* background-position-y: -8px; */
+		/* background-size: 100% 100%; */
+		background-size: cover;
+	}
+
+	.topbar-color {
+		color: #333333;
+	}
+
+	.topbtm {
+		width: 100%;
+		margin-bottom: 40px;
+		justify-content: space-evenly;
+	}
+
+	.topbtm-name {
+		padding: 5px 12px;
+		background-color: #9fda39;
+		border-radius: 5px;
+		/* backdrop-filter: blur(30px); */
+		text-align: center;
+		font-weight: 500;
+		color: #497400;
+		font-size: 14px;
+	}
+
+	.mid {
+		width: 90%;
+		height: 120px;
+		position: relative;
+		z-index: 20;
+		margin-top: -100px;
+		background: #ffffff;
+		border-radius: 9px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+		font-family: Source Han Sans CN;
+	}
+
+	.mid-1 {
+		width: 90%;
+		margin: 12px;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.mid-1-name {
+		max-width: 360rpx;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.mid-2 {
+		width: 92%;
+		/* margin: 0 10px; */
+	}
+
+	.mid-2-value {
+		font-weight: 900;
+		font-size: 22px;
+	}
+
+	.mid-2-text {
+		color: #989898;
+		font-size: 12px;
+	}
+
+	.mid-line {
+		width: 0px;
+		height: 45.04px;
+		border: 1px solid;
+		border-color: #e6e6e6;
+	}
+
+	.overview-1 {
+		width: 111px;
+		height: 54px;
+		background: #ffb40b;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-2 {
+		margin-top: -43px;
+		color: #ffffff;
+		font-size: 18px;
+		pointer-events: auto;
+	}
+
+	.overview-3 {
+		width: 111px;
+		height: 54px;
+		background: #f39509;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-4 {
+		width: 111px;
+		height: 54px;
+		background: #81cd00;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-5 {
+		width: 111px;
+		height: 54px;
+		background: #64cbb0;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.ovline1 {
+		margin-top: 9px;
+		color: #ffffff;
+		font-size: 12px;
+	}
+
+	.ovline2 {
+		color: #ffffff;
+		font-size: 16px;
+	}
+
+	.main {
+		width: 100%;
+		margin-top: 20px;
+		margin-bottom: 10px;
+		/* height: 70vh; */
+		justify-content: space-around;
+		/* justify-content: space-between; */
+	}
+
+	.main-title {
+		margin-bottom: 10px;
+		font-weight: 550;
+		color: #333333;
+		font-size: 16px;
+	}
+</style>

+ 552 - 0
card/pages/tpl/style1/signup.vue

@@ -0,0 +1,552 @@
+<!-- 
+[模板] 样式1 - 报名
+http://localhost:5173/card/#/pages/tpl/style1/signup
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style1/signup
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color"
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo"></my-topbar>
+			</view>
+			<view class="main uni-column">
+				<view class="timebar uni-row">
+					<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
+					<text class="acttime">{{acttime}}</text>
+				</view>
+
+				<input class="uni-input" maxlength="12" placeholder="请填写昵称或姓名" v-model="nickName" @click="nickNameClick" />
+
+				<view class="introduce uni-column">
+					<text class="introduce-title">{{introduce.title}}</text>
+					<text class="introduce-content" v-html="introduce.content"></text>
+				</view>
+				
+				<view v-if="activityRules.content.length > 0" class="activityRules uni-column">
+					<text class="activityRules-title">{{activityRules.title}}</text>
+					<text class="activityRules-content" v-html="activityRules.content"></text>
+				</view>
+				
+				<button class="btnSignup bgcolor-main" v-if="mcState<=1" @click="btnSignup">报 名</button>
+				<button class="btnSignup btnSignup-disable" v-if="mcState==2">活动已结束</button>
+			</view>
+
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+
+			<uni-popup ref="alertDialog" type="dialog">
+				<uni-popup-dialog type="info" cancelText="取消" confirmText="确认" title="您填写的姓名:" @confirm="dialogConfirm"
+					@close="dialogClose">
+					<view class="dialog-content uni-column">
+						<text class="dialog-content-1">{{nickName}}</text>
+					</view>
+				</uni-popup-dialog>
+			</uni-popup>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '../../../common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		apiCardDetailQuery,
+		apiOnlineMcSignUp,
+		apiMatchRsDetailQuery,
+		checkResCode,
+		checkToken
+	} from '../../../common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "signup",
+				firstEnterKey: 'firstEnter-tpl-style1',
+				rankKey: "rank-tpl-style1",
+				queryObj: {},
+				queryString: "",
+				from: "", // 来源页面
+				token: "",
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				coiId: 0, // 已报名单位id
+				coiName: "", // 已报名单位名称,可为空
+				teamNum: 0, // 已报名队伍编号,可为0
+				nickName: "", // 昵称
+				// coiRs: [], // 组织信息集合
+				// orgList: [], // 分组下拉列表数据源
+				// teamList: [], // 
+				interval: null,
+
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				// teamType: 0, // 队伍类型  0: 红黄蓝紫 1: 学生/家长
+				introduce: {
+					title: "",
+					content: ""
+				},
+				activityRules: {
+					title: "",
+					content: ""
+				},
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.from = query["from"] ?? "";
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.getCardDetailQuery();
+			this.matchRsDetailQuery();
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealNotice]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载队伍类型  0: 红黄蓝紫 1: 学生/家长
+				/* if (config.teamType != undefined && config.teamType >= 0) {
+					this.teamType = config.teamType;
+				} */
+
+				// 加载介绍内容
+				const introduce = config.introduce;
+				if (introduce != undefined) {
+					if (introduce.title != undefined) {
+						this.introduce.title = introduce.title;
+					}
+					if (introduce.content != undefined) {
+						this.introduce.content = introduce.content;
+					}
+				}
+
+				// 加载活动规则
+				const activityRules = config.activityRules;
+				if (activityRules != undefined) {
+					if (activityRules.title != undefined) {
+						this.activityRules.title = activityRules.title;
+					}
+					if (activityRules.content != undefined) {
+						this.activityRules.content = activityRules.content;
+					}
+				}
+				
+				this.pageReady = true;
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取倒计时
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			// 卡片信息查询
+			getCardDetailQuery() {
+				uni.request({
+					url: apiCardDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getCardDetailQuery", res);
+						const data = res.data.data;
+						this.mcType = data.mcType;
+						this.mcId = data.mcId;
+						this.mcName = data.mcName;
+						this.beginSecond = data.beginSecond;
+						this.endSecond = data.endSecond;
+						this.coiId = data.coiId;
+						this.coiName = data.coiName;
+						this.teamNum = data.teamNum;
+
+						this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+						this.getActtime();
+						// this.getOnlineMcSignUpDetail();
+
+						this.clear();
+						this.interval = setInterval(this.getActtime, 60000);
+					},
+					fail: (err) => {
+						console.log("getCardDetailQuery err", err)
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 线上赛报名
+			onlineMcSignUp() {
+				uni.request({
+					url: apiOnlineMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId,
+						coiId: this.coiId,
+						selectTeam: this.teamNum,
+						nickName: this.nickName
+					},
+					success: (res) => {
+						// console.log("onlineMcSignUp", res);
+
+						if (checkResCode(res)) {
+							uni.showToast({
+								title: '比赛报名成功!',
+								icon: 'none',
+								duration: 3000
+							});
+
+							const url = '/pages/tpl/style1/rankList?' + this.queryString;
+							tools.appAction(url, "uni.navigateTo");
+						}
+					},
+					fail: (err) => {
+						console.log("onlineMcSignUp err", err);
+						uni.showToast({
+							title: '出错了,报名失败',
+							icon: 'none',
+							duration: 3000
+						});
+					},
+				});
+			},
+			btnBack() {
+				// console.log("from:", this.from)
+				if (this.from != '') {
+					// window.history.back();
+					const url = '/pages/tpl/style1/rankList?' + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {
+					const url = `action://to_home/`;
+					tools.appAction(url);
+				}
+			},
+			btnInfo() {
+				this.$refs.mypopup.popupOpen();
+			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
+			btnSignup() {
+				if (!checkToken(this.token)) {
+					return;
+				}
+
+				if (!(this.nickName.trim().length > 0)) {
+					uni.showToast({
+						title: '请填写姓名',
+						icon: 'none',
+						duration: 2000
+					});
+					return;
+				}
+
+				this.nickName = this.nickName.trim();
+				this.$refs.alertDialog.open();
+			},
+			dialogConfirm() {
+				this.onlineMcSignUp();
+			},
+			dialogClose() {}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		min-height: 100vh;
+	}
+
+	.page-top {
+		width: 100%;
+		height: 170px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url("/static/backgroud/top_bg_egg3.png");
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: cover;
+	}
+
+	.topbar-color {
+		color: #333333;
+	}
+
+	.main {
+		width: 76%;
+		min-height: 800rpx;
+		/* margin-top: 20rpx; */
+		justify-content: space-around;
+	}
+
+	.timebar {
+		width: 90%;
+		height: 32px;
+		margin-top: 10px;
+		padding: 0 15px;
+		justify-content: center;
+		background: #ffffff;
+		border: 0.5px solid;
+		border-color: #e7e7e7;
+		border-radius: 20px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+	}
+
+	.acttime {
+		font-weight: 550;
+		color: #333333;
+		font-size: 14px;
+		text-wrap: nowrap;
+	}
+
+	.clock {
+		width: 15px;
+		height: 15px;
+		margin-right: 10px;
+	}
+
+	.uni-input {
+		width: 90%;
+		height: 43px;
+		margin-top: 15px;
+		padding: 0 15px;
+		background: #f1f1f1;
+		border-radius: 9px;
+	}
+
+	.input-placeholder {
+		color: #333333;
+		font-size: 16px;
+	}
+
+	.introduce {
+		width: 100%;
+		margin-top: 10px;
+		margin-bottom: 10px;
+		align-items: flex-start;
+		justify-content: space-around;
+	}
+
+	.introduce-title {
+		color: #333333;
+		font-size: 15px;
+		line-height: 30px;
+		font-family: Source Han Sans CN;
+	}
+
+	.introduce-content {
+		color: #333333;
+		font-size: 13px;
+		line-height: 23px;
+		font-family: Source Han Sans CN;
+	}
+
+	.activityRules {
+		width: 100%;
+		margin-top: 5px;
+		margin-bottom: 10px;
+		padding: 10px 15px;
+		align-items: flex-start;
+		justify-content: space-around;
+		border-radius: 9px;
+		background: #EBEBEB;
+	}
+
+	.activityRules-title {
+		color: #333333;
+		font-size: 14px;
+		line-height: 25px;
+		font-weight: 500;
+		font-family: Source Han Sans CN;
+	}
+
+	.activityRules-content {
+		color: #333333;
+		font-size: 13px;
+		line-height: 23px;
+		font-family: Source Han Sans CN;
+	}
+
+	.btnSignup {
+		width: 100%;
+		height: 100rpx;
+		margin-top: 30rpx;
+		margin-bottom: 30rpx;
+		color: white;
+		font-weight: bold;
+		line-height: 100rpx;
+		border-radius: 55rpx;
+	}
+
+	.bgcolor-main {
+		background-color: #81cd00;
+	}
+
+	.btnSignup-disable {
+		background-color: #c3c3c3;
+	}
+
+	.dialog-content {
+		width: 279px;
+		height: 110px;
+		background: #f1f1f1;
+		border-radius: 9px;
+		justify-content: center;
+		text-align: center;
+		font-weight: 550;
+		color: #333333;
+	}
+
+	.dialog-content-1 {
+		font-size: 40rpx;
+	}
+</style>

+ 263 - 0
card/pages/tpl/style2/cardconfig/test.js

@@ -0,0 +1,263 @@
+export const localCardConfig = `{
+	"common": {
+		"css": "
+			.color-main {
+				color: #ff870e !important;
+			}
+			.bgcolor-main {
+				background-color: #ff870e !important;
+			}
+			.tab-active{
+				background-color: #a43a07 !important;
+			}
+			.swiper-item-button {
+				background-color: #ff870e !important;
+			}
+			.uni-swiper-dot-active {
+				background: #ff870e !important;
+			}
+			.topbar-color {
+				color: #ffffff !important;
+			}
+			.topbar-rule {
+				margin-left: 3px;
+				color: #FFFFFF;
+				border-radius: 4px;
+				background: #FF870D;
+			}
+			.page-top {
+				height: 220px !important;
+				background-image: url('static/backgroud/top_bg_sddx.png') !important;
+			}
+		",
+		"tabActiveColor": "#FF870D",
+		"popupRuleConfig": {
+			"height": "530px"
+		},
+		"popupRuleList": [{
+				"type": 1,
+				"data": {
+					"title": "小飞龙定向赛",
+					"img": "/static/common/egg.png",
+					"content": "济南奥体中心“一场三馆”包括体育场、体育馆、网球馆和游泳馆,呈现出“东荷西柳”的总体布局。 体育场以济南的“市树”柳树为母题,将垂柳柔美飘逸的形态固化为建筑语言。"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "规则",
+					"img": "/static/common/guize.png"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "奖励",
+					"img": "/static/common/jiangli.png"
+				}
+			},
+			"default2"
+		],
+		"popupExchgConfig": {
+			"height": "460px"
+		},
+		"popupExchgList": [
+		],
+		"popupMessageConfig": {
+			"height": "500px"
+		},
+		"popupWarnConfig": {
+			"height": "550px"
+		},
+		"popupHelpConfig": {
+			"height": "539px"
+		},
+		"popupHelpList": [
+		]
+	},
+	"index": {
+		"css": "
+			.content-bg{
+				background: url('static/cardbg/xfl5.png') !important;
+				background-size: cover !important;
+			}
+			.logo{
+				width: 40vw !important;
+				height: 40vw !important;
+				background: url('static/logo/xfl2.png') no-repeat center !important;
+				background-size: contain !important;
+			}
+			.type {
+				color: #ffffff !important;
+			}
+			.name {
+				color: #ffffff !important;
+			}
+			.mod-button{
+				color: #ffffff !important;
+				background-color: #9d4f00 !important;
+			}
+		"
+	},
+	"signup": {
+		"css": "
+			.page-top {
+				height: 200px !important;
+			}
+		",
+		"introduce": {
+			"title": "介绍:",
+			"content": " · 小飞龙定向赛再次来袭!这次有五个场地哟~<br> · 神秘“蛋叔”闪亮登场~<br> · 蛋叔放大招,百味豆换鸡蛋!<br> · 时不可待!冲鸭!<br><br> · 能不能把蛋叔整郁闷,就看你的啦~<br> · 还等啥?火速报名吧!"
+		},
+		"activityRules": {
+			"title": "活动规则:",
+			"content": "<li>随时参赛、不限完赛次数、起点任选、实时排名 <li>起点 -各途经点 -结束点完整打卡为一次有效完赛"
+		}
+	},
+	"rankList": {
+		"css": "
+			.topbtm-name{
+				background-color: #c77f34 !important;
+				color: #ffffff !important;
+			}
+			.topbtm-egg{
+				background-color: #c6690a !important;
+				color: #ffffff !important;
+			}
+			.main-bar{
+				display: flex !important;
+				background-color: #FFEDDB !important;
+				color: #ff870d !important;
+			}
+		",
+		"param": {
+			"subTitle": "“筑梦宿州·创赢未来 ”特训营",
+			"labelRightAnswerNum": "正确答题",
+			"labelTicketName": "",
+			"labelAwardAddress": "",
+			"labelGoodsList": "兑换商品",
+			"tab1InitActIndex": 0,
+			"tab2InitActIndex": 0
+		},
+		"rankParam": {
+			"dispArrStr": "teamCp,teamDistance,teamRightAnswerPer,teamSpeed,regionCp,regionDistance,regionRightAnswerPer,regionSpeed",
+			"tab2Items_person_total": ["累计积分", "总里程", "正确答题", "单场用时"],
+			"tab2Items_person_region": ["累计积分", "总里程", "正确答题", "单场用时"],
+			"rankTypeList_person_total": ["totalCp", "totalDistance", "rightAnswerPer", "speed"],
+			"rankTypeList_person_region": ["totalCp", "totalDistance", "rightAnswerPer", "speed"],
+			"rank1List": ["teamCpRs", "teamDistanceRs", "teamRightAnswerPerRs", "teamSpeedRs"],
+			"rank2List": ["regionCpRs", "regionDistanceRs", "regionRightAnswerPerRs", "regionSpeedRs"]
+		}
+	},
+	"rankOverview": {
+		"css": "
+			.page-top {
+				height: 230px !important;
+			}
+			.logo{
+				width: 180.5px !important;
+				height: 130.5px !important;
+				background-image: url('static/logo/sddx_jjyjy.png');
+			}
+			.mid {
+				margin-top: -50px !important;
+			}
+			.top-acttime{
+				display: flex !important;
+				text-shadow: 2px 2px 1px #9c9c9c !important;
+				font-size: 20px !important; 
+				line-height: 30px !important;
+			}
+			.topbar-color{
+				color: #ffffff;
+			}
+			.midType0{
+				margin-top: -20px !important;
+			}
+			.e-select{
+				background-color: #a43a07 !important;
+			}
+			.main-title {
+				display: none !important;
+			}
+		",
+		"pathList": {
+			"row1": [{
+					"type": 3,
+					"pathImg": "/static/common/lingxiucheng.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "领秀城起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/quanchenggongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "泉城公园起始点"
+					}
+				}
+			],
+			"row2": [{
+					"type": 3,
+					"pathImg": "/static/common/baihuagongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "百花公园起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/aotizhongxin.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "奥体中心起始点"
+					}
+				}
+			],
+			"row3": [{
+				"type": 3,
+				"pathImg": "/static/common/muniushan.png",
+				"path": {
+					"ocaId": 1,
+					"mcType": 3
+				},
+				"navImg": "/static/common/nav.png",
+				"point": {
+					"longitude": 117.022194,
+					"latitude": 36.661612,
+					"name": "牧牛山起始点"
+				}
+			}]
+		},
+		"pathListStyle" : {
+			"showLine" : true,
+			"style": "justify-content: flex-start;"
+		}
+	}
+}`;

+ 402 - 0
card/pages/tpl/style2/index.vue

@@ -0,0 +1,402 @@
+<!-- 
+[模板] 样式2 - 卡片页
+http://localhost:5173/card/#/pages/tpl/style2/index
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style2/index
+ -->
+<template>
+	<view class="body body-radius">
+		<view v-if="pageReady" class="content content-bg" @click="btnClick">
+			<view class="card-top uni-row">
+				<view class="top-right uni-row">
+					<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
+					<view class="countdown">{{countdown}}</view>
+				</view>
+			</view>
+			<view class="card-main uni-column">
+				<view class="logo"></view>
+				<view class="uni-row" style="position: relative;">
+					<image v-if="notice" mode="aspectFit" src="/static/common/notice.png" class="notice"></image>
+					<text class="type mod-text">{{type}}</text>
+				</view>
+				<view class="name mod-text">{{ecName}}</view>
+				<button class="button mod-button">{{btnText}}</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '../../../common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		ossUrl,
+		apiCardBaseQuery,
+		apiUserJoinCardQuery,
+		apiMatchRsDetailQuery
+	} from '../../../common/api';
+	
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "index",
+				rankKey: "rank-tpl-style2",
+				queryObj: {},
+				queryString: "",
+				token: "",
+				
+				ecId: 0, // 卡片id
+				ecName: '', // 卡片名称
+				ecDesc: '', // 卡片简介
+				beginSecond: null, // 卡片开始时间戳,单位秒
+				endSecond: null, // 卡片结束时间戳,单位秒
+				secondCardName: '', // 跳转页面名称
+				
+				isJoin: null, // 是否报名
+				isFinished: false, // 赛事是否结束
+				
+				// mcId: 0, // 赛事id
+				// mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				// mcName: "", // 赛事名称
+				
+				countdown: "", // 倒计时
+				interval: null,
+				
+				type: "",
+				btnText: "",
+				notice: false,	// 是否显示(小红点)通知图标
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			
+			this.type = query["type"] ?? "锦标赛";
+			this.btnText = query["btnText"] ?? "开始比赛";
+			
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+			
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.getCardBaseQuery();
+			this.matchRsDetailQuery();
+		},
+		onShow() {
+			this.getUserJoinCardQuery();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealNotice]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							that.notice = true;
+							// that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						that.notice = true;
+						// that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+				
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = "距结束 " + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "已结束";
+						this.isFinished = true;
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 卡片基本信息查询
+			getCardBaseQuery() {
+				uni.request({
+					url: apiCardBaseQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						pageName: this.pageName
+					},
+					success: (res) => {
+						// console.log("getCardBaseQuery", res)
+						const data = res.data.data;
+						
+						this.ecName = data.ecName;
+						this.ecDesc = data.ecDesc;
+						this.beginSecond = data.beginSecond;
+						this.endSecond = data.endSecond;
+						this.secondCardName = data.secondCardName;
+						
+						this.getCountdown();
+						
+						this.clear();
+						this.interval = setInterval(this.getCountdown, 60000);
+					},
+					fail: (err) => {
+						console.log("getCardBaseQuery err", err)
+					},
+				});
+			},
+			// 用户是否已经报名卡片对应赛事查询
+			getUserJoinCardQuery() {
+				uni.request({
+					url: apiUserJoinCardQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getUserJoinCardQuery", res)
+						const code = res.data.code;
+						const data = res.data.data;
+						if (code == 0) {
+							this.isJoin = data.isJoin;
+							/*if (this.isJoin) {
+								this.btnText = "已报名";
+							} else {
+								this.btnText = "未报名";
+							} */
+						}
+					},
+					fail: (err) => {
+						console.log("getUserJoinCardQuery err", err)
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			btnClick() {
+				if (this.isJoin) {	// 已报名
+					const url = `${ossUrl}#/pages/tpl/style2/rankList?${this.queryString}&full=true`;
+					tools.appAction(url);
+				}
+				else {	// 未报名
+					if (!this.isFinished) {	// 赛事未结束
+						if (this.secondCardName == 'rankList') {
+							const url = `${ossUrl}#/pages/tpl/style2/rankList?${this.queryString}&full=true`;
+							tools.appAction(url);
+						} else {
+							const url = `${ossUrl}#/pages/tpl/style2/signup?${this.queryString}&full=true`;
+							tools.appAction(url);
+						}
+					}
+					else {	// 赛事已结束
+						// const url = `${ossUrl}#/pages/tpl/style2/rankOverview?${this.queryString}&full=true`;
+						const url = `${ossUrl}#/pages/tpl/style2/rankList?${this.queryString}&full=true`;
+						tools.appAction(url);
+					}
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+	}
+
+	.content-bg {
+		background: linear-gradient(180deg, #7aedff 0%, #047200 100%);
+		/* background: linear-gradient(180deg, #178bff 0%, #004d9b 100%); */
+		/* background: linear-gradient(180deg, #7aedff 0%, #8d2219 100%); */
+	}
+	
+	.card-top {
+		width: 100%;
+		justify-content: flex-end;
+	}
+
+	.top-right {
+		min-width: 180rpx;
+		height: 80rpx;
+		margin-top: 30rpx;
+		margin-right: 30rpx;
+		padding-left: 16rpx;
+		padding-right: 16rpx;
+		background-color: rgba(0, 0, 0, 0.3);
+		border-radius: 12px;
+	}
+
+	.clock {
+		width: 50rpx;
+		height: 50rpx;
+		margin-right: 12rpx;
+	}
+
+	.countdown {
+		min-width: 120rpx;
+		text-align: center;
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 46rpx;
+		/* letter-spacing: 2rpx; */
+	}
+
+	.card-main {
+		width: 100%;
+		/* height: 700rpx; */
+		height: 660rpx;
+		margin-top: 20rpx;
+		justify-content: space-evenly;
+	}
+
+	.logo {
+		width: 50vw;
+		height: 50vw;
+		background-image: url('/static/logo/jbs.png');
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.notice {
+		width: 30rpx;
+		height: 30rpx;
+		position: absolute;
+		left: -60rpx;
+	}
+	
+	.type {
+		opacity: 60%;
+		/* line-height: 25px; */
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 40rpx;
+		text-align: center;
+	}
+
+	.name {
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 50rpx;
+		text-align: center;
+	}
+
+	.button {
+		width: 320rpx;
+		height: 86rpx;
+		margin-top: 30rpx;
+		color: #000000;
+		background: #ffffff;
+		border-radius: 56rpx;
+		font-size: 46rpx;
+		line-height: 80rpx;
+	}
+	
+	/* .mod-text {
+		color: #FF6203 !important;
+	}
+	
+	.mod-button {
+		color: #ffffff !important;
+		background-color: #FF6203 !important;
+	} */
+	
+</style>

+ 922 - 0
card/pages/tpl/style2/rankList.vue

@@ -0,0 +1,922 @@
+<!-- 
+[模板] 样式2 - 排名列表
+http://localhost:5173/card/#/pages/tpl/style2/rankList
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style2/rankList
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color" :showMessage="cardConfigData.popupMessageList.length > 0"
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo" @btnMessageClick="btnMessage"></my-topbar>
+
+				<view class="topcontent uni-column uni-jcsa">
+					<view class="tcview uni-row uni-jcsb">
+						<view class="tcbox uni-column uni-jcc">
+							<text class="tcbox-label">赛事总里程</text>
+							<text class="tcbox-value">{{fmtDistanct(all_totalDistance)}} 千米</text>
+						</view>
+						<view class="tcbox uni-column uni-jcc">
+							<text class="tcbox-label">{{configParam.labelRightAnswerNum}}</text>
+							<text class="tcbox-value">{{all_totalAnswerNum}} 次</text>
+						</view>
+					</view>
+					<text class="today" :data-content="today">{{today}}</text>
+				</view>
+				
+				<view class="topcontent2 uni-column uni-jcsa">
+					<view class="logo"></view>
+					<text v-if="configParam.subTitle && configParam.subTitle.length > 0" class="top-acttime">{{configParam.subTitle}}</text>
+				</view>
+
+				<!-- <view class="topcontent uni-column uni-jcsa">
+					<view class="logo"></view>
+					<view class="tcbar uni-row uni-jcsb">
+						<text class="tcbar-text">赛事总里程:{{fmtDistanct(all_totalDistance)}} 千米</text>
+						<text class="tcbar-text">{{configParam.labelRightAnswerNum}}:{{all_totalAnswerNum}} 次</text>
+					</view>
+				</view> -->
+
+				<view class="topbtm uni-row">
+					<view class="topbtm-egg" @click="btnMyEgg">{{configParam.labelTicketName}}</view>
+					<text class="topbtm-name">{{nickName}}</text>
+					<view class="topbtm-egg" v-if="configParam.labelGoodsList && configParam.labelGoodsList.length > 0" @click="btnGoodsList">{{configParam.labelGoodsList}}</view>
+					<view class="topbtm-egg" v-else @click="btnExchg">{{configParam.labelAwardAddress}}</view>
+				</view>
+			</view>
+			<view class="main uni-column">
+				<view class="main-bar uni-row uni-jcse">
+					<text>题目输出:{{all_totalAnswerNum}}</text>
+					<text>总里程:{{fmtDistanct(all_totalDistance)}}km</text>
+					<text>总打点:{{all_totalCp}}</text>
+					<text>总百味豆:{{all_totalSysPoint}}</text>
+				</view>
+				
+				<my-tab ref="tab1" :tabItems="tab1Items" :type="1" @onTabClick="onTab1Click"
+					:initActIndex=configParam.tab1InitActIndex @onSelectChange="onSelectChange" :fontSize="14"></my-tab>
+				<my-tab ref="tab2" :tabItems="tab2Items" :tabItemsMark="tab2ItemsMark" :type="0"
+					:initActIndex=configParam.tab2InitActIndex @onTabClick="onTab2Click" :fontSize="12"></my-tab>
+
+				<view class="tab-view uni-column">
+					<!-- 个人成绩总排名列表 -->
+					<template v-if="tab1Current==0" v-for="(item, index) in rank1List" :key="index">
+						<my-ranklist v-show="tab2Current === index" :rankRs="rankList[item]"
+							:rank-type="rankTypeList[index]"></my-ranklist>
+					</template>
+
+					<!-- 个人成绩区域排名列表 -->
+					<template v-if="tab1Current==1" v-for="(item, index) in rank2List" :key="index">
+						<my-ranklist v-show="tab2Current === index" :rankRs="rankList[item]"
+							:rank-type="rankTypeList[index]"></my-ranklist>
+					</template>
+				</view>
+
+				<button class="btnBack bgcolor-main" @click="btnStartGame">{{btnStartGameText}}</button>
+			</view>
+
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+			<my-popup ref="mypopupExchg" :config="cardConfigData.popupExchgConfig" :dataList="cardConfigData.popupExchgList"></my-popup>
+			<my-popup ref="mypopupMessage" :config="cardConfigData.popupMessageConfig" :dataList="cardConfigData.popupMessageList" @noMoreRemindersClick="onNoMoreRemindersClick"></my-popup>
+			<my-popup ref="mypopupWarn" :config="cardConfigData.popupWarnConfig" :dataList="cardConfigData.popupWarnList"></my-popup>
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import { teamName } from '/common/define';
+	import {
+		token,
+		apiMatchRsDetailQuery,
+		apiCardRankDetailQuery,
+		apiUserCurrentRankNumQuery,
+		apiIsAllowMcSignUp,
+		apiUserJoinCardQuery,
+		apiMapListQuery,
+		apiCompStatisticQuery,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "rankList",
+				firstEnterKey: 'firstEnter-tpl-style2',
+				rankKey: "rank-tpl-style2",
+				mapKey: "rank-tpl-style2-map",
+				messageKey: "message-tpl-style2",
+				queryObj: {},
+				queryString: "",
+				token: "",
+				ovtype: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				ocaId: 0, // 关联id,带入到App活动详情页面
+				nickName: "", // 昵称
+				
+				isJoin: null, // 是否报名
+				btnStartGameText: "",
+				today: "",
+
+				mapList: [], // 卡片对应地图列表详情
+				all_totalDistance: 0, // 赛事所有人累计里程,单位米
+				all_totalRightAnswerNum: 0, // 赛事所有人正确答题数(校园文化输出)
+				all_totalAnswerNum: 0, // 赛事所有人答题数(校园文化输出)
+				all_totalCp: 0, // 赛事中所有人打点数
+				all_totalSysPoint: 0, // 赛事中所有人百味豆
+
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				allowMcSignUp: false, // 是否允许重新分组
+				countdown: "", // 倒计时
+				rankList: { // 排名列表
+					// totalDistanceRs: [],
+					// totalCpRs: [],
+					// totalSysPointRs: [],
+					// fastPaceRs: []
+				},
+				interval: null,
+
+				teamType: 0, // 队伍类型
+				// dispArrStr: "totalDistance,totalCp,totalSysPoint,fastPace",
+				dispArrStr: "teamCp,teamTodayCp,teamDistance,teamRightAnswerPer,teamTodayPace,regionCp,regionTodayCp,regionDistance,regionRightAnswerPer,regionTodayPace", // 要显示的集合范围
+				tab1Current: 0,
+				tab2Current: 0,				
+				tab1Items: ["个人(总排名)"],
+				// tab1Items: ["团体", {
+				// 	selectValue: 1,
+				// 	data: [
+				// 		{text: "个人(中心校区)", value: 1},
+				// 		{text: "个人(千佛山校区南区)", value: 2},
+				// 		{text: "个人(兴隆校区)", value: 3}
+				// 	]}
+				// ],
+				tab2Items: [],
+				tab2Items_person_total: ["总积分", "今日积分", "总里程", "校园文化", "今日配速"],
+				tab2Items_person_region: ["总积分", "今日积分", "总里程", "校园文化", "今日配速"],
+				tab2ItemsMark: [{
+					textColor: "#ff6203",
+					icon: "static/common/award.png"
+				}],
+				tabActiveColor: "#81cd00",
+
+				rankTypeList: [],
+				// 团队成绩类型列表
+				rankTypeList_person_total: ["totalScore", "totalScore", "totalDistance", "rightAnswerPer", "fastPace"],
+				// 个人成绩类型列表
+				rankTypeList_person_region: ["totalScore", "totalScore", "totalDistance", "rightAnswerPer", "fastPace"],
+				// 团体成绩列表
+				rank1List: ["teamCpRs", "teamTodayCpRs", "teamDistanceRs", "teamRightAnswerPerRs", "teamTodayPaceRs"],
+				// 个人成绩列表
+				rank2List: ["regionCpRs", "regionTodayCpRs", "regionDistanceRs", "regionRightAnswerPerRs", "regionTodayPaceRs"],
+
+				configParam: {
+					subTitle: "",
+					labelRightAnswerNum: "文化输出",
+					labelTicketName: "我的奖券",
+					labelAwardAddress: "兑奖地址",
+					// labelGoodsList: "兑换商品",
+					tab1InitActIndex: 0,
+					tab2InitActIndex: 0
+				},
+				// selectedMapId: 0, // 用户选择的地图ID
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			this.ovtype = query["ovtype"] ?? "";
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			this.mapKey += "-" + this.ecId;
+			console.log("mapKey:", this.mapKey);
+
+			this.messageKey += "-" + this.ecId;
+			console.log("messageKey:", this.messageKey);
+			
+			this.today = tools.timestampToTime(null, 2);
+
+			const mapValue = uni.getStorageSync(this.mapKey);
+			if (mapValue) {
+				console.log("mapValue:", mapValue);
+				// this.selectedMapId = mapValue;
+				this.ocaId = mapValue;
+			}
+			
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+			// this.tab2Items = this.tab2Items_person_total;
+		},
+		onShow() {
+			this.getUserJoinCardQuery();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			initTab() {
+				if (this.tab1Current == 0) {
+					this.rankTypeList = this.rankTypeList_person_total;
+					this.tab2Items = this.tab2Items_person_total;
+				} else {
+					this.rankTypeList = this.rankTypeList_person_region;
+					this.tab2Items = this.tab2Items_person_region;
+				}
+			},
+			dealNotice(rank) {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				cardfunc.unReadMessageQuery(this.unReadMessageQueryCallback);
+				cardfunc.warnMessageQuery(this.warnMessageQueryCallback);
+				this.matchRsDetailQuery();
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			unReadMessageQueryCallback(unReadMessage, mqIdListStr) {
+				// console.log("[unReadMessageQueryCallback] unReadMessage", unReadMessage);
+				if (unReadMessage.length > 0) {
+					this.messageKey += mqIdListStr;
+					// console.log("[unReadMessageQueryCallback] messageKey:", this.messageKey);
+					const messageValue = uni.getStorageSync(this.messageKey);
+					// console.log("[unReadMessageQueryCallback] messageValue:", messageValue);
+					if (!messageValue) {
+						this.$refs.mypopupMessage.popupOpen();
+						// uni.setStorageSync(this.messageKey, true);
+					}
+				}
+			},
+			warnMessageQueryCallback(warnMessage) {
+				// console.log("[warnMessageQueryCallback] warnMessage", warnMessage);
+				if (warnMessage.length > 0) {
+					this.$refs.mypopupWarn.popupOpen();
+				}
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载成绩参数
+				const rankParam = config.rankParam;
+				if (rankParam != undefined) {
+					if (rankParam.tab2ItemsMark != undefined) {
+						this.tab2ItemsMark = rankParam.tab2ItemsMark;
+					}
+					if (rankParam.dispArrStr != undefined && rankParam.dispArrStr.length > 0) {
+						this.dispArrStr = rankParam.dispArrStr;
+						// console.log("[loadConfig] dispArrStr:", rankParam.dispArrStr);
+					}
+					if (rankParam.tab2Items_person_total != undefined && rankParam.tab2Items_person_total.length > 0) {
+						this.tab2Items_person_total = rankParam.tab2Items_person_total;
+						// console.log("[loadConfig] tab2Items_person_total:", rankParam.tab2Items_person_total);
+					}
+					if (rankParam.tab2Items_person_region != undefined && rankParam.tab2Items_person_region.length > 0) {
+						this.tab2Items_person_region = rankParam.tab2Items_person_region;
+					}
+					if (rankParam.rankTypeList_person_total != undefined && rankParam.rankTypeList_person_total.length > 0) {
+						this.rankTypeList_person_total = rankParam.rankTypeList_person_total;
+					}
+					if (rankParam.rankTypeList_person_region != undefined && rankParam.rankTypeList_person_region.length > 0) {
+						this.rankTypeList_person_region = rankParam.rankTypeList_person_region;
+					}
+					if (rankParam.rank1List != undefined && rankParam.rank1List.length > 0) {
+						this.rank1List = rankParam.rank1List;
+					}
+					if (rankParam.rank2List != undefined && rankParam.rank2List.length > 0) {
+						this.rank2List = rankParam.rank2List;
+					}
+				}
+				// console.log("[loadConfig] rankParam:", rankParam);
+				
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.subTitle != undefined && param.subTitle.length > 0) {
+						this.configParam.subTitle = param.subTitle;
+					}
+					if (param.labelRightAnswerNum != undefined && param.labelRightAnswerNum.length > 0) {
+						this.configParam.labelRightAnswerNum = param.labelRightAnswerNum;
+					}
+					if (param.labelTicketName != undefined && param.labelTicketName.length > 0) {
+						this.configParam.labelTicketName = param.labelTicketName;
+					}
+					if (param.labelAwardAddress != undefined && param.labelAwardAddress.length > 0) {
+						this.configParam.labelAwardAddress = param.labelAwardAddress;
+					}
+					if (param.labelGoodsList != undefined && param.labelGoodsList.length > 0) {
+						this.configParam.labelGoodsList = param.labelGoodsList;
+					}
+					if (param.tab1InitActIndex != undefined && param.tab1InitActIndex >= 0) {
+						this.configParam.tab1InitActIndex = param.tab1InitActIndex;
+						this.tab1Current = param.tab1InitActIndex;
+					}
+					if (param.tab2InitActIndex != undefined && param.tab2InitActIndex >= 0) {
+						this.configParam.tab2InitActIndex = param.tab2InitActIndex;
+						this.tab2Current = param.tab2InitActIndex;
+					}
+				}
+				this.initTab();
+				// console.log("[loadConfig] param:", this.configParam);
+				
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = '距结束 ' + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "活动已结束";
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 格式化 距离
+			fmtDistanct(val) {
+				// return Math.round(val * 100 / 1000) / 100;
+				if (val < 10000)
+					return Math.round(val * 100 / 1000) / 100;
+				else
+					return Math.round(val / 1000);
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取活动时间
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			getTeamName(teamType, teamIndex) {
+				return teamName[teamType][teamIndex];
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (checkResCode(res)) {
+							const data = res.data.data;
+							this.mcType = data.mcType;
+							this.mcId = data.mcId;
+							this.mcName = data.mcName;
+							this.beginSecond = data.beginSecond;
+							this.endSecond = data.endSecond;
+							this.nickName = data.nickName;
+
+							this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+							this.getCountdown();
+							this.getActtime();
+							this.mapListQuery();
+							this.compStatisticQuery();
+							// this.getCardRankDetailQuery();
+
+							this.clear();
+							this.interval = setInterval(this.getCountdown, 60000);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 卡片对应地图列表详情查询
+			mapListQuery() {
+				uni.request({
+					url: apiMapListQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("mapListQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.mapList = data;
+
+							let mapItems = {
+								// selectValue: this.selectedMapId,
+								selectValue: this.ocaId,
+								data: []
+							};
+							for (var i = 0; i < data.length; i++) {
+								if (mapItems.selectValue == 0 && i == 0) {
+									mapItems.selectValue = data[i].ocaId;
+									// this.selectedMapId = data[i].ocaId;
+									this.ocaId = data[i].ocaId;
+								}
+								let map = {};
+								map.text = "个人(" + data[i].mapName + ")";
+								map.value = data[i].ocaId;
+								mapItems.data.push(map);
+							}
+							this.tab1Items.push(mapItems);
+							this.getCardRankDetailQuery();
+						}
+					},
+					fail: (err) => {
+						console.log("mapListQuery err", err);
+					},
+				});
+			},
+			// 赛事总成绩统计查询
+			compStatisticQuery() {
+				uni.request({
+					url: apiCompStatisticQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("compStatisticQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.all_totalDistance = data.totalDistance;
+							this.all_totalRightAnswerNum = data.totalRightAnswerNum;
+							this.all_totalAnswerNum = data.totalAnswerNum;
+							this.all_totalCp = data.totalCp;
+							this.all_totalSysPoint = data.totalSysPoint;
+						}
+					},
+					fail: (err) => {
+						console.log("compStatisticQuery err", err);
+					},
+				});
+			},
+			// 排名查询
+			getCardRankDetailQuery() {
+				uni.request({
+					url: apiCardRankDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcIdListStr: this.mcId,
+						mcType: this.mcType,
+						ocaId: this.ocaId,
+						dispArrStr: this.dispArrStr
+					},
+					success: (res) => {
+						// console.log("getCardRankDetailQuery", res);
+						const rankdata = res.data.data;
+						this.rankList = rankdata;
+					},
+					fail: (err) => {
+						console.log("getCardRankDetailQuery err", err);
+					},
+				});
+			},
+			// 是否允许重新分组(报名)
+			isAllowMcSignUp() {
+				uni.request({
+					url: apiIsAllowMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("isAllowMcSignUp", res)
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.allowMcSignUp = data.allowSignUp;
+						}
+					},
+					fail: (err) => {
+						console.log("isAllowMcSignUp err", err)
+					},
+				});
+			},
+			// 用户是否已经报名卡片对应赛事查询
+			getUserJoinCardQuery() {
+				uni.request({
+					url: apiUserJoinCardQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getUserJoinCardQuery", res)
+						const code = res.data.code;
+						const data = res.data.data;
+						if (code == 0) {
+							this.isJoin = data.isJoin;
+							if (this.isJoin) { // 已报名
+								// this.btnStartGameText = "我要比赛";
+								this.btnStartGameText = "选择场地";
+							} else {	// 未报名
+								this.btnStartGameText = "我要报名";
+							}
+						}
+					},
+					fail: (err) => {
+						console.log("getUserJoinCardQuery err", err)
+					},
+				});
+			},
+			onNoMoreRemindersClick() {
+				this.$refs.mypopupMessage.popupClose();
+				uni.setStorageSync(this.messageKey, true);
+			},
+			btnBack() {
+				// window.history.back();
+				const url = `action://to_home/`;
+				tools.appAction(url);
+			},
+			btnStartGame() {
+				if (this.isJoin) {	// 已报名
+					const url = "/pages/tpl/style2/rankOverview?" + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {	// 未报名
+					const url = "/pages/tpl/style2/signup?" + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				}
+			},
+			btnInfo() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopup.popupOpen();
+			},
+			btnMessage() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopupMessage.popupOpen();
+			},
+			btnMyEgg() {
+				const url = "/pages/achievement/index2?tabCurrent=2&" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnExchg() {
+				this.$refs.mypopupExchg.popupOpen();
+			},
+			btnGoodsList() {
+				this.queryObj.from = "/pages/tpl/style2/rankList";
+				this.queryString = tools.objectToQueryString(this.queryObj);
+				const url = "/pages/exchange/style1/goodsList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			onTab1Click(val) {
+				console.log("onTab1Click: ", val);
+				this.tab1Current = val;
+				this.initTab();
+			},
+			onTab2Click(val) {
+				// console.log("onTab2Click: ", val);
+				this.tab2Current = val;
+			},
+			onSelectChange(val) {
+				// console.log("onSelectChange: ", val);
+				this.ocaId = val.value;
+				this.getCardRankDetailQuery();
+
+				uni.setStorageSync(this.mapKey, this.ocaId);
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+	}
+
+	.page-top {
+		width: 100%;
+		height: 170px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url('static/backgroud/top_bg_sddx.png');
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: cover;
+	}
+
+	.topcontent {
+		width: 90%;
+		/* height: 90%; */
+		/* margin-bottom: 20px; */
+	}
+
+	.topcontent2 {
+		width: 90%;
+		display: none;
+	}
+	
+	.logo {
+		width: 80px;
+		height: 80px;
+		margin-top: 10px;
+		margin-bottom: 10px;
+		/* background-image: url('/static/logo/sddx.png'); */
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.top-acttime {
+		text-shadow: 3px 3px 0px #640008;
+		font-family: YouSheBiaoTiHei;
+		/* font-family: Arial, Helvetica, sans-serif; */
+		font-weight: bold;
+		color: #ffee0b;
+		font-size: 20px;
+	}
+	
+	.tcview {
+		width: 90%;
+		/* height: 100px; */
+	}
+
+	.tcbox {
+		width: 121px;
+		height: 63px;
+		opacity: 1;
+		border-radius: 6px;
+		background: #9A300E;
+		border: 1px solid #D3A254;
+	}
+
+	.tcbox-label {
+		font-size: 12px;
+		font-weight: 500;
+		line-height: 23px;
+		color: #f3d809;
+	}
+
+	.tcbox-value {
+		font-size: 16px;
+		font-weight: 500;
+		line-height: 26px;
+		color: #f3d809;
+	}
+	
+	.today {
+		margin-top: 8px;
+		color: #751f00;
+		font-size: 16px;
+		/* font-family: "黑体", sans-serif; */
+		font-weight: 3700;
+		position: relative;
+		z-index: 0;
+	}
+
+	.today::after {
+		content: attr(data-content);
+		-webkit-text-stroke: 3px #DCA452;
+		/* font-family: "黑体", sans-serif; */
+		position: absolute;
+		left: 0;
+		top: 0;
+		z-index: -1;
+	}
+
+	.tcbar {
+		display: none;
+		width: 92%;
+		padding: 6px 12px;
+		background: #9a300e;
+		border-radius: 6px;
+	}
+
+	.tcbar-text {
+		font-family: Source Han Sans CN;
+		font-weight: 500;
+		color: #f3d809;
+		font-size: 13px;
+	}
+
+	.mcName {
+		font-size: 40rpx;
+		font-weight: 550;
+	}
+
+	.topbtm {
+		width: 100%;
+		margin-bottom: 5px;
+		justify-content: space-around;
+	}
+
+	.topbtm-name {
+		padding: 3px 12px;
+		background-color: #9A300E;
+		border: 1px solid #D3A254;
+		border-radius: 6px;
+		text-align: center;
+		font-weight: 500;
+		color: #ffffff;
+		font-size: 14px;
+	}
+
+	.topbtm-egg {
+		width: 60px;
+		padding: 3px 12px;
+		background-color: #9A300E;
+		border: 1px solid #D3A254;
+		border-radius: 6px;
+		text-align: center;
+		color: #ffffff;
+		font-size: 14px;
+	}
+
+	.topbtm-null {
+		width: 60px;
+		padding: 3px 12px;
+	}
+
+	.cal {
+		width: 46rpx;
+		height: 46rpx;
+		margin-right: 20rpx;
+	}
+
+	.main {
+		width: 100%;
+		/* height: 70vh; */
+		flex-grow: 1;
+		justify-content: space-around;
+		/* justify-content: space-between; */
+	}
+	
+	.main-bar {
+		display: none;
+		width: 100%;
+		height: 21px;
+		background-color: #d8e8c6;
+		
+		font-size: 10px;
+		font-weight: 500;
+		color: #3d6706;
+	}
+
+	/* /deep/ .tab-active {
+		background-color: #a43a07 !important;
+	} */
+
+	.main-tab {
+		width: 90%;
+		margin-top: 20rpx;
+	}
+
+	.tab-view {
+		width: 100%;
+		/* height: 69vh; */
+		flex-grow: 1;
+	}
+
+	.btnBack {
+		width: 70%;
+		height: 80rpx;
+		margin-bottom: 20rpx;
+		/* font-weight: bold; */
+		color: white;
+		font-size: 32rpx;
+		line-height: 80rpx;
+		border-radius: 27px;
+		background-color: #2e85ec;
+	}
+
+	/* .swiper-item-button {
+		background-color: #ff870e !important;
+	}
+	
+	.uni-swiper-dot-active {
+		background: #ff870e !important;
+	} */
+</style>

+ 910 - 0
card/pages/tpl/style2/rankOverview.vue

@@ -0,0 +1,910 @@
+<!-- 
+[模板] 样式2 - 排名总览
+http://localhost:5173/card/#/pages/tpl/style2/rankOverview
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style2/rankOverview
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color"
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo"></my-topbar>
+				
+				<view class="topcontent uni-column uni-jcsa">
+					<view class="logo"></view>
+					<text v-if="configParam.subTitle.length > 0" class="top-acttime">{{configParam.subTitle}}</text>
+					<text v-else class="top-acttime">{{fmtMcTime2(beginSecond, endSecond)}}</text>
+				</view>
+			</view>
+			<view v-if="configParam.midType == 0" class="midType0 uni-column">
+				<view class="mid-0 uni-row uni-jcc">
+					<view class="mid-0-select">
+						<e-select v-model="ocaId" :options="mapList" :search="false" :inputClick="true"
+							maxHeight="300px" :clearable="false" @change="eSelectChange"></e-select>
+					</view>
+					<text v-if="cardConfigData.popupHelpList.length > 0" class="mid-0-help" @click="btnHelp">帮助</text>
+				</view>
+				<view class="midType0-mid-1 uni-row uni-jcc">
+					<view class="uni-row">
+						<image v-if="cardConfigData.popupWarnList.length > 0" class="mid-1-yellowCard" mode="aspectFit" 
+						 @click="btnWarn" src="/static/common/card_yellow.gif"></image>
+						<text class="mid-1-text">{{nickName}}</text>
+					</view>
+					<!-- <text class="mid-1-text">{{coiName}}</text> -->
+					<text class="mid-1-text" style="color: #aaaaaa; margin-left: 20px;" v-if="mcState==1 && allowMcSignUp"
+						@click="btnReGroup">修改</text>
+				</view>
+				<view class="mid-2 uni-row uni-jcsa">
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalNum}}</text>
+						<text class="mid-2-text">总场次</text>
+					</view>
+
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalCp}}</text>
+						<text class="mid-2-text">总打点数</text>
+					</view>
+
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalCpRankNum}}</text>
+						<text class="mid-2-text">个人排名</text>
+					</view>
+
+				</view>
+			</view>
+			
+			<view v-if="configParam.midType == 1" class="midType1 uni-column">
+				<view class="mid-0 uni-row uni-jcc">
+					<view class="mid-0-select">
+						<e-select v-model="ocaId" :options="mapList" :search="false" :inputClick="true"
+							maxHeight="300px" :clearable="false" @change="eSelectChange"></e-select>
+					</view>
+					<view class="mid-0-right uni-row uni-jcsa">
+						<text class="" style="" v-if="mcState==1 && allowMcSignUp"
+							@click="btnReGroup">修改</text>
+						<text v-if="cardConfigData.popupHelpList.length > 0" class="" @click="btnHelp">帮助</text>
+					</view>
+				</view>
+				<view class="midType1-mid-1 uni-row uni-jcse">
+					<view class="uni-row">
+						<image v-if="cardConfigData.popupWarnList.length > 0" class="mid-1-yellowCard" mode="aspectFit" 
+						 @click="btnWarn" src="/static/common/card_yellow.gif"></image>
+						<text class="mid-1-name">{{nickName}}</text>
+					</view>
+					<!-- <text class="mid-1-name">{{coiName}}</text> -->
+					<text class="uni-nowrap">总场次:{{regionTotalNum}}</text>
+				</view>
+				<view class="mid-2 uni-row uni-jcsa">
+					<view class="uni-column">
+						<text class="mid-2-value" style="color: #ff0045;">{{regionTotalSysPoint}}</text>
+						<text class="mid-2-text">百味豆(个)</text>
+					</view>
+			
+					<view class="mid-line"></view>
+			
+					<view class="uni-column">
+						<text class="mid-2-value">{{fmtDistanct(regionTotalDictance)}}</text>
+						<text class="mid-2-text">总里程km</text>
+					</view>
+			
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalCp}}</text>
+						<text class="mid-2-text">打点数(个)</text>
+					</view>
+			
+					<view class="mid-line"></view>
+			
+					<view class="uni-column">
+						<text class="mid-2-value">{{fmtPace(regionFastPace)}}</text>
+						<text class="mid-2-text">最快配速</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="main uni-column">
+				<text class="main-title">选择比赛路线</text>
+				<my-pathList :style="pathListStyle.style" :pathList="pathList"
+					:selectedPath="ocaId" :mcState="mcState" :showLine="pathListStyle.showLine"
+					@onPathClick="onPathClick"></my-pathList>
+			</view>
+			
+			<view class="bottom uni-column">
+				<button class="btnStartGame bgcolor-main" @click="btnStartGame">开始比赛</button>
+			</view>
+			
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+			<my-popup ref="mypopupHelp" :config="cardConfigData.popupHelpConfig" :dataList="cardConfigData.popupHelpList"></my-popup>
+			<my-popup ref="mypopupWarn" :config="cardConfigData.popupWarnConfig" :dataList="cardConfigData.popupWarnList"></my-popup>
+			<!-- <my-popup-map ref="mypopupmap" :point="navPoint"></my-popup-map> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		apiMapListQuery,
+		apiMatchRsDetailQuery,
+		apiIsAllowMcSignUp,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "rankOverview",
+				firstEnterKey: 'firstEnter-tpl-style2',
+				rankKey: "rank-tpl-style2",
+				mapKey: "rank-tpl-style2-map",
+				queryObj: {},
+				queryString: "",
+				token: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				coiName: "", // 已报名单位名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				ocaId: 0, // 关联id,带入到App活动详情页面
+				nickName: "", // 昵称
+
+				totalNum: null, // 总场次
+				totalDistanct: null, // 总距离,单位米
+				totalDistanctRankNum: null, // 总距离排名
+				totalCp: null, // 总打点数
+				totalCpRankNum: null, // 总打点数排名
+				totalSysPoint: null, // 总百味豆
+				totalSysPointRankNum: null, // 总百味豆排名
+				fastPace: null, // 个人最快配速
+				fastPaceRankNum: null, // 个人最快配速排名
+				regionTotalNum: null, // 地图内总场次
+				regionTotalCp: null, // 地图内打点数
+				regionTotalCpRankNum: null, // 地图内个人打点排名
+				regionTotalSysPoint: null, // 地图内积分(百味豆)
+				regionTotalDictance: null, // 地图内里程(单位米)
+				regionFastPace: null, // 地图内最快配速
+				ocaRs: [], // 卡片对应活动集合
+
+				mapList: [], // 地图列表
+
+				interval: null,
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				allowMcSignUp: false, // 是否允许重新分组
+				pathList: {},
+				pathListStyle: {},
+				navPoint: {},
+				configParam: {
+					subTitle: "",
+					midType: 0
+				},
+			}
+		},
+		computed: {
+			pathListLen() {
+				return Object.keys(this.pathList).length;
+			}
+		},
+		onLoad(query) { // 类型非必填,可自动推导
+			// uni.showLoading({
+			// 	title: '加载中'
+			// });
+			
+			// console.log("query:", query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			this.mapKey += "-" + this.ecId;
+			console.log("mapKey:", this.mapKey);
+
+			const mapValue = uni.getStorageSync(this.mapKey);
+			if (mapValue) {
+				console.log("mapValue:", mapValue);
+				this.ocaId = mapValue;
+			}
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+		},
+		onShow() {
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				this.matchRsDetailQuery();
+				cardfunc.warnMessageQuery();
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载比赛路线数据
+				const pathList = config.pathList;
+				// console.log("[loadConfig] pathList:", pathList);
+				if (pathList != undefined) {
+					this.pathList = pathList;
+				}
+
+				// 加载比赛路线样式
+				const pathListStyle = config.pathListStyle;
+				// console.log("[loadConfig] pathList:", pathList);
+				if (pathListStyle != undefined) {
+					this.pathListStyle = pathListStyle;
+				}
+				
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.subTitle != undefined && param.subTitle.length > 0) {
+						this.configParam.subTitle = param.subTitle;
+					}
+					if (param.midType != undefined && param.midType >= 0) {
+						this.configParam.midType = param.midType;
+					}
+				}
+				// console.log("[loadConfig] param:", this.configParam);
+
+				this.pageReady = true;
+				// uni.hideLoading();
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = '距结束 ' + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "活动已结束";
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 格式化 距离
+			fmtDistanct(val) {
+				if (val >= 0) {
+					return Math.round(val * 100 / 1000) / 100;
+				} else {
+					return '--';
+				}
+				/* if (val < 1000)
+					return Math.round(val * 10 / 1000) / 10;
+				else
+					return Math.round(val / 1000); */
+			},
+			// 格式化 配速
+			fmtPace(val) {
+				if (val >= 0) {
+					return tools.convertSecondsToHMS(val, 2);
+				} else {
+					return '--';
+				}
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取活动时间
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			fmtMcTime2(timestamp1, timestamp2) {
+				return tools.fmtMcTime2(timestamp1, timestamp2);
+			},
+			// 卡片对应地图列表详情查询
+			mapListQuery() {
+				uni.request({
+					url: apiMapListQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("mapListQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.mapList = data;
+
+							let mapItems = [];
+							for (var i = 0; i < data.length; i++) {
+								let map = {};
+								map.text = data[i].mapName;
+								map.value = data[i].ocaId;
+								mapItems.push(map);
+								if (i == 0 && this.ocaId == 0) {
+									this.ocaId = data[i].ocaId;
+									this.matchRsDetailQuery();
+									uni.setStorageSync(this.mapKey, this.ocaId);
+								}
+							}
+							this.mapList = mapItems;
+							// console.log("mapList", this.mapList);
+						}
+					},
+					fail: (err) => {
+						console.log("mapListQuery err", err);
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						ocaId: this.ocaId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (checkResCode(res)) {
+							const data = res.data.data;
+							this.mcType = data.mcType;
+							this.mcId = data.mcId;
+							this.mcName = data.mcName;
+							this.coiName = data.coiName;
+							this.beginSecond = data.beginSecond;
+							this.endSecond = data.endSecond;
+							this.nickName = data.nickName;
+							this.totalNum = data.totalNum;
+							this.totalDistanct = data.totalDistanct;
+							this.totalDistanctRankNum = data.totalDistanctRankNum;
+							this.totalCp = data.totalCp;
+							this.totalCpRankNum = data.totalCpRankNum;
+							this.totalSysPoint = data.totalSysPoint;
+							this.totalSysPointRankNum = data.totalSysPointRankNum;
+							this.fastPace = data.fastPace;
+							this.fastPaceRankNum = data.fastPaceRankNum;
+							this.regionTotalNum = data.regionTotalNum;
+							this.regionTotalCp = data.regionTotalCp;
+							this.regionTotalCpRankNum = data.regionTotalCpRankNum;
+							this.regionTotalSysPoint = data.regionTotalSysPoint;
+							this.regionTotalDictance = data.regionTotalDictance;
+							this.regionFastPace = data.regionFastPace;
+							this.ocaRs = data.ocaRs;
+
+							this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+
+							this.getCountdown();
+							this.getActtime();
+
+							this.isAllowMcSignUp();
+							this.mapListQuery();
+
+							this.clear();
+							this.interval = setInterval(this.getCountdown, 60000);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 是否允许重新分组(报名)
+			isAllowMcSignUp() {
+				uni.request({
+					url: apiIsAllowMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("isAllowMcSignUp", res)
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.allowMcSignUp = data.allowSignUp;
+						}
+					},
+					fail: (err) => {
+						console.log("isAllowMcSignUp err", err)
+					},
+				});
+			},
+			btnBack() {
+				// const url = `action://to_home/`;
+				const url = "/pages/tpl/style2/rankList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnInfo() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopup.popupOpen();
+			},
+			btnHelp() {
+				this.$refs.mypopupHelp.popupOpen();
+			},
+			btnWarn() {
+				this.$refs.mypopupWarn.popupOpen();
+			},
+			btnReGroup() {
+				this.queryObj.from = "rankOverview";
+				this.queryString = tools.objectToQueryString(this.queryObj);
+
+				const url = '/pages/tpl/style2/signup?' + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			onOverviewClick(ovtype) {
+				this.queryObj.ovtype = ovtype;
+				this.queryString = tools.objectToQueryString(this.queryObj);
+
+				const url = "/pages/tpl/style2/rankList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			eSelectChange(data) {
+				// console.log("eSelectChange:", data);
+				this.ocaId = data.value;
+				this.matchRsDetailQuery();
+				uni.setStorageSync(this.mapKey, this.ocaId);
+			},
+			onPathClick(data) {
+				console.log("onPathClick:", data);
+				if (this.ocaId != data.path.ocaId) {
+					this.ocaId = data.path.ocaId;
+					this.matchRsDetailQuery();
+					uni.setStorageSync(this.mapKey, data.path.ocaId);
+				}
+			},
+			btnStartGame() {
+				// 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				if (this.mcState == 1) {
+					const url = `action://to_detail/?id=${this.ocaId}&matchType=${this.mcType}`;
+					tools.appAction(url);
+				} else if (this.mcState == 0) {
+					uni.showToast({
+						title: '比赛尚未开始',
+						icon: 'none',
+						duration: 3000
+					});
+				} else if (this.mcState == 2) {
+					uni.showToast({
+						title: '比赛已结束',
+						icon: 'none',
+						duration: 3000
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		min-height: 100vh;
+		overflow-x: scroll;
+	}
+
+	.page-top {
+		position: relative;
+		z-index: 10;
+		width: 100%;
+		height: 270px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url("/static/backgroud/top_bg_egg2.png");
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: cover;
+		/* background-size: 100% 100%; */
+	}
+
+	.topbar-color {
+		color: #333333;
+	}
+
+	.topcontent {
+		height: 90%;
+		margin-bottom: 50px;
+	}
+
+	.logo {
+		width: 80px;
+		height: 80px;
+		margin-top: 10px;
+		/* background-image: url('/static/logo/sddx.png'); */
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.top-acttime {
+		display: none;
+		text-shadow: 3px 3px 0px #640008;
+		font-family: YouSheBiaoTiHei;
+		/* font-family: Arial, Helvetica, sans-serif; */
+		font-weight: bold;
+		color: #ffee0b;
+		font-size: 26px;
+	}
+
+	.topbtm {
+		width: 100%;
+		/* height: 60rpx; */
+		margin-bottom: 40px;
+		justify-content: space-evenly;
+	}
+
+	.topbtm-name {
+		/* width: 320rpx; */
+		/* height: 28px; */
+		padding: 5px 12px;
+		background-color: #9fda39;
+		border-radius: 5px;
+		/* backdrop-filter: blur(30px); */
+		text-align: center;
+		font-weight: 500;
+		color: #497400;
+		font-size: 14px;
+	}
+
+	.midType0 {
+		width: 90%;
+		height: 150px;
+		position: relative;
+		z-index: 20;
+		margin-top: -40px;
+		background: #ffffff;
+		border-radius: 9px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+		font-family: Source Han Sans CN;
+	}
+
+	.midType1 {
+		width: 90%;
+		/* height: 112px; */
+		height: 150px;
+		position: relative;
+		z-index: 20;
+		margin-top: -70px;
+		background: #ffffff;
+		border-radius: 9px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+		font-family: Source Han Sans CN;
+	}
+	
+	.mid-0 {
+		width: 90%;
+		margin: 12px;
+	}
+	
+	.mid-0-select {
+		width: 60%;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.mid-0-help {
+		position: absolute;
+		right: 30px;
+		font-size: 12px;
+		font-weight: 500;
+		color: #992900;
+	}
+
+	.mid-0-right {
+		position: absolute;
+		width: 60px;
+		/* background-color: #81cd00; */
+		right: 10px;
+		font-size: 12px;
+		font-weight: 500;
+		color: #992900;
+	}
+	
+	.midType0-mid-1 {
+		width: 90%;
+		margin-bottom: 12px;
+	}
+	
+	.midType1-select {
+		/* width: 46%; */
+		min-width: 46%;
+		max-width: 50%;
+		margin: 0 5px;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.midType1-mid-1 {
+		width: 90%;
+		margin-bottom: 12px;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.mid-1-yellowCard {
+		width: 15px;
+		height: 20px;
+		margin-right: 10px;
+	}
+
+	.mid-1-name {
+		max-width: 360rpx;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+	
+	.mid-1-text {
+		/* min-width: 60px; */
+		font-weight: 500;
+		color: #9a300e;
+		font-size: 12px;
+	}
+
+	.mid-2 {
+		width: 92%;
+		/* margin: 0 10px; */
+	}
+
+	.mid-2-value {
+		font-weight: 900;
+		font-size: 22px;
+		line-height: 25px;
+	}
+
+	.mid-2-text {
+		color: #989898;
+		font-size: 12px;
+	}
+
+	.mid-line {
+		width: 0px;
+		height: 40px;
+		border: 1px solid;
+		border-color: #e6e6e6;
+	}
+
+	.overview-1 {
+		width: 111px;
+		height: 54px;
+		background: #ffb40b;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-2 {
+		margin-top: -43px;
+		color: #ffffff;
+		font-size: 18px;
+		pointer-events: auto;
+	}
+
+	.overview-3 {
+		width: 111px;
+		height: 54px;
+		background: #f39509;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-4 {
+		width: 111px;
+		height: 54px;
+		background: #81cd00;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-5 {
+		width: 111px;
+		height: 54px;
+		background: #64cbb0;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.ovline1 {
+		margin-top: 9px;
+		color: #ffffff;
+		font-size: 12px;
+	}
+
+	.ovline2 {
+		color: #ffffff;
+		font-size: 16px;
+	}
+
+	.main {
+		width: 100%;
+		margin-top: 20px;
+		margin-bottom: 20px;
+		/* height: 70vh; */
+		justify-content: space-around;
+		/* justify-content: space-between; */
+	}
+
+	.main-title {
+		margin-bottom: 10px;
+		font-weight: 550;
+		color: #333333;
+		font-size: 16px;
+	}
+
+	.bottom {
+		width: 100%;
+		flex-grow: 1;
+		justify-content: flex-end;
+	}
+
+	.btnStartGame {
+		width: 70%;
+		height: 80rpx;
+		/* margin-top: 50rpx; */
+		margin-bottom: 20rpx;
+		/* font-weight: bold; */
+		color: white;
+		font-size: 32rpx;
+		line-height: 80rpx;
+		border-radius: 27px;
+		background-color: #2e85ec;
+	}
+	
+	/deep/ .e-select {
+		/* width: 60%; */
+		height: 60rpx;
+		background: #2e85ec;
+		color: #ffffff;
+		border-radius: 18px;
+		justify-content: space-around;
+
+		font-size: 16px;
+		font-weight: 500;
+		line-height: 60rpx;
+		text-align: center;
+		border: none !important;
+	}
+
+	/deep/ .e-select-input-text {
+		color: inherit !important;
+	}
+
+	/deep/ .e-select-selector-item {
+		color: #818181;
+	}
+
+	/deep/ .uni-icons {
+		color: inherit !important;
+	}
+
+	/deep/ .e-select-icon {
+		width: 26px !important;
+	}
+</style>

+ 706 - 0
card/pages/tpl/style2/signup.vue

@@ -0,0 +1,706 @@
+<!-- 
+[模板] 样式2 - 报名
+http://localhost:5173/card/#/pages/tpl/style2/signup
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style2/signup
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color" @btnBackClick="btnBack"
+					@btnInfoClick="btnInfo"></my-topbar>
+
+				<view class="topcontent uni-column uni-jcsa">
+					<view class="logo"></view>
+					<text v-if="configParam.subTitle.length > 0" class="top-acttime">{{configParam.subTitle}}</text>
+					<text v-else class="top-acttime">{{fmtMcTime2(beginSecond, endSecond)}}</text>
+				</view>
+			</view>
+
+			<view class="timebar uni-row">
+				<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
+				<text class="acttime">{{acttime}}</text>
+			</view>
+
+			<view class="main uni-column uni-jct">
+				<input class="uni-input" maxlength="12" :placeholder="'请填写'+configParam.labelName" placeholder-style="font-size: 14px;" v-model="nickName" @click="nickNameClick" />
+				<!-- <e-select v-model="coiId" :options="coiRs" :props="eSelectionProps" clearable
+					maxHeight="40vh" :placeholder="'请选择'+configParam.labelOrg+'(可输入关键字)'" @getText="getESelectText"
+					@change="eSelectChange"></e-select> -->
+
+				<view class="introduce uni-column">
+					<text class="introduce-title">{{introduce.title}}</text>
+					<text class="introduce-content" v-html="introduce.content"></text>
+				</view>
+
+				<view v-if="activityRules.content.length > 0" class="activityRules uni-column">
+					<text class="activityRules-title">{{activityRules.title}}</text>
+					<text class="activityRules-content" v-html="activityRules.content"></text>
+				</view>
+
+				<button class="btnSignup bgcolor-main" v-if="mcState<=1" @click="btnSignup">报 名</button>
+				<button class="btnSignup btnSignup-disable" v-if="mcState==2">活动已结束</button>
+
+			</view>
+
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+
+			<!-- <uni-popup ref="alertDialog" type="dialog">
+				<uni-popup-dialog type="info" cancelText="取消" confirmText="确认" title="您填写的姓名:" @confirm="dialogConfirm"
+					@close="dialogClose">
+					<view class="dialog-content uni-column">
+						<text class="dialog-content-1">{{nickName}}</text>
+					</view>
+				</uni-popup-dialog>
+			</uni-popup> -->
+
+			<uni-popup ref="alertDialog" type="dialog">
+				<uni-popup-dialog type="info" cancelText="取消" confirmText="确认" title="你的报名信息:" @confirm="dialogConfirm"
+					@close="dialogClose">
+					<view class="dialog-content uni-column">
+						<text class="dialog-content-1">{{mcName}}</text>
+						<view class="uni-column uni-ais">
+							<text class="dialog-content-2">{{configParam.labelName}}: {{nickName}}</text>
+							<!-- <text class="dialog-content-2">{{configParam.labelOrg}}: {{coiName}}</text> -->
+							<!-- <text class="dialog-content-2">{{teamName}}</text> -->
+						</view>
+					</view>
+				</uni-popup-dialog>
+			</uni-popup>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '../../../common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		apiCardDetailQuery,
+		apiOnlineMcSignUpDetail,
+		apiOnlineMcSignUp,
+		apiMatchRsDetailQuery,
+		checkResCode,
+		checkToken
+	} from '../../../common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "signup",
+				firstEnterKey: 'firstEnter-tpl-style2',
+				rankKey: "rank-tpl-style2",
+				queryObj: {},
+				queryString: "",
+				from: "", // 来源页面
+				token: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				coiId: 0, // 已报名单位id
+				coiName: "", // 已报名单位名称,可为空
+				teamNum: 0, // 已报名队伍编号,可为0
+				nickName: "", // 昵称
+				coiRs: [], // 组织信息集合
+				// orgList: [], // 分组下拉列表数据源
+				// teamList: [], // 
+				interval: null,
+
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				// teamType: 0, // 队伍类型  0: 红黄蓝紫 1: 学生/家长
+				introduce: {
+					title: "",
+					content: ""
+				},
+				activityRules: {
+					title: "",
+					content: ""
+				},
+				configParam: {
+					labelName: "昵称",
+					labelOrg: "组织",
+					subTitle: ""
+				},
+
+				eSelectionProps: {
+					text: 'coiName',
+					value: 'coiId',
+					// disabled: 'noallowed'
+				},
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.from = query["from"] ?? "";
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.getCardDetailQuery();
+			this.matchRsDetailQuery();
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealNotice]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载队伍类型  0: 红黄蓝紫 1: 学生/家长
+				/* if (config.teamType != undefined && config.teamType >= 0) {
+					this.teamType = config.teamType;
+				} */
+
+				// 加载介绍内容
+				const introduce = config.introduce;
+				if (introduce != undefined) {
+					if (introduce.title != undefined) {
+						this.introduce.title = introduce.title;
+					}
+					if (introduce.content != undefined) {
+						this.introduce.content = introduce.content;
+					}
+				}
+
+				// 加载活动规则
+				const activityRules = config.activityRules;
+				if (activityRules != undefined) {
+					if (activityRules.title != undefined) {
+						this.activityRules.title = activityRules.title;
+					}
+					if (activityRules.content != undefined) {
+						this.activityRules.content = activityRules.content;
+					}
+				}
+
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.labelName != undefined && param.labelName.length > 0) {
+						this.configParam.labelName = param.labelName;
+					}
+					if (param.labelOrg != undefined && param.labelOrg.length > 0) {
+						this.configParam.labelOrg = param.labelOrg;
+					}
+					if (param.subTitle != undefined && param.subTitle.length > 0) {
+						this.configParam.subTitle = param.subTitle;
+					}
+				}
+				// console.log("[loadConfig] param:", this.configParam);
+				
+				this.pageReady = true;
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取倒计时
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			fmtMcTime2(timestamp1, timestamp2) {
+				return tools.fmtMcTime2(timestamp1, timestamp2);
+			},
+			// 卡片信息查询
+			getCardDetailQuery() {
+				uni.request({
+					url: apiCardDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getCardDetailQuery", res);
+						const data = res.data.data;
+						this.mcType = data.mcType;
+						this.mcId = data.mcId;
+						this.mcName = data.mcName;
+						this.beginSecond = data.beginSecond;
+						this.endSecond = data.endSecond;
+						this.coiId = data.coiId;
+						this.coiName = data.coiName;
+						this.teamNum = data.teamNum;
+						this.nickName = data.nickName;
+
+						this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+						this.getActtime();
+						this.getOnlineMcSignUpDetail();
+
+						this.clear();
+						this.interval = setInterval(this.getActtime, 60000);
+					},
+					fail: (err) => {
+						console.log("getCardDetailQuery err", err)
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 线上赛报名页面信息详情
+			getOnlineMcSignUpDetail() {
+				uni.request({
+					url: apiOnlineMcSignUpDetail,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId,
+					},
+					success: (res) => {
+						// console.log("getOnlineMcSignUpDetail", res);
+						this.coiRs = res.data.data.coiRs;
+						if (this.nickName == undefined || this.nickName == '') {
+							this.nickName = res.data.data.name;
+						}
+						/* const rsNum = this.coiRs.length;
+						this.orgList = [];
+						for (let i = 0; i < rsNum; i++) {
+							this.orgList[i] = {};
+							this.orgList[i].value = this.coiRs[i].coiId;
+							this.orgList[i].text = this.coiRs[i].coiName;
+							this.orgList[i].teamNum = this.coiRs[i].teamNum;
+						}
+						// console.log("orgList", this.orgList);
+						if (this.coiId > 0) {
+							this.orgChange(this.coiId, false);
+						} */
+					},
+					fail: (err) => {
+						console.log("getOnlineMcSignUpDetail err", err)
+					},
+				});
+			},
+			// 线上赛报名
+			onlineMcSignUp() {
+				uni.request({
+					url: apiOnlineMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId,
+						coiId: this.coiId,
+						selectTeam: this.teamNum,
+						nickName: this.nickName
+					},
+					success: (res) => {
+						// console.log("onlineMcSignUp", res);
+
+						if (checkResCode(res)) {
+							uni.showToast({
+								title: '比赛报名成功!',
+								icon: 'none',
+								duration: 3000
+							});
+
+							const url = '/pages/tpl/style2/rankList?' + this.queryString;
+							tools.appAction(url, "uni.navigateTo");
+						}
+					},
+					fail: (err) => {
+						console.log("onlineMcSignUp err", err);
+						uni.showToast({
+							title: '出错了,报名失败',
+							icon: 'none',
+							duration: 3000
+						});
+					},
+				});
+			},
+			btnBack() {
+				// console.log("from:", this.from)
+				if (this.from != '') {
+					// window.history.back();
+					const url = '/pages/tpl/style2/' + this.from + '?' + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {
+					const url = `action://to_home/`;
+					tools.appAction(url);
+				}
+			},
+			btnInfo() {
+				this.$refs.mypopup.popupOpen();
+			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
+			btnSignup() {
+				if (!checkToken(this.token)) {
+					return;
+				}
+
+				if (!(this.nickName.trim().length > 0)) {
+					uni.showToast({
+						title: `请填写${this.configParam.labelName}`,
+						icon: 'none',
+						duration: 2000
+					});
+					return;
+				}
+
+				/* if (!(this.coiId > 0)) {
+					uni.showToast({
+						title: `请选择${this.configParam.labelOrg}`,
+						icon: 'none',
+						duration: 2000
+					});
+					return;
+				} */
+
+				this.nickName = this.nickName.trim();
+				// this.coiName = tools.getSelectedText(this.orgList, this.coiId);
+				// this.teamName = tools.getSelectedText(this.teamList, this.teamNum);
+
+				this.$refs.alertDialog.open();
+			},
+			dialogConfirm() {
+				this.onlineMcSignUp();
+			},
+			dialogClose() {},
+			// 获取输入框中值
+			getESelectText(data) {
+				// console.log("getESelectText:", data);
+				this.coiName = data;
+			},
+			// 获取选择选项值
+			eSelectChange(data) {
+				// console.log("eSelectChange:", data);
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		min-height: 100vh;
+	}
+
+	.page-top {
+		width: 100%;
+		height: 220px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url('static/backgroud/top_bg_sddx.png');
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: cover;
+	}
+
+	.topbar-color-default {
+		color: #333333;
+	}
+
+	.topcontent {
+		height: 90%;
+		margin-bottom: 30px;
+	}
+
+	.logo {
+		width: 80px;
+		height: 80px;
+		margin-top: 10px;
+		/* background-image: url('/static/logo/sddx.png'); */
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.top-acttime {
+		display: none;
+		text-shadow: 3px 3px 0px #640008;
+		font-family: YouSheBiaoTiHei;
+		/* font-family: Arial, Helvetica, sans-serif; */
+		font-weight: bold;
+		color: #ffee0b;
+		font-size: 26px;
+	}
+
+	.mcName {
+		font-size: 40rpx;
+		font-weight: 550;
+	}
+
+	.main {
+		width: 76%;
+		flex-grow: 1;
+	}
+
+	.timebar {
+		min-width: 68%;
+		height: 32px;
+		margin-top: -17px;
+		padding: 0 15px;
+		justify-content: center;
+		background: #ffffff;
+		border: 0.5px solid;
+		border-color: #e7e7e7;
+		border-radius: 20px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+	}
+
+	.acttime {
+		font-weight: 550;
+		color: #333333;
+		font-size: 14px;
+		text-wrap: nowrap;
+	}
+
+	.clock {
+		width: 15px;
+		height: 15px;
+		margin-right: 10px;
+	}
+
+	.uni-input {
+		width: 90%;
+		height: 34px;
+		margin-top: 15px;
+		margin-bottom: 15px;
+		padding: 0 13px;
+		/* background: #f1f1f1; */
+		border: 1px solid #dcdfe6;
+		border-radius: 4px;
+		font-size: 14px;
+	}
+
+	.input-placeholder {
+		color: #333333;
+		font-size: 16px;
+	}
+
+	.introduce {
+		width: 100%;
+		margin-top: 12px;
+		margin-bottom: 10px;
+		align-items: flex-start;
+		justify-content: space-around;
+	}
+
+	.introduce-title {
+		color: #333333;
+		font-size: 15px;
+		line-height: 30px;
+		font-family: Source Han Sans CN;
+	}
+
+	.introduce-content {
+		color: #333333;
+		font-size: 14px;
+		line-height: 23px;
+		font-family: Source Han Sans CN;
+	}
+
+	.activityRules {
+		width: 100%;
+		margin-top: 5px;
+		margin-bottom: 10px;
+		padding: 10px 15px;
+		align-items: flex-start;
+		justify-content: space-around;
+		border-radius: 9px;
+		background: #EBEBEB;
+	}
+
+	.activityRules-title {
+		color: #333333;
+		font-size: 14px;
+		line-height: 25px;
+		font-weight: 500;
+		font-family: Source Han Sans CN;
+	}
+
+	.activityRules-content {
+		color: #333333;
+		font-size: 13px;
+		line-height: 23px;
+		font-family: Source Han Sans CN;
+	}
+
+	.btnSignup {
+		width: 100%;
+		height: 100rpx;
+		margin-top: 30rpx;
+		margin-bottom: 30rpx;
+		color: white;
+		font-weight: bold;
+		line-height: 100rpx;
+		border-radius: 55rpx;
+	}
+
+	.bgcolor-main {
+		background-color: #81cd00;
+	}
+
+	.btnSignup-disable {
+		background-color: #c3c3c3;
+	}
+
+	.dialog-content {
+		width: 280px;
+		height: 110px;
+		padding: 0 20px;
+		background: #f1f1f1;
+		border-radius: 9px;
+		justify-content: center;
+		text-align: center;
+		font-weight: 550;
+		color: #333333;
+	}
+
+	.dialog-content-1 {
+		font-size: 34rpx;
+		margin-bottom: 30rpx;
+	}
+
+	.dialog-content-2 {
+		font-size: 28rpx;
+		margin-bottom: 10rpx;
+		text-align: left;
+	}
+</style>

+ 259 - 0
card/pages/tpl/style3/cardconfig/test.js

@@ -0,0 +1,259 @@
+export const localCardConfig = `{
+	"common": {
+		"css": "
+			.color-main {
+				color: #ff870e !important;
+			}
+			.bgcolor-main {
+				background-color: #ff870e !important;
+			}
+			.tab-active{
+				background-color: #a43a07 !important;
+			}
+			.swiper-item-button {
+				background-color: #ff870e !important;
+			}
+			.uni-swiper-dot-active {
+				background: #ff870e !important;
+			}
+			.topbar-color {
+				color: #ffffff !important;
+			}
+			.topbar-rule {
+				margin-left: 3px;
+				color: #FFFFFF;
+				border-radius: 4px;
+				background: #FF870D;
+			}
+			.page-top {
+				height: 220px !important;
+				background-image: url('static/backgroud/top_bg_sddx.png') !important;
+			}
+		",
+		"tabActiveColor": "#FF870D",
+		"popupRuleConfig": {
+			"height": "530px"
+		},
+		"popupRuleList": [{
+				"type": 1,
+				"data": {
+					"title": "小飞龙定向赛",
+					"img": "/static/common/egg.png",
+					"content": "济南奥体中心“一场三馆”包括体育场、体育馆、网球馆和游泳馆,呈现出“东荷西柳”的总体布局。 体育场以济南的“市树”柳树为母题,将垂柳柔美飘逸的形态固化为建筑语言。"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "规则",
+					"img": "/static/common/guize.png"
+				}
+			},
+			{
+				"type": 2,
+				"data": {
+					"title": "奖励",
+					"img": "/static/common/jiangli.png"
+				}
+			},
+			"default2"
+		],
+		"popupExchgConfig": {
+			"height": "460px"
+		},
+		"popupExchgList": [
+		],
+		"popupMessageConfig": {
+			"height": "500px"
+		},
+		"popupWarnConfig": {
+			"height": "550px"
+		},
+		"popupHelpConfig": {
+			"height": "539px"
+		},
+		"popupHelpList": [
+		]
+	},
+	"index": {
+		"css": "
+			.content-bg{
+				background: url('static/cardbg/xfl5.png') !important;
+				background-size: cover !important;
+			}
+			.logo{
+				width: 40vw !important;
+				height: 40vw !important;
+				background: url('static/logo/xfl2.png') no-repeat center !important;
+				background-size: contain !important;
+			}
+			.mod-text{
+				color: #000000 !important;
+			}
+			.mod-button{
+				color: #ffffff !important;
+				background-color: #9d4f00 !important;
+			}
+		"
+	},
+	"signup": {
+		"css": "
+			.page-top {
+				height: 200px !important;
+			}
+		",
+		"introduce": {
+			"title": "介绍:",
+			"content": " · 小飞龙定向赛再次来袭!这次有五个场地哟~<br> · 神秘“蛋叔”闪亮登场~<br> · 蛋叔放大招,百味豆换鸡蛋!<br> · 时不可待!冲鸭!<br><br> · 能不能把蛋叔整郁闷,就看你的啦~<br> · 还等啥?火速报名吧!"
+		},
+		"activityRules": {
+			"title": "活动规则:",
+			"content": "<li>随时参赛、不限完赛次数、起点任选、实时排名 <li>起点 -各途经点 -结束点完整打卡为一次有效完赛"
+		}
+	},
+	"rankList": {
+		"css": "
+			.topbtm-name{
+				background-color: #c77f34 !important;
+				color: #ffffff !important;
+			}
+			.topbtm-egg{
+				background-color: #c6690a !important;
+				color: #ffffff !important;
+			}
+			.main-bar{
+				display: flex !important;
+				background-color: #FFEDDB !important;
+				color: #ff870d !important;
+			}
+		",
+		"param": {
+			"subTitle": "“筑梦宿州·创赢未来 ”特训营",
+			"labelRightAnswerNum": "正确答题",
+			"labelTicketName": "",
+			"labelAwardAddress": "",
+			"labelGoodsList": "兑换商品",
+			"tab1InitActIndex": 0,
+			"tab2InitActIndex": 0
+		},
+		"rankParam": {
+			"dispArrStr": "teamCp,teamDistance,teamRightAnswerPer,teamSpeed,regionCp,regionDistance,regionRightAnswerPer,regionSpeed",
+			"tab2Items_team": ["累计积分", "总里程", "正确答题", "单场用时"],
+			"tab2Items_person_region": ["累计积分", "总里程", "正确答题", "单场用时"],
+			"rankTypeList_team": ["totalCp", "totalDistance", "rightAnswerPer", "speed"],
+			"rankTypeList_person_region": ["totalCp", "totalDistance", "rightAnswerPer", "speed"],
+			"rank1List": ["teamCpRs", "teamDistanceRs", "teamRightAnswerPerRs", "teamSpeedRs"],
+			"rank2List": ["regionCpRs", "regionDistanceRs", "regionRightAnswerPerRs", "regionSpeedRs"]
+		}
+	},
+	"rankOverview": {
+		"css": "
+			.page-top {
+				height: 230px !important;
+			}
+			.logo{
+				width: 180.5px !important;
+				height: 130.5px !important;
+				background-image: url('static/logo/sddx_jjyjy.png');
+			}
+			.mid {
+				margin-top: -50px !important;
+			}
+			.top-acttime{
+				display: flex !important;
+				font-size: 20px !important; 
+				line-height: 30px !important;
+			}
+			.topbar-color{
+				color: #ffffff;
+			}
+			.midType0{
+				margin-top: -20px !important;
+			}
+			.e-select{
+				background-color: #a43a07 !important;
+			}
+			.main-title {
+				display: none !important;
+			}
+		",
+		"pathList": {
+			"row1": [{
+					"type": 3,
+					"pathImg": "/static/common/lingxiucheng.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "领秀城起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/quanchenggongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "泉城公园起始点"
+					}
+				}
+			],
+			"row2": [{
+					"type": 3,
+					"pathImg": "/static/common/baihuagongyuan.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "百花公园起始点"
+					}
+				},
+				{
+					"type": 3,
+					"pathImg": "/static/common/aotizhongxin.png",
+					"path": {
+						"ocaId": 1,
+						"mcType": 3
+					},
+					"navImg": "/static/common/nav.png",
+					"point": {
+						"longitude": 117.022194,
+						"latitude": 36.661612,
+						"name": "奥体中心起始点"
+					}
+				}
+			],
+			"row3": [{
+				"type": 3,
+				"pathImg": "/static/common/muniushan.png",
+				"path": {
+					"ocaId": 1,
+					"mcType": 3
+				},
+				"navImg": "/static/common/nav.png",
+				"point": {
+					"longitude": 117.022194,
+					"latitude": 36.661612,
+					"name": "牧牛山起始点"
+				}
+			}]
+		},
+		"pathListStyle" : {
+			"showLine" : true,
+			"style": "justify-content: flex-start;"
+		}
+	}
+}`;

+ 402 - 0
card/pages/tpl/style3/index.vue

@@ -0,0 +1,402 @@
+<!-- 
+[模板] 样式3 - 卡片页
+http://localhost:5173/card/#/pages/tpl/style3/index
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style3/index
+ -->
+<template>
+	<view class="body body-radius">
+		<view v-if="pageReady" class="content content-bg" @click="btnClick">
+			<view class="card-top uni-row">
+				<view class="top-right uni-row">
+					<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
+					<view class="countdown">{{countdown}}</view>
+				</view>
+			</view>
+			<view class="card-main uni-column">
+				<view class="logo"></view>
+				<view class="uni-row" style="position: relative;">
+					<image v-if="notice" mode="aspectFit" src="/static/common/notice.png" class="notice"></image>
+					<text class="type mod-text">{{type}}</text>
+				</view>
+				<view class="name mod-text">{{ecName}}</view>
+				<button class="button mod-button">{{btnText}}</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '../../../common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		ossUrl,
+		apiCardBaseQuery,
+		apiUserJoinCardQuery,
+		apiMatchRsDetailQuery
+	} from '../../../common/api';
+	
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "index",
+				rankKey: "rank-tpl-style3",
+				queryObj: {},
+				queryString: "",
+				token: "",
+				
+				ecId: 0, // 卡片id
+				ecName: '', // 卡片名称
+				ecDesc: '', // 卡片简介
+				beginSecond: null, // 卡片开始时间戳,单位秒
+				endSecond: null, // 卡片结束时间戳,单位秒
+				secondCardName: '', // 跳转页面名称
+				
+				isJoin: null, // 是否报名
+				isFinished: false, // 赛事是否结束
+				
+				// mcId: 0, // 赛事id
+				// mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				// mcName: "", // 赛事名称
+				
+				countdown: "", // 倒计时
+				interval: null,
+				
+				type: "",
+				btnText: "",
+				notice: false,	// 是否显示(小红点)通知图标
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			
+			this.type = query["type"] ?? "锦标赛";
+			this.btnText = query["btnText"] ?? "开始比赛";
+			
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+			
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.getCardBaseQuery();
+			this.matchRsDetailQuery();
+		},
+		onShow() {
+			this.getUserJoinCardQuery();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealNotice]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							that.notice = true;
+							// that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						that.notice = true;
+						// that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+				
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = "距结束 " + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "已结束";
+						this.isFinished = true;
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 卡片基本信息查询
+			getCardBaseQuery() {
+				uni.request({
+					url: apiCardBaseQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						pageName: this.pageName
+					},
+					success: (res) => {
+						// console.log("getCardBaseQuery", res)
+						const data = res.data.data;
+						
+						this.ecName = data.ecName;
+						this.ecDesc = data.ecDesc;
+						this.beginSecond = data.beginSecond;
+						this.endSecond = data.endSecond;
+						this.secondCardName = data.secondCardName;
+						
+						this.getCountdown();
+						
+						this.clear();
+						this.interval = setInterval(this.getCountdown, 60000);
+					},
+					fail: (err) => {
+						console.log("getCardBaseQuery err", err)
+					},
+				});
+			},
+			// 用户是否已经报名卡片对应赛事查询
+			getUserJoinCardQuery() {
+				uni.request({
+					url: apiUserJoinCardQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getUserJoinCardQuery", res)
+						const code = res.data.code;
+						const data = res.data.data;
+						if (code == 0) {
+							this.isJoin = data.isJoin;
+							/*if (this.isJoin) {
+								this.btnText = "已报名";
+							} else {
+								this.btnText = "未报名";
+							} */
+						}
+					},
+					fail: (err) => {
+						console.log("getUserJoinCardQuery err", err)
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			btnClick() {
+				if (this.isJoin) {	// 已报名
+					const url = `${ossUrl}#/pages/tpl/style3/rankList?${this.queryString}&full=true`;
+					tools.appAction(url);
+				}
+				else {	// 未报名
+					if (!this.isFinished) {	// 赛事未结束
+						if (this.secondCardName == 'rankList') {
+							const url = `${ossUrl}#/pages/tpl/style3/rankList?${this.queryString}&full=true`;
+							tools.appAction(url);
+						} else {
+							const url = `${ossUrl}#/pages/tpl/style3/signup?${this.queryString}&full=true`;
+							tools.appAction(url);
+						}
+					}
+					else {	// 赛事已结束
+						// const url = `${ossUrl}#/pages/tpl/style3/rankOverview?${this.queryString}&full=true`;
+						const url = `${ossUrl}#/pages/tpl/style3/rankList?${this.queryString}&full=true`;
+						tools.appAction(url);
+					}
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+	}
+
+	.content-bg {
+		background: linear-gradient(180deg, #7aedff 0%, #047200 100%);
+		/* background: linear-gradient(180deg, #178bff 0%, #004d9b 100%); */
+		/* background: linear-gradient(180deg, #7aedff 0%, #8d2219 100%); */
+	}
+	
+	.card-top {
+		width: 100%;
+		justify-content: flex-end;
+	}
+
+	.top-right {
+		min-width: 180rpx;
+		height: 80rpx;
+		margin-top: 30rpx;
+		margin-right: 30rpx;
+		padding-left: 16rpx;
+		padding-right: 16rpx;
+		background-color: rgba(0, 0, 0, 0.3);
+		border-radius: 12px;
+	}
+
+	.clock {
+		width: 50rpx;
+		height: 50rpx;
+		margin-right: 12rpx;
+	}
+
+	.countdown {
+		min-width: 120rpx;
+		text-align: center;
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 46rpx;
+		/* letter-spacing: 2rpx; */
+	}
+
+	.card-main {
+		width: 100%;
+		/* height: 700rpx; */
+		height: 660rpx;
+		margin-top: 20rpx;
+		justify-content: space-evenly;
+	}
+
+	.logo {
+		width: 50vw;
+		height: 50vw;
+		background-image: url('/static/logo/jbs.png');
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.notice {
+		width: 30rpx;
+		height: 30rpx;
+		position: absolute;
+		left: -60rpx;
+	}
+	
+	.type {
+		opacity: 60%;
+		/* line-height: 25px; */
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 40rpx;
+		text-align: center;
+	}
+
+	.name {
+		font-family: Roboto;
+		color: #ffffff;
+		font-size: 50rpx;
+		text-align: center;
+	}
+
+	.button {
+		width: 320rpx;
+		height: 86rpx;
+		margin-top: 30rpx;
+		color: #000000;
+		background: #ffffff;
+		border-radius: 56rpx;
+		font-size: 46rpx;
+		line-height: 80rpx;
+	}
+	
+	/* .mod-text {
+		color: #FF6203 !important;
+	}
+	
+	.mod-button {
+		color: #ffffff !important;
+		background-color: #FF6203 !important;
+	} */
+	
+</style>

+ 922 - 0
card/pages/tpl/style3/rankList.vue

@@ -0,0 +1,922 @@
+<!-- 
+[模板] 样式2 - 排名列表
+http://localhost:5173/card/#/pages/tpl/style3/rankList
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style3/rankList
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color" :showMessage="cardConfigData.popupMessageList.length > 0"
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo" @btnMessageClick="btnMessage"></my-topbar>
+
+				<view class="topcontent uni-column uni-jcsa">
+					<view class="tcview uni-row uni-jcsb">
+						<view class="tcbox uni-column uni-jcc">
+							<text class="tcbox-label">赛事总里程</text>
+							<text class="tcbox-value">{{fmtDistanct(all_totalDistance)}} 千米</text>
+						</view>
+						<view class="tcbox uni-column uni-jcc">
+							<text class="tcbox-label">{{configParam.labelRightAnswerNum}}</text>
+							<text class="tcbox-value">{{all_totalAnswerNum}} 次</text>
+						</view>
+					</view>
+					<text class="today" :data-content="today">{{today}}</text>
+				</view>
+				
+				<view class="topcontent2 uni-column uni-jcsa">
+					<view class="logo"></view>
+					<text v-if="configParam.subTitle && configParam.subTitle.length > 0" class="top-acttime">{{configParam.subTitle}}</text>
+				</view>
+
+				<!-- <view class="topcontent uni-column uni-jcsa">
+					<view class="logo"></view>
+					<view class="tcbar uni-row uni-jcsb">
+						<text class="tcbar-text">赛事总里程:{{fmtDistanct(all_totalDistance)}} 千米</text>
+						<text class="tcbar-text">{{configParam.labelRightAnswerNum}}:{{all_totalAnswerNum}} 次</text>
+					</view>
+				</view> -->
+
+				<view class="topbtm uni-row">
+					<view class="topbtm-egg" @click="btnMyEgg">{{configParam.labelTicketName}}</view>
+					<text class="topbtm-name">{{nickName}}</text>
+					<view class="topbtm-egg" v-if="configParam.labelGoodsList && configParam.labelGoodsList.length > 0" @click="btnGoodsList">{{configParam.labelGoodsList}}</view>
+					<view class="topbtm-egg" v-else @click="btnExchg">{{configParam.labelAwardAddress}}</view>
+				</view>
+			</view>
+			<view class="main uni-column">
+				<view class="main-bar uni-row uni-jcse">
+					<text>题目输出:{{all_totalAnswerNum}}</text>
+					<text>总里程:{{fmtDistanct(all_totalDistance)}}km</text>
+					<text>总打点:{{all_totalCp}}</text>
+					<text>总百味豆:{{all_totalSysPoint}}</text>
+				</view>
+				
+				<my-tab ref="tab1" :tabItems="tab1Items" :type="1" @onTabClick="onTab1Click"
+					:initActIndex=configParam.tab1InitActIndex @onSelectChange="onSelectChange" :fontSize="14"></my-tab>
+				<my-tab ref="tab2" :tabItems="tab2Items" :tabItemsMark="tab2ItemsMark" :type="0"
+					:initActIndex=configParam.tab2InitActIndex @onTabClick="onTab2Click" :fontSize="12"></my-tab>
+
+				<view class="tab-view uni-column">
+					<!-- 团体成绩列表 -->
+					<template v-if="tab1Current==0" v-for="(item, index) in rank1List" :key="index">
+						<my-ranklist v-show="tab2Current === index" :rankRs="rankList[item]"
+							:rank-type="rankTypeList[index]"></my-ranklist>
+					</template>
+
+					<!-- 个人成绩区域排名列表 -->
+					<template v-if="tab1Current==1" v-for="(item, index) in rank2List" :key="index">
+						<my-ranklist v-show="tab2Current === index" :rankRs="rankList[item]"
+							:rank-type="rankTypeList[index]"></my-ranklist>
+					</template>
+				</view>
+
+				<button class="btnBack bgcolor-main" @click="btnStartGame">{{btnStartGameText}}</button>
+			</view>
+
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+			<my-popup ref="mypopupExchg" :config="cardConfigData.popupExchgConfig" :dataList="cardConfigData.popupExchgList"></my-popup>
+			<my-popup ref="mypopupMessage" :config="cardConfigData.popupMessageConfig" :dataList="cardConfigData.popupMessageList" @noMoreRemindersClick="onNoMoreRemindersClick"></my-popup>
+			<my-popup ref="mypopupWarn" :config="cardConfigData.popupWarnConfig" :dataList="cardConfigData.popupWarnList"></my-popup>
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import { teamName } from '/common/define';
+	import {
+		token,
+		apiMatchRsDetailQuery,
+		apiCardRankDetailQuery,
+		apiUserCurrentRankNumQuery,
+		apiIsAllowMcSignUp,
+		apiUserJoinCardQuery,
+		apiMapListQuery,
+		apiCompStatisticQuery,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "rankList",
+				firstEnterKey: 'firstEnter-tpl-style3',
+				rankKey: "rank-tpl-style3",
+				mapKey: "rank-tpl-style3-map",
+				messageKey: "message-tpl-style3",
+				queryObj: {},
+				queryString: "",
+				token: "",
+				ovtype: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				ocaId: 0, // 关联id,带入到App活动详情页面
+				nickName: "", // 昵称
+				
+				isJoin: null, // 是否报名
+				btnStartGameText: "",
+				today: "",
+
+				mapList: [], // 卡片对应地图列表详情
+				all_totalDistance: 0, // 赛事所有人累计里程,单位米
+				all_totalRightAnswerNum: 0, // 赛事所有人正确答题数(校园文化输出)
+				all_totalAnswerNum: 0, // 赛事所有人答题数(校园文化输出)
+				all_totalCp: 0, // 赛事中所有人打点数
+				all_totalSysPoint: 0, // 赛事中所有人百味豆
+
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				allowMcSignUp: false, // 是否允许重新分组
+				countdown: "", // 倒计时
+				rankList: { // 排名列表
+					// totalDistanceRs: [],
+					// totalCpRs: [],
+					// totalSysPointRs: [],
+					// fastPaceRs: []
+				},
+				interval: null,
+
+				teamType: 0, // 队伍类型
+				// dispArrStr: "totalDistance,totalCp,totalSysPoint,fastPace",
+				dispArrStr: "teamCp,teamTodayCp,teamDistance,teamRightAnswerPer,teamTodayPace,regionCp,regionTodayCp,regionDistance,regionRightAnswerPer,regionTodayPace", // 要显示的集合范围
+				tab1Current: 0,
+				tab2Current: 0,				
+				tab1Items: ["团体"],
+				// tab1Items: ["团体", {
+				// 	selectValue: 1,
+				// 	data: [
+				// 		{text: "个人(中心校区)", value: 1},
+				// 		{text: "个人(千佛山校区南区)", value: 2},
+				// 		{text: "个人(兴隆校区)", value: 3}
+				// 	]}
+				// ],
+				tab2Items: [],
+				tab2Items_team: ["总积分", "今日积分", "总里程", "校园文化", "今日配速"],
+				tab2Items_person_region: ["总积分", "今日积分", "总里程", "校园文化", "今日配速"],
+				tab2ItemsMark: [{
+					textColor: "#ff6203",
+					icon: "static/common/award.png"
+				}],
+				tabActiveColor: "#81cd00",
+
+				rankTypeList: [],
+				// 团队成绩类型列表
+				rankTypeList_team: ["totalScore", "totalScore", "totalDistance", "rightAnswerPer", "fastPace"],
+				// 个人成绩类型列表
+				rankTypeList_person_region: ["totalScore", "totalScore", "totalDistance", "rightAnswerPer", "fastPace"],
+				// 团体成绩列表
+				rank1List: ["teamCpRs", "teamTodayCpRs", "teamDistanceRs", "teamRightAnswerPerRs", "teamTodayPaceRs"],
+				// 个人成绩列表
+				rank2List: ["regionCpRs", "regionTodayCpRs", "regionDistanceRs", "regionRightAnswerPerRs", "regionTodayPaceRs"],
+
+				configParam: {
+					subTitle: "",
+					labelRightAnswerNum: "文化输出",
+					labelTicketName: "我的奖券",
+					labelAwardAddress: "兑奖地址",
+					// labelGoodsList: "兑换商品",
+					tab1InitActIndex: 0,
+					tab2InitActIndex: 0
+				},
+				// selectedMapId: 0, // 用户选择的地图ID
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+			this.ovtype = query["ovtype"] ?? "";
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			this.mapKey += "-" + this.ecId;
+			console.log("mapKey:", this.mapKey);
+
+			this.messageKey += "-" + this.ecId;
+			console.log("messageKey:", this.messageKey);
+			
+			this.today = tools.timestampToTime(null, 2);
+
+			const mapValue = uni.getStorageSync(this.mapKey);
+			if (mapValue) {
+				console.log("mapValue:", mapValue);
+				// this.selectedMapId = mapValue;
+				this.ocaId = mapValue;
+			}
+			
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+			// this.tab2Items = this.tab2Items_team;
+		},
+		onShow() {
+			this.getUserJoinCardQuery();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			initTab() {
+				if (this.tab1Current == 0) {
+					this.rankTypeList = this.rankTypeList_team;
+					this.tab2Items = this.tab2Items_team;
+				} else {
+					this.rankTypeList = this.rankTypeList_person_region;
+					this.tab2Items = this.tab2Items_person_region;
+				}
+			},
+			dealNotice(rank) {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				cardfunc.unReadMessageQuery(this.unReadMessageQueryCallback);
+				cardfunc.warnMessageQuery(this.warnMessageQueryCallback);
+				this.matchRsDetailQuery();
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			unReadMessageQueryCallback(unReadMessage, mqIdListStr) {
+				// console.log("[unReadMessageQueryCallback] unReadMessage", unReadMessage);
+				if (unReadMessage.length > 0) {
+					this.messageKey += mqIdListStr;
+					// console.log("[unReadMessageQueryCallback] messageKey:", this.messageKey);
+					const messageValue = uni.getStorageSync(this.messageKey);
+					// console.log("[unReadMessageQueryCallback] messageValue:", messageValue);
+					if (!messageValue) {
+						this.$refs.mypopupMessage.popupOpen();
+						// uni.setStorageSync(this.messageKey, true);
+					}
+				}
+			},
+			warnMessageQueryCallback(warnMessage) {
+				// console.log("[warnMessageQueryCallback] warnMessage", warnMessage);
+				if (warnMessage.length > 0) {
+					this.$refs.mypopupWarn.popupOpen();
+				}
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载成绩参数
+				const rankParam = config.rankParam;
+				if (rankParam != undefined) {
+					if (rankParam.tab2ItemsMark != undefined) {
+						this.tab2ItemsMark = rankParam.tab2ItemsMark;
+					}
+					if (rankParam.dispArrStr != undefined && rankParam.dispArrStr.length > 0) {
+						this.dispArrStr = rankParam.dispArrStr;
+						// console.log("[loadConfig] dispArrStr:", rankParam.dispArrStr);
+					}
+					if (rankParam.tab2Items_team != undefined && rankParam.tab2Items_team.length > 0) {
+						this.tab2Items_team = rankParam.tab2Items_team;
+						// console.log("[loadConfig] tab2Items_team:", rankParam.tab2Items_team);
+					}
+					if (rankParam.tab2Items_person_region != undefined && rankParam.tab2Items_person_region.length > 0) {
+						this.tab2Items_person_region = rankParam.tab2Items_person_region;
+					}
+					if (rankParam.rankTypeList_team != undefined && rankParam.rankTypeList_team.length > 0) {
+						this.rankTypeList_team = rankParam.rankTypeList_team;
+					}
+					if (rankParam.rankTypeList_person_region != undefined && rankParam.rankTypeList_person_region.length > 0) {
+						this.rankTypeList_person_region = rankParam.rankTypeList_person_region;
+					}
+					if (rankParam.rank1List != undefined && rankParam.rank1List.length > 0) {
+						this.rank1List = rankParam.rank1List;
+					}
+					if (rankParam.rank2List != undefined && rankParam.rank2List.length > 0) {
+						this.rank2List = rankParam.rank2List;
+					}
+				}
+				// console.log("[loadConfig] rankParam:", rankParam);
+				
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.subTitle != undefined && param.subTitle.length > 0) {
+						this.configParam.subTitle = param.subTitle;
+					}
+					if (param.labelRightAnswerNum != undefined && param.labelRightAnswerNum.length > 0) {
+						this.configParam.labelRightAnswerNum = param.labelRightAnswerNum;
+					}
+					if (param.labelTicketName != undefined && param.labelTicketName.length > 0) {
+						this.configParam.labelTicketName = param.labelTicketName;
+					}
+					if (param.labelAwardAddress != undefined && param.labelAwardAddress.length > 0) {
+						this.configParam.labelAwardAddress = param.labelAwardAddress;
+					}
+					if (param.labelGoodsList != undefined && param.labelGoodsList.length > 0) {
+						this.configParam.labelGoodsList = param.labelGoodsList;
+					}
+					if (param.tab1InitActIndex != undefined && param.tab1InitActIndex >= 0) {
+						this.configParam.tab1InitActIndex = param.tab1InitActIndex;
+						this.tab1Current = param.tab1InitActIndex;
+					}
+					if (param.tab2InitActIndex != undefined && param.tab2InitActIndex >= 0) {
+						this.configParam.tab2InitActIndex = param.tab2InitActIndex;
+						this.tab2Current = param.tab2InitActIndex;
+					}
+				}
+				this.initTab();
+				// console.log("[loadConfig] param:", this.configParam);
+				
+				this.pageReady = true;
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = '距结束 ' + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "活动已结束";
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 格式化 距离
+			fmtDistanct(val) {
+				// return Math.round(val * 100 / 1000) / 100;
+				if (val < 10000)
+					return Math.round(val * 100 / 1000) / 100;
+				else
+					return Math.round(val / 1000);
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取活动时间
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			getTeamName(teamType, teamIndex) {
+				return teamName[teamType][teamIndex];
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (checkResCode(res)) {
+							const data = res.data.data;
+							this.mcType = data.mcType;
+							this.mcId = data.mcId;
+							this.mcName = data.mcName;
+							this.beginSecond = data.beginSecond;
+							this.endSecond = data.endSecond;
+							this.nickName = data.nickName;
+
+							this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+							this.getCountdown();
+							this.getActtime();
+							this.mapListQuery();
+							this.compStatisticQuery();
+							// this.getCardRankDetailQuery();
+
+							this.clear();
+							this.interval = setInterval(this.getCountdown, 60000);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 卡片对应地图列表详情查询
+			mapListQuery() {
+				uni.request({
+					url: apiMapListQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("mapListQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.mapList = data;
+
+							let mapItems = {
+								// selectValue: this.selectedMapId,
+								selectValue: this.ocaId,
+								data: []
+							};
+							for (var i = 0; i < data.length; i++) {
+								if (mapItems.selectValue == 0 && i == 0) {
+									mapItems.selectValue = data[i].ocaId;
+									// this.selectedMapId = data[i].ocaId;
+									this.ocaId = data[i].ocaId;
+								}
+								let map = {};
+								map.text = "个人(" + data[i].mapName + ")";
+								map.value = data[i].ocaId;
+								mapItems.data.push(map);
+							}
+							this.tab1Items.push(mapItems);
+							this.getCardRankDetailQuery();
+						}
+					},
+					fail: (err) => {
+						console.log("mapListQuery err", err);
+					},
+				});
+			},
+			// 赛事总成绩统计查询
+			compStatisticQuery() {
+				uni.request({
+					url: apiCompStatisticQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("compStatisticQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.all_totalDistance = data.totalDistance;
+							this.all_totalRightAnswerNum = data.totalRightAnswerNum;
+							this.all_totalAnswerNum = data.totalAnswerNum;
+							this.all_totalCp = data.totalCp;
+							this.all_totalSysPoint = data.totalSysPoint;
+						}
+					},
+					fail: (err) => {
+						console.log("compStatisticQuery err", err);
+					},
+				});
+			},
+			// 排名查询
+			getCardRankDetailQuery() {
+				uni.request({
+					url: apiCardRankDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcIdListStr: this.mcId,
+						mcType: this.mcType,
+						ocaId: this.ocaId,
+						dispArrStr: this.dispArrStr
+					},
+					success: (res) => {
+						// console.log("getCardRankDetailQuery", res);
+						const rankdata = res.data.data;
+						this.rankList = rankdata;
+					},
+					fail: (err) => {
+						console.log("getCardRankDetailQuery err", err);
+					},
+				});
+			},
+			// 是否允许重新分组(报名)
+			isAllowMcSignUp() {
+				uni.request({
+					url: apiIsAllowMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("isAllowMcSignUp", res)
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.allowMcSignUp = data.allowSignUp;
+						}
+					},
+					fail: (err) => {
+						console.log("isAllowMcSignUp err", err)
+					},
+				});
+			},
+			// 用户是否已经报名卡片对应赛事查询
+			getUserJoinCardQuery() {
+				uni.request({
+					url: apiUserJoinCardQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getUserJoinCardQuery", res)
+						const code = res.data.code;
+						const data = res.data.data;
+						if (code == 0) {
+							this.isJoin = data.isJoin;
+							if (this.isJoin) { // 已报名
+								// this.btnStartGameText = "我要比赛";
+								this.btnStartGameText = "选择场地";
+							} else {	// 未报名
+								this.btnStartGameText = "我要报名";
+							}
+						}
+					},
+					fail: (err) => {
+						console.log("getUserJoinCardQuery err", err)
+					},
+				});
+			},
+			onNoMoreRemindersClick() {
+				this.$refs.mypopupMessage.popupClose();
+				uni.setStorageSync(this.messageKey, true);
+			},
+			btnBack() {
+				// window.history.back();
+				const url = `action://to_home/`;
+				tools.appAction(url);
+			},
+			btnStartGame() {
+				if (this.isJoin) {	// 已报名
+					const url = "/pages/tpl/style3/rankOverview?" + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {	// 未报名
+					const url = "/pages/tpl/style3/signup?" + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				}
+			},
+			btnInfo() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopup.popupOpen();
+			},
+			btnMessage() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopupMessage.popupOpen();
+			},
+			btnMyEgg() {
+				const url = "/pages/achievement/index2?tabCurrent=2&" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnExchg() {
+				this.$refs.mypopupExchg.popupOpen();
+			},
+			btnGoodsList() {
+				this.queryObj.from = "/pages/tpl/style3/rankList";
+				this.queryString = tools.objectToQueryString(this.queryObj);
+				const url = "/pages/exchange/style1/goodsList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			onTab1Click(val) {
+				console.log("onTab1Click: ", val);
+				this.tab1Current = val;
+				this.initTab();
+			},
+			onTab2Click(val) {
+				// console.log("onTab2Click: ", val);
+				this.tab2Current = val;
+			},
+			onSelectChange(val) {
+				// console.log("onSelectChange: ", val);
+				this.ocaId = val.value;
+				this.getCardRankDetailQuery();
+
+				uni.setStorageSync(this.mapKey, this.ocaId);
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		height: 100vh;
+	}
+
+	.page-top {
+		width: 100%;
+		height: 170px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url('static/backgroud/top_bg_sddx.png');
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: cover;
+	}
+
+	.topcontent {
+		width: 90%;
+		/* height: 90%; */
+		/* margin-bottom: 20px; */
+	}
+
+	.topcontent2 {
+		width: 90%;
+		display: none;
+	}
+	
+	.logo {
+		width: 80px;
+		height: 80px;
+		margin-top: 10px;
+		margin-bottom: 10px;
+		/* background-image: url('/static/logo/sddx.png'); */
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.top-acttime {
+		text-shadow: 3px 3px 0px #640008;
+		font-family: YouSheBiaoTiHei;
+		/* font-family: Arial, Helvetica, sans-serif; */
+		font-weight: bold;
+		color: #ffee0b;
+		font-size: 20px;
+	}
+	
+	.tcview {
+		width: 90%;
+		/* height: 100px; */
+	}
+
+	.tcbox {
+		width: 121px;
+		height: 63px;
+		opacity: 1;
+		border-radius: 6px;
+		background: #9A300E;
+		border: 1px solid #D3A254;
+	}
+
+	.tcbox-label {
+		font-size: 12px;
+		font-weight: 500;
+		line-height: 23px;
+		color: #f3d809;
+	}
+
+	.tcbox-value {
+		font-size: 16px;
+		font-weight: 500;
+		line-height: 26px;
+		color: #f3d809;
+	}
+	
+	.today {
+		margin-top: 8px;
+		color: #751f00;
+		font-size: 16px;
+		/* font-family: "黑体", sans-serif; */
+		font-weight: 3700;
+		position: relative;
+		z-index: 0;
+	}
+
+	.today::after {
+		content: attr(data-content);
+		-webkit-text-stroke: 3px #DCA452;
+		/* font-family: "黑体", sans-serif; */
+		position: absolute;
+		left: 0;
+		top: 0;
+		z-index: -1;
+	}
+
+	.tcbar {
+		display: none;
+		width: 92%;
+		padding: 6px 12px;
+		background: #9a300e;
+		border-radius: 6px;
+	}
+
+	.tcbar-text {
+		font-family: Source Han Sans CN;
+		font-weight: 500;
+		color: #f3d809;
+		font-size: 13px;
+	}
+
+	.mcName {
+		font-size: 40rpx;
+		font-weight: 550;
+	}
+
+	.topbtm {
+		width: 100%;
+		margin-bottom: 5px;
+		justify-content: space-around;
+	}
+
+	.topbtm-name {
+		padding: 3px 12px;
+		background-color: #9A300E;
+		border: 1px solid #D3A254;
+		border-radius: 6px;
+		text-align: center;
+		font-weight: 500;
+		color: #ffffff;
+		font-size: 14px;
+	}
+
+	.topbtm-egg {
+		width: 60px;
+		padding: 3px 12px;
+		background-color: #9A300E;
+		border: 1px solid #D3A254;
+		border-radius: 6px;
+		text-align: center;
+		color: #ffffff;
+		font-size: 14px;
+	}
+
+	.topbtm-null {
+		width: 60px;
+		padding: 3px 12px;
+	}
+
+	.cal {
+		width: 46rpx;
+		height: 46rpx;
+		margin-right: 20rpx;
+	}
+
+	.main {
+		width: 100%;
+		/* height: 70vh; */
+		flex-grow: 1;
+		justify-content: space-around;
+		/* justify-content: space-between; */
+	}
+	
+	.main-bar {
+		display: none;
+		width: 100%;
+		height: 21px;
+		background-color: #d8e8c6;
+		
+		font-size: 10px;
+		font-weight: 500;
+		color: #3d6706;
+	}
+
+	/* /deep/ .tab-active {
+		background-color: #a43a07 !important;
+	} */
+
+	.main-tab {
+		width: 90%;
+		margin-top: 20rpx;
+	}
+
+	.tab-view {
+		width: 100%;
+		/* height: 69vh; */
+		flex-grow: 1;
+	}
+
+	.btnBack {
+		width: 70%;
+		height: 80rpx;
+		margin-bottom: 20rpx;
+		/* font-weight: bold; */
+		color: white;
+		font-size: 32rpx;
+		line-height: 80rpx;
+		border-radius: 27px;
+		background-color: #2e85ec;
+	}
+
+	/* .swiper-item-button {
+		background-color: #ff870e !important;
+	}
+	
+	.uni-swiper-dot-active {
+		background: #ff870e !important;
+	} */
+</style>

+ 910 - 0
card/pages/tpl/style3/rankOverview.vue

@@ -0,0 +1,910 @@
+<!-- 
+[模板] 样式2 - 排名总览
+http://localhost:5173/card/#/pages/tpl/style3/rankOverview
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style3/rankOverview
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color"
+					@btnBackClick="btnBack" @btnInfoClick="btnInfo"></my-topbar>
+				
+				<view class="topcontent uni-column uni-jcsa">
+					<view class="logo"></view>
+					<text v-if="configParam.subTitle.length > 0" class="top-acttime">{{configParam.subTitle}}</text>
+					<text v-else class="top-acttime">{{fmtMcTime2(beginSecond, endSecond)}}</text>
+				</view>
+			</view>
+			<view v-if="configParam.midType == 0" class="midType0 uni-column">
+				<view class="mid-0 uni-row uni-jcc">
+					<view class="mid-0-select">
+						<e-select v-model="ocaId" :options="mapList" :search="false" :inputClick="true"
+							maxHeight="300px" :clearable="false" @change="eSelectChange"></e-select>
+					</view>
+					<text v-if="cardConfigData.popupHelpList.length > 0" class="mid-0-help" @click="btnHelp">帮助</text>
+				</view>
+				<view class="midType0-mid-1 uni-row uni-jcsa">
+					<view class="uni-row">
+						<image v-if="cardConfigData.popupWarnList.length > 0" class="mid-1-yellowCard" mode="aspectFit" 
+						 @click="btnWarn" src="/static/common/card_yellow.gif"></image>
+						<text class="mid-1-text">{{nickName}}</text>
+					</view>
+					<text class="mid-1-text">{{coiName}}</text>
+					<text class="mid-1-text" style="color: #aaaaaa;" v-if="mcState==1 && allowMcSignUp"
+						@click="btnReGroup">修改</text>
+				</view>
+				<view class="mid-2 uni-row uni-jcsa">
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalNum}}</text>
+						<text class="mid-2-text">总场次</text>
+					</view>
+
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalCp}}</text>
+						<text class="mid-2-text">总打点数</text>
+					</view>
+
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalCpRankNum}}</text>
+						<text class="mid-2-text">个人排名</text>
+					</view>
+
+				</view>
+			</view>
+			
+			<view v-if="configParam.midType == 1" class="midType1 uni-column">
+				<view class="mid-0 uni-row uni-jcc">
+					<view class="mid-0-select">
+						<e-select v-model="ocaId" :options="mapList" :search="false" :inputClick="true"
+							maxHeight="300px" :clearable="false" @change="eSelectChange"></e-select>
+					</view>
+					<view class="mid-0-right uni-row uni-jcsa">
+						<text class="" style="" v-if="mcState==1 && allowMcSignUp"
+							@click="btnReGroup">修改</text>
+						<text v-if="cardConfigData.popupHelpList.length > 0" class="" @click="btnHelp">帮助</text>
+					</view>
+				</view>
+				<view class="midType1-mid-1 uni-row uni-jcsb">
+					<view class="uni-row">
+						<image v-if="cardConfigData.popupWarnList.length > 0" class="mid-1-yellowCard" mode="aspectFit" 
+						 @click="btnWarn" src="/static/common/card_yellow.gif"></image>
+						<text class="mid-1-name">{{nickName}}</text>
+					</view>
+					<text class="mid-1-name">{{coiName}}</text>
+					<text class="uni-nowrap">总场次:{{regionTotalNum}}</text>
+				</view>
+				<view class="mid-2 uni-row uni-jcsa">
+					<view class="uni-column">
+						<text class="mid-2-value" style="color: #ff0045;">{{regionTotalSysPoint}}</text>
+						<text class="mid-2-text">百味豆(个)</text>
+					</view>
+			
+					<view class="mid-line"></view>
+			
+					<view class="uni-column">
+						<text class="mid-2-value">{{fmtDistanct(regionTotalDictance)}}</text>
+						<text class="mid-2-text">总里程km</text>
+					</view>
+			
+					<view class="mid-line"></view>
+					<view class="uni-column">
+						<text class="mid-2-value">{{regionTotalCp}}</text>
+						<text class="mid-2-text">打点数(个)</text>
+					</view>
+			
+					<view class="mid-line"></view>
+			
+					<view class="uni-column">
+						<text class="mid-2-value">{{fmtPace(regionFastPace)}}</text>
+						<text class="mid-2-text">最快配速</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="main uni-column">
+				<text class="main-title">选择比赛路线</text>
+				<my-pathList :style="pathListStyle.style" :pathList="pathList"
+					:selectedPath="ocaId" :mcState="mcState" :showLine="pathListStyle.showLine"
+					@onPathClick="onPathClick"></my-pathList>
+			</view>
+			
+			<view class="bottom uni-column">
+				<button class="btnStartGame bgcolor-main" @click="btnStartGame">开始比赛</button>
+			</view>
+			
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+			<my-popup ref="mypopupHelp" :config="cardConfigData.popupHelpConfig" :dataList="cardConfigData.popupHelpList"></my-popup>
+			<my-popup ref="mypopupWarn" :config="cardConfigData.popupWarnConfig" :dataList="cardConfigData.popupWarnList"></my-popup>
+			<!-- <my-popup-map ref="mypopupmap" :point="navPoint"></my-popup-map> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '/common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		apiMapListQuery,
+		apiMatchRsDetailQuery,
+		apiIsAllowMcSignUp,
+		checkResCode
+	} from '/common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "rankOverview",
+				firstEnterKey: 'firstEnter-tpl-style3',
+				rankKey: "rank-tpl-style3",
+				mapKey: "rank-tpl-style3-map",
+				queryObj: {},
+				queryString: "",
+				token: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				coiName: "", // 已报名单位名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				ocaId: 0, // 关联id,带入到App活动详情页面
+				nickName: "", // 昵称
+
+				totalNum: null, // 总场次
+				totalDistanct: null, // 总距离,单位米
+				totalDistanctRankNum: null, // 总距离排名
+				totalCp: null, // 总打点数
+				totalCpRankNum: null, // 总打点数排名
+				totalSysPoint: null, // 总百味豆
+				totalSysPointRankNum: null, // 总百味豆排名
+				fastPace: null, // 个人最快配速
+				fastPaceRankNum: null, // 个人最快配速排名
+				regionTotalNum: null, // 地图内总场次
+				regionTotalCp: null, // 地图内打点数
+				regionTotalCpRankNum: null, // 地图内个人打点排名
+				regionTotalSysPoint: null, // 地图内积分(百味豆)
+				regionTotalDictance: null, // 地图内里程(单位米)
+				regionFastPace: null, // 地图内最快配速
+				ocaRs: [], // 卡片对应活动集合
+
+				mapList: [], // 地图列表
+
+				interval: null,
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				allowMcSignUp: false, // 是否允许重新分组
+				pathList: {},
+				pathListStyle: {},
+				navPoint: {},
+				configParam: {
+					subTitle: "",
+					midType: 0
+				},
+			}
+		},
+		computed: {
+			pathListLen() {
+				return Object.keys(this.pathList).length;
+			}
+		},
+		onLoad(query) { // 类型非必填,可自动推导
+			// uni.showLoading({
+			// 	title: '加载中'
+			// });
+			
+			// console.log("query:", query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			this.mapKey += "-" + this.ecId;
+			console.log("mapKey:", this.mapKey);
+
+			const mapValue = uni.getStorageSync(this.mapKey);
+			if (mapValue) {
+				console.log("mapValue:", mapValue);
+				this.ocaId = mapValue;
+			}
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+		},
+		onShow() {
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				this.matchRsDetailQuery();
+				cardfunc.warnMessageQuery();
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载比赛路线数据
+				const pathList = config.pathList;
+				// console.log("[loadConfig] pathList:", pathList);
+				if (pathList != undefined) {
+					this.pathList = pathList;
+				}
+
+				// 加载比赛路线样式
+				const pathListStyle = config.pathListStyle;
+				// console.log("[loadConfig] pathList:", pathList);
+				if (pathListStyle != undefined) {
+					this.pathListStyle = pathListStyle;
+				}
+				
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.subTitle != undefined && param.subTitle.length > 0) {
+						this.configParam.subTitle = param.subTitle;
+					}
+					if (param.midType != undefined && param.midType >= 0) {
+						this.configParam.midType = param.midType;
+					}
+				}
+				// console.log("[loadConfig] param:", this.configParam);
+
+				this.pageReady = true;
+				// uni.hideLoading();
+			},
+			// 获取倒计时
+			getCountdown() {
+				// console.log(this.endSecond)
+				if (this.endSecond > 0) {
+					const now = Date.now() / 1000;
+					const dif = this.endSecond - now;
+					// const dif = 3600*24 - 60;
+					if (dif > 0) {
+						this.countdown = '距结束 ' + tools.convertSecondsToDHM(dif);
+					} else {
+						this.countdown = "活动已结束";
+					}
+					// this.countdown = tools.convertSecondsToHMS(dif);
+				} else {
+					this.countdown = "距结束 --天--小时";
+				}
+			},
+			// 格式化 距离
+			fmtDistanct(val) {
+				if (val >= 0) {
+					return Math.round(val * 100 / 1000) / 100;
+				} else {
+					return '--';
+				}
+				/* if (val < 1000)
+					return Math.round(val * 10 / 1000) / 10;
+				else
+					return Math.round(val / 1000); */
+			},
+			// 格式化 配速
+			fmtPace(val) {
+				if (val >= 0) {
+					return tools.convertSecondsToHMS(val, 2);
+				} else {
+					return '--';
+				}
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取活动时间
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			fmtMcTime2(timestamp1, timestamp2) {
+				return tools.fmtMcTime2(timestamp1, timestamp2);
+			},
+			// 卡片对应地图列表详情查询
+			mapListQuery() {
+				uni.request({
+					url: apiMapListQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId
+					},
+					success: (res) => {
+						// console.log("mapListQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.mapList = data;
+
+							let mapItems = [];
+							for (var i = 0; i < data.length; i++) {
+								let map = {};
+								map.text = data[i].mapName;
+								map.value = data[i].ocaId;
+								mapItems.push(map);
+								if (i == 0 && this.ocaId == 0) {
+									this.ocaId = data[i].ocaId;
+									this.matchRsDetailQuery();
+									uni.setStorageSync(this.mapKey, this.ocaId);
+								}
+							}
+							this.mapList = mapItems;
+							// console.log("mapList", this.mapList);
+						}
+					},
+					fail: (err) => {
+						console.log("mapListQuery err", err);
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId,
+						ocaId: this.ocaId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (checkResCode(res)) {
+							const data = res.data.data;
+							this.mcType = data.mcType;
+							this.mcId = data.mcId;
+							this.mcName = data.mcName;
+							this.coiName = data.coiName;
+							this.beginSecond = data.beginSecond;
+							this.endSecond = data.endSecond;
+							this.nickName = data.nickName;
+							this.totalNum = data.totalNum;
+							this.totalDistanct = data.totalDistanct;
+							this.totalDistanctRankNum = data.totalDistanctRankNum;
+							this.totalCp = data.totalCp;
+							this.totalCpRankNum = data.totalCpRankNum;
+							this.totalSysPoint = data.totalSysPoint;
+							this.totalSysPointRankNum = data.totalSysPointRankNum;
+							this.fastPace = data.fastPace;
+							this.fastPaceRankNum = data.fastPaceRankNum;
+							this.regionTotalNum = data.regionTotalNum;
+							this.regionTotalCp = data.regionTotalCp;
+							this.regionTotalCpRankNum = data.regionTotalCpRankNum;
+							this.regionTotalSysPoint = data.regionTotalSysPoint;
+							this.regionTotalDictance = data.regionTotalDictance;
+							this.regionFastPace = data.regionFastPace;
+							this.ocaRs = data.ocaRs;
+
+							this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+
+							this.getCountdown();
+							this.getActtime();
+
+							this.isAllowMcSignUp();
+							this.mapListQuery();
+
+							this.clear();
+							this.interval = setInterval(this.getCountdown, 60000);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 是否允许重新分组(报名)
+			isAllowMcSignUp() {
+				uni.request({
+					url: apiIsAllowMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("isAllowMcSignUp", res)
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							this.allowMcSignUp = data.allowSignUp;
+						}
+					},
+					fail: (err) => {
+						console.log("isAllowMcSignUp err", err)
+					},
+				});
+			},
+			btnBack() {
+				// const url = `action://to_home/`;
+				const url = "/pages/tpl/style3/rankList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			btnInfo() {
+				// console.log(this.$refs.mypopup);
+				this.$refs.mypopup.popupOpen();
+			},
+			btnHelp() {
+				this.$refs.mypopupHelp.popupOpen();
+			},
+			btnWarn() {
+				this.$refs.mypopupWarn.popupOpen();
+			},
+			btnReGroup() {
+				this.queryObj.from = "rankOverview";
+				this.queryString = tools.objectToQueryString(this.queryObj);
+
+				const url = '/pages/tpl/style3/signup?' + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			onOverviewClick(ovtype) {
+				this.queryObj.ovtype = ovtype;
+				this.queryString = tools.objectToQueryString(this.queryObj);
+
+				const url = "/pages/tpl/style3/rankList?" + this.queryString;
+				tools.appAction(url, "uni.navigateTo");
+			},
+			eSelectChange(data) {
+				// console.log("eSelectChange:", data);
+				this.ocaId = data.value;
+				this.matchRsDetailQuery();
+				uni.setStorageSync(this.mapKey, this.ocaId);
+			},
+			onPathClick(data) {
+				console.log("onPathClick:", data);
+				if (this.ocaId != data.path.ocaId) {
+					this.ocaId = data.path.ocaId;
+					this.matchRsDetailQuery();
+					uni.setStorageSync(this.mapKey, data.path.ocaId);
+				}
+			},
+			btnStartGame() {
+				// 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				if (this.mcState == 1) {
+					const url = `action://to_detail/?id=${this.ocaId}&matchType=${this.mcType}`;
+					tools.appAction(url);
+				} else if (this.mcState == 0) {
+					uni.showToast({
+						title: '比赛尚未开始',
+						icon: 'none',
+						duration: 3000
+					});
+				} else if (this.mcState == 2) {
+					uni.showToast({
+						title: '比赛已结束',
+						icon: 'none',
+						duration: 3000
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		min-height: 100vh;
+		overflow-x: scroll;
+	}
+
+	.page-top {
+		position: relative;
+		z-index: 10;
+		width: 100%;
+		height: 270px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url("/static/backgroud/top_bg_egg2.png");
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: cover;
+		/* background-size: 100% 100%; */
+	}
+
+	.topbar-color {
+		color: #333333;
+	}
+
+	.topcontent {
+		height: 90%;
+		margin-bottom: 50px;
+	}
+
+	.logo {
+		width: 80px;
+		height: 80px;
+		margin-top: 10px;
+		/* background-image: url('/static/logo/sddx.png'); */
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.top-acttime {
+		display: none;
+		text-shadow: 3px 3px 0px #640008;
+		font-family: YouSheBiaoTiHei;
+		/* font-family: Arial, Helvetica, sans-serif; */
+		font-weight: bold;
+		color: #ffee0b;
+		font-size: 26px;
+	}
+
+	.topbtm {
+		width: 100%;
+		/* height: 60rpx; */
+		margin-bottom: 40px;
+		justify-content: space-evenly;
+	}
+
+	.topbtm-name {
+		/* width: 320rpx; */
+		/* height: 28px; */
+		padding: 5px 12px;
+		background-color: #9fda39;
+		border-radius: 5px;
+		/* backdrop-filter: blur(30px); */
+		text-align: center;
+		font-weight: 500;
+		color: #497400;
+		font-size: 14px;
+	}
+
+	.midType0 {
+		width: 90%;
+		height: 150px;
+		position: relative;
+		z-index: 20;
+		margin-top: -40px;
+		background: #ffffff;
+		border-radius: 9px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+		font-family: Source Han Sans CN;
+	}
+
+	.midType1 {
+		width: 90%;
+		/* height: 112px; */
+		height: 150px;
+		position: relative;
+		z-index: 20;
+		margin-top: -70px;
+		background: #ffffff;
+		border-radius: 9px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+		font-family: Source Han Sans CN;
+	}
+	
+	.mid-0 {
+		width: 90%;
+		margin: 12px;
+	}
+	
+	.mid-0-select {
+		width: 60%;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.mid-0-help {
+		position: absolute;
+		right: 30px;
+		font-size: 12px;
+		font-weight: 500;
+		color: #992900;
+	}
+
+	.mid-0-right {
+		position: absolute;
+		width: 60px;
+		/* background-color: #81cd00; */
+		right: 10px;
+		font-size: 12px;
+		font-weight: 500;
+		color: #992900;
+	}
+	
+	.midType0-mid-1 {
+		width: 90%;
+		margin-bottom: 12px;
+	}
+	
+	.midType1-select {
+		/* width: 46%; */
+		min-width: 46%;
+		max-width: 50%;
+		margin: 0 5px;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.midType1-mid-1 {
+		width: 90%;
+		margin-bottom: 12px;
+		font-weight: 500;
+		color: #8e8e8e;
+		font-size: 14px;
+	}
+	
+	.mid-1-yellowCard {
+		width: 15px;
+		height: 20px;
+		margin-right: 10px;
+	}
+
+	.mid-1-name {
+		max-width: 360rpx;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+	
+	.mid-1-text {
+		/* min-width: 60px; */
+		font-weight: 500;
+		color: #9a300e;
+		font-size: 12px;
+	}
+
+	.mid-2 {
+		width: 92%;
+		/* margin: 0 10px; */
+	}
+
+	.mid-2-value {
+		font-weight: 900;
+		font-size: 22px;
+		line-height: 25px;
+	}
+
+	.mid-2-text {
+		color: #989898;
+		font-size: 12px;
+	}
+
+	.mid-line {
+		width: 0px;
+		height: 40px;
+		border: 1px solid;
+		border-color: #e6e6e6;
+	}
+
+	.overview-1 {
+		width: 111px;
+		height: 54px;
+		background: #ffb40b;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-2 {
+		margin-top: -43px;
+		color: #ffffff;
+		font-size: 18px;
+		pointer-events: auto;
+	}
+
+	.overview-3 {
+		width: 111px;
+		height: 54px;
+		background: #f39509;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-4 {
+		width: 111px;
+		height: 54px;
+		background: #81cd00;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.overview-5 {
+		width: 111px;
+		height: 54px;
+		background: #64cbb0;
+		border-radius: 50%;
+		box-shadow: 3px 3px 0px rgba(140, 140, 140, 1);
+		pointer-events: auto;
+	}
+
+	.ovline1 {
+		margin-top: 9px;
+		color: #ffffff;
+		font-size: 12px;
+	}
+
+	.ovline2 {
+		color: #ffffff;
+		font-size: 16px;
+	}
+
+	.main {
+		width: 100%;
+		margin-top: 20px;
+		margin-bottom: 20px;
+		/* height: 70vh; */
+		justify-content: space-around;
+		/* justify-content: space-between; */
+	}
+
+	.main-title {
+		margin-bottom: 10px;
+		font-weight: 550;
+		color: #333333;
+		font-size: 16px;
+	}
+
+	.bottom {
+		width: 100%;
+		flex-grow: 1;
+		justify-content: flex-end;
+	}
+
+	.btnStartGame {
+		width: 70%;
+		height: 80rpx;
+		/* margin-top: 50rpx; */
+		margin-bottom: 20rpx;
+		/* font-weight: bold; */
+		color: white;
+		font-size: 32rpx;
+		line-height: 80rpx;
+		border-radius: 27px;
+		background-color: #2e85ec;
+	}
+	
+	/deep/ .e-select {
+		/* width: 60%; */
+		height: 60rpx;
+		background: #2e85ec;
+		color: #ffffff;
+		border-radius: 18px;
+		justify-content: space-around;
+
+		font-size: 16px;
+		font-weight: 500;
+		line-height: 60rpx;
+		text-align: center;
+		border: none !important;
+	}
+
+	/deep/ .e-select-input-text {
+		color: inherit !important;
+	}
+
+	/deep/ .e-select-selector-item {
+		color: #818181;
+	}
+
+	/deep/ .uni-icons {
+		color: inherit !important;
+	}
+
+	/deep/ .e-select-icon {
+		width: 26px !important;
+	}
+</style>

+ 706 - 0
card/pages/tpl/style3/signup.vue

@@ -0,0 +1,706 @@
+<!-- 
+[模板] 样式2 - 报名
+http://localhost:5173/card/#/pages/tpl/style3/signup
+https://oss-mbh5.colormaprun.com/card/#/pages/tpl/style3/signup
+ -->
+<template>
+	<view class="body">
+		<view v-if="pageReady" class="content uni-column">
+			<view class="uni-column page-top">
+				<my-topbar :mcName="mcName" class="topbar-color" @btnBackClick="btnBack"
+					@btnInfoClick="btnInfo"></my-topbar>
+
+				<view class="topcontent uni-column uni-jcsa">
+					<view class="logo"></view>
+					<text v-if="configParam.subTitle.length > 0" class="top-acttime">{{configParam.subTitle}}</text>
+					<text v-else class="top-acttime">{{fmtMcTime2(beginSecond, endSecond)}}</text>
+				</view>
+			</view>
+
+			<view class="timebar uni-row">
+				<image mode="aspectFit" class="clock" src="/static/default/clock.png"></image>
+				<text class="acttime">{{acttime}}</text>
+			</view>
+
+			<view class="main uni-column uni-jct">
+				<input class="uni-input" maxlength="12" :placeholder="'请填写'+configParam.labelName" placeholder-style="font-size: 14px;" v-model="nickName" @click="nickNameClick" />
+				<e-select v-model="coiId" :options="coiRs" :props="eSelectionProps" clearable
+					maxHeight="40vh" :placeholder="'请选择'+configParam.labelOrg+'(可输入关键字)'" @getText="getESelectText"
+					@change="eSelectChange"></e-select>
+
+				<view class="introduce uni-column">
+					<text class="introduce-title">{{introduce.title}}</text>
+					<text class="introduce-content" v-html="introduce.content"></text>
+				</view>
+
+				<view v-if="activityRules.content.length > 0" class="activityRules uni-column">
+					<text class="activityRules-title">{{activityRules.title}}</text>
+					<text class="activityRules-content" v-html="activityRules.content"></text>
+				</view>
+
+				<button class="btnSignup bgcolor-main" v-if="mcState<=1" @click="btnSignup">报 名</button>
+				<button class="btnSignup btnSignup-disable" v-if="mcState==2">活动已结束</button>
+
+			</view>
+
+			<my-popup ref="mypopup" :config="cardConfigData.popupRuleConfig" :dataList="cardConfigData.popupRuleList" :acttime="acttime"></my-popup>
+
+			<!-- <uni-popup ref="alertDialog" type="dialog">
+				<uni-popup-dialog type="info" cancelText="取消" confirmText="确认" title="您填写的姓名:" @confirm="dialogConfirm"
+					@close="dialogClose">
+					<view class="dialog-content uni-column">
+						<text class="dialog-content-1">{{nickName}}</text>
+					</view>
+				</uni-popup-dialog>
+			</uni-popup> -->
+
+			<uni-popup ref="alertDialog" type="dialog">
+				<uni-popup-dialog type="info" cancelText="取消" confirmText="确认" title="你的报名信息:" @confirm="dialogConfirm"
+					@close="dialogClose">
+					<view class="dialog-content uni-column">
+						<text class="dialog-content-1">{{mcName}}</text>
+						<view class="uni-column uni-ais">
+							<text class="dialog-content-2">{{configParam.labelName}}: {{nickName}}</text>
+							<text class="dialog-content-2">{{configParam.labelOrg}}: {{coiName}}</text>
+							<!-- <text class="dialog-content-2">{{teamName}}</text> -->
+						</view>
+					</view>
+				</uni-popup-dialog>
+			</uni-popup>
+		</view>
+	</view>
+</template>
+
+<script>
+	import tools from '../../../common/tools';
+	import cardfunc from '../../../common/cardfunc';
+	import { localCardConfig } from "./cardconfig/test.js";
+	import {
+		token,
+		apiCardDetailQuery,
+		apiOnlineMcSignUpDetail,
+		apiOnlineMcSignUp,
+		apiMatchRsDetailQuery,
+		checkResCode,
+		checkToken
+	} from '../../../common/api';
+
+	export default {
+		data() {
+			return {
+				cardConfigData: cardfunc.cardConfigData,
+				pageReady: false,
+				pageName: "signup",
+				firstEnterKey: 'firstEnter-tpl-style3',
+				rankKey: "rank-tpl-style3",
+				queryObj: {},
+				queryString: "",
+				from: "", // 来源页面
+				token: "",
+
+				ecId: 0, // 卡片id
+				mcId: 0, // 赛事id
+				mcType: 0, // 赛事类型 1 普通活动 2 线下赛 3 线上赛
+				mcName: "", // 赛事名称
+				acttime: "", // 活动时间
+				beginSecond: null, // 活动或赛事开始时间戳,单位秒
+				endSecond: null, // 活动或赛事结束时间戳,单位秒
+				coiId: 0, // 已报名单位id
+				coiName: "", // 已报名单位名称,可为空
+				teamNum: 0, // 已报名队伍编号,可为0
+				nickName: "", // 昵称
+				coiRs: [], // 组织信息集合
+				// orgList: [], // 分组下拉列表数据源
+				// teamList: [], // 
+				interval: null,
+
+				mcState: 0, // 赛事/活动状态 0: 未开始  1: 进行中  2: 已结束
+				// teamType: 0, // 队伍类型  0: 红黄蓝紫 1: 学生/家长
+				introduce: {
+					title: "",
+					content: ""
+				},
+				activityRules: {
+					title: "",
+					content: ""
+				},
+				configParam: {
+					labelName: "昵称",
+					labelOrg: "组织",
+					subTitle: ""
+				},
+
+				eSelectionProps: {
+					text: 'coiName',
+					value: 'coiId',
+					// disabled: 'noallowed'
+				},
+			}
+		},
+		computed: {},
+		onLoad(query) { // 类型非必填,可自动推导
+			// console.log(query);
+			this.queryObj = query;
+			this.queryString = tools.objectToQueryString(this.queryObj);
+			// console.log(queryString);
+			this.from = query["from"] ?? "";
+			this.token = query["token"] ?? token;
+			this.ecId = query["id"] ?? 0;
+
+			this.firstEnterKey += "-" + this.ecId;
+			console.log("firstEnterKey:", this.firstEnterKey);
+
+			this.rankKey += "-" + this.ecId;
+			console.log("rankKey:", this.rankKey);
+
+			cardfunc.init(this, this.token, this.ecId);
+			cardfunc.getCardConfig(this.cardConfigQueryCallback, localCardConfig);
+			
+			this.getCardDetailQuery();
+			this.matchRsDetailQuery();
+		},
+		// 页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用
+		onReady() {
+			// this.dealFirstEnter();
+		},
+		onUnload() {
+			this.clear();
+		},
+		methods: {
+			dealNotice(rank) {
+				// console.log('[dealNotice]');
+				let that = this;
+				uni.getStorage({
+					key: that.rankKey,
+					success: (res) => {
+						// console.log('[getStorage]', that.rankKey, res.data);
+						const oldRank = res.data;
+						if (oldRank != rank) {
+							// that.notice = true;
+							that.setRankValue(rank);
+						}
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.rankKey, e);
+						// that.notice = false;
+						that.setRankValue(rank);
+					},
+				})
+			},
+			setRankValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.rankKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.rankKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.rankKey, e);
+					},
+				})
+			},
+			dealFirstEnter() {
+				// console.log('[dealFirstEnter]');
+				let that = this;
+				uni.getStorage({
+					key: that.firstEnterKey,
+					success: (res) => {
+						console.log('[getStorage]', that.firstEnterKey, res.data);
+					},
+					fail: (e) => {
+						console.log('[getStorage] fail', that.firstEnterKey, e);
+						that.btnInfo();
+						that.setFirstEnterValue(true);
+					},
+				})
+			},
+			setFirstEnterValue(data) {
+				let that = this;
+				uni.setStorage({
+					key: that.firstEnterKey,
+					data: data,
+					success: () => {
+						console.log('[setStorage] success', that.firstEnterKey, data);
+					},
+					fail: (e) => {
+						console.log('[setStorage] fail', that.firstEnterKey, e);
+					},
+				})
+			},
+			clear() {
+				if (this.interval != null) {
+					clearInterval(this.interval);
+					this.interval = null;
+				}
+			},
+			cardConfigQueryCallback(cardconfig) {
+				this.loadConfig(cardconfig);
+				setTimeout(this.dealFirstEnter, 500);
+			},
+			loadConfig(cardconfig) {
+				cardconfig = cardfunc.parseCardConfig(cardconfig);
+				// console.log("[loadCardConfig] cardconfig:", cardconfig);
+				
+				// 加载卡片通用配置
+				if (cardconfig.common != undefined) {
+					cardfunc.loadCardCommonConfig(cardconfig.common);
+				}
+				
+				// -------- 加载当前页面的配置 --------
+				
+				const config = cardfunc.parseCardConfig(cardconfig[this.pageName]);
+				// console.log("[loadConfig] config_page:", config);
				if (config == undefined || config == null) {
+					this.pageReady = true;
					return;
				}
+				
+				// 加载CSS样式
+				const css = config.css;
+				if (css != undefined && css.length > 0) {
+					tools.loadCssCode(css);
+				}
+
+				// 加载队伍类型  0: 红黄蓝紫 1: 学生/家长
+				/* if (config.teamType != undefined && config.teamType >= 0) {
+					this.teamType = config.teamType;
+				} */
+
+				// 加载介绍内容
+				const introduce = config.introduce;
+				if (introduce != undefined) {
+					if (introduce.title != undefined) {
+						this.introduce.title = introduce.title;
+					}
+					if (introduce.content != undefined) {
+						this.introduce.content = introduce.content;
+					}
+				}
+
+				// 加载活动规则
+				const activityRules = config.activityRules;
+				if (activityRules != undefined) {
+					if (activityRules.title != undefined) {
+						this.activityRules.title = activityRules.title;
+					}
+					if (activityRules.content != undefined) {
+						this.activityRules.content = activityRules.content;
+					}
+				}
+
+				// 加载页面参数
+				const param = config.param;
+				if (param != undefined) {
+					if (param.labelName != undefined && param.labelName.length > 0) {
+						this.configParam.labelName = param.labelName;
+					}
+					if (param.labelOrg != undefined && param.labelOrg.length > 0) {
+						this.configParam.labelOrg = param.labelOrg;
+					}
+					if (param.subTitle != undefined && param.subTitle.length > 0) {
+						this.configParam.subTitle = param.subTitle;
+					}
+				}
+				// console.log("[loadConfig] param:", this.configParam);
+				
+				this.pageReady = true;
+			},
+			fmtMcTime(timestamp) {
+				return tools.fmtMcTime(timestamp);
+			},
+			// 获取倒计时
+			getActtime() {
+				this.acttime = tools.getActtime(this.beginSecond, this.endSecond);
+			},
+			fmtMcTime2(timestamp1, timestamp2) {
+				return tools.fmtMcTime2(timestamp1, timestamp2);
+			},
+			// 卡片信息查询
+			getCardDetailQuery() {
+				uni.request({
+					url: apiCardDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("getCardDetailQuery", res);
+						const data = res.data.data;
+						this.mcType = data.mcType;
+						this.mcId = data.mcId;
+						this.mcName = data.mcName;
+						this.beginSecond = data.beginSecond;
+						this.endSecond = data.endSecond;
+						this.coiId = data.coiId;
+						this.coiName = data.coiName;
+						this.teamNum = data.teamNum;
+						this.nickName = data.nickName;
+
+						this.mcState = tools.checkMcState(this.beginSecond, this.endSecond);
+
+						this.getActtime();
+						this.getOnlineMcSignUpDetail();
+
+						this.clear();
+						this.interval = setInterval(this.getActtime, 60000);
+					},
+					fail: (err) => {
+						console.log("getCardDetailQuery err", err)
+					},
+				});
+			},
+			// 卡片对应线上赛多个活动查询
+			matchRsDetailQuery() {
+				uni.request({
+					url: apiMatchRsDetailQuery,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						ecId: this.ecId
+					},
+					success: (res) => {
+						// console.log("matchRsDetailQuery", res);
+						if (res.data.code == 0) {
+							const data = res.data.data;
+							const rank = JSON.stringify(data);
+							this.dealNotice(rank);
+						}
+					},
+					fail: (err) => {
+						console.log("matchRsDetailQuery err", err)
+					},
+				});
+			},
+			// 线上赛报名页面信息详情
+			getOnlineMcSignUpDetail() {
+				uni.request({
+					url: apiOnlineMcSignUpDetail,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId,
+					},
+					success: (res) => {
+						// console.log("getOnlineMcSignUpDetail", res);
+						this.coiRs = res.data.data.coiRs;
+						if (this.nickName == undefined || this.nickName == '') {
+							this.nickName = res.data.data.name;
+						}
+						/* const rsNum = this.coiRs.length;
+						this.orgList = [];
+						for (let i = 0; i < rsNum; i++) {
+							this.orgList[i] = {};
+							this.orgList[i].value = this.coiRs[i].coiId;
+							this.orgList[i].text = this.coiRs[i].coiName;
+							this.orgList[i].teamNum = this.coiRs[i].teamNum;
+						}
+						// console.log("orgList", this.orgList);
+						if (this.coiId > 0) {
+							this.orgChange(this.coiId, false);
+						} */
+					},
+					fail: (err) => {
+						console.log("getOnlineMcSignUpDetail err", err)
+					},
+				});
+			},
+			// 线上赛报名
+			onlineMcSignUp() {
+				uni.request({
+					url: apiOnlineMcSignUp,
+					header: {
+						"Content-Type": "application/x-www-form-urlencoded",
+						"token": this.token,
+					},
+					method: "POST",
+					data: {
+						mcId: this.mcId,
+						coiId: this.coiId,
+						selectTeam: this.teamNum,
+						nickName: this.nickName
+					},
+					success: (res) => {
+						// console.log("onlineMcSignUp", res);
+
+						if (checkResCode(res)) {
+							uni.showToast({
+								title: '比赛报名成功!',
+								icon: 'none',
+								duration: 3000
+							});
+
+							const url = '/pages/tpl/style3/rankList?' + this.queryString;
+							tools.appAction(url, "uni.navigateTo");
+						}
+					},
+					fail: (err) => {
+						console.log("onlineMcSignUp err", err);
+						uni.showToast({
+							title: '出错了,报名失败',
+							icon: 'none',
+							duration: 3000
+						});
+					},
+				});
+			},
+			btnBack() {
+				// console.log("from:", this.from)
+				if (this.from != '') {
+					// window.history.back();
+					const url = '/pages/tpl/style3/' + this.from + '?' + this.queryString;
+					tools.appAction(url, "uni.navigateTo");
+				} else {
+					const url = `action://to_home/`;
+					tools.appAction(url);
+				}
+			},
+			btnInfo() {
+				this.$refs.mypopup.popupOpen();
+			},
+			nickNameClick() {
+				checkToken(this.token);
+			},
+			btnSignup() {
+				if (!checkToken(this.token)) {
+					return;
+				}
+
+				if (!(this.nickName.trim().length > 0)) {
+					uni.showToast({
+						title: `请填写${this.configParam.labelName}`,
+						icon: 'none',
+						duration: 2000
+					});
+					return;
+				}
+
+				if (!(this.coiId > 0)) {
+					uni.showToast({
+						title: `请选择${this.configParam.labelOrg}`,
+						icon: 'none',
+						duration: 2000
+					});
+					return;
+				}
+
+				this.nickName = this.nickName.trim();
+				// this.coiName = tools.getSelectedText(this.orgList, this.coiId);
+				// this.teamName = tools.getSelectedText(this.teamList, this.teamNum);
+
+				this.$refs.alertDialog.open();
+			},
+			dialogConfirm() {
+				this.onlineMcSignUp();
+			},
+			dialogClose() {},
+			// 获取输入框中值
+			getESelectText(data) {
+				// console.log("getESelectText:", data);
+				this.coiName = data;
+			},
+			// 获取选择选项值
+			eSelectChange(data) {
+				// console.log("eSelectChange:", data);
+			},
+		}
+	}
+</script>
+
+<style scoped>
+	.content {
+		width: 100vw;
+		min-height: 100vh;
+	}
+
+	.page-top {
+		width: 100%;
+		height: 220px;
+		padding-top: 36px;
+		justify-content: space-between;
+		background-image: url('static/backgroud/top_bg_sddx.png');
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: cover;
+	}
+
+	.topbar-color-default {
+		color: #333333;
+	}
+
+	.topcontent {
+		height: 90%;
+		margin-bottom: 30px;
+	}
+
+	.logo {
+		width: 80px;
+		height: 80px;
+		margin-top: 10px;
+		/* background-image: url('/static/logo/sddx.png'); */
+		background-repeat: no-repeat;
+		background-position-x: center;
+		background-position-y: center;
+		background-size: contain;
+	}
+
+	.top-acttime {
+		display: none;
+		text-shadow: 3px 3px 0px #640008;
+		font-family: YouSheBiaoTiHei;
+		/* font-family: Arial, Helvetica, sans-serif; */
+		font-weight: bold;
+		color: #ffee0b;
+		font-size: 26px;
+	}
+
+	.mcName {
+		font-size: 40rpx;
+		font-weight: 550;
+	}
+
+	.main {
+		width: 76%;
+		flex-grow: 1;
+	}
+
+	.timebar {
+		min-width: 68%;
+		height: 32px;
+		margin-top: -17px;
+		padding: 0 15px;
+		justify-content: center;
+		background: #ffffff;
+		border: 0.5px solid;
+		border-color: #e7e7e7;
+		border-radius: 20px;
+		box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.13);
+	}
+
+	.acttime {
+		font-weight: 550;
+		color: #333333;
+		font-size: 14px;
+		text-wrap: nowrap;
+	}
+
+	.clock {
+		width: 15px;
+		height: 15px;
+		margin-right: 10px;
+	}
+
+	.uni-input {
+		width: 90%;
+		height: 34px;
+		margin-top: 15px;
+		margin-bottom: 15px;
+		padding: 0 13px;
+		/* background: #f1f1f1; */
+		border: 1px solid #dcdfe6;
+		border-radius: 4px;
+		font-size: 14px;
+	}
+
+	.input-placeholder {
+		color: #333333;
+		font-size: 16px;
+	}
+
+	.introduce {
+		width: 100%;
+		margin-top: 12px;
+		margin-bottom: 10px;
+		align-items: flex-start;
+		justify-content: space-around;
+	}
+
+	.introduce-title {
+		color: #333333;
+		font-size: 15px;
+		line-height: 30px;
+		font-family: Source Han Sans CN;
+	}
+
+	.introduce-content {
+		color: #333333;
+		font-size: 14px;
+		line-height: 23px;
+		font-family: Source Han Sans CN;
+	}
+
+	.activityRules {
+		width: 100%;
+		margin-top: 5px;
+		margin-bottom: 10px;
+		padding: 10px 15px;
+		align-items: flex-start;
+		justify-content: space-around;
+		border-radius: 9px;
+		background: #EBEBEB;
+	}
+
+	.activityRules-title {
+		color: #333333;
+		font-size: 14px;
+		line-height: 25px;
+		font-weight: 500;
+		font-family: Source Han Sans CN;
+	}
+
+	.activityRules-content {
+		color: #333333;
+		font-size: 13px;
+		line-height: 23px;
+		font-family: Source Han Sans CN;
+	}
+
+	.btnSignup {
+		width: 100%;
+		height: 100rpx;
+		margin-top: 30rpx;
+		margin-bottom: 30rpx;
+		color: white;
+		font-weight: bold;
+		line-height: 100rpx;
+		border-radius: 55rpx;
+	}
+
+	.bgcolor-main {
+		background-color: #81cd00;
+	}
+
+	.btnSignup-disable {
+		background-color: #c3c3c3;
+	}
+
+	.dialog-content {
+		width: 280px;
+		height: 110px;
+		padding: 0 20px;
+		background: #f1f1f1;
+		border-radius: 9px;
+		justify-content: center;
+		text-align: center;
+		font-weight: 550;
+		color: #333333;
+	}
+
+	.dialog-content-1 {
+		font-size: 34rpx;
+		margin-bottom: 30rpx;
+	}
+
+	.dialog-content-2 {
+		font-size: 28rpx;
+		margin-bottom: 10rpx;
+		text-align: left;
+	}
+</style>

BIN
card/static/exchange/score.png


BIN
card/static/exchange/score2.png


BIN
card/static/exchange/top_right.png


+ 39 - 0
card/uni_modules/uni-number-box/changelog.md

@@ -0,0 +1,39 @@
+## 1.2.8(2024-04-26)
+- 修复 在vue2下H5黑边的bug
+## 1.2.7(2024-04-26)
+- 修复 在vue2手动输入后失焦导致清空数值的严重bug
+## 1.2.6(2024-02-22)
+- 新增 设置宽度属性width(单位:px)
+## 1.2.5(2024-02-21)
+- 修复 step步长小于1时,键盘类型为number的bug
+## 1.2.4(2024-02-02)
+- 修复 加减号垂直位置偏移样式问题
+## 1.2.3(2023-05-23)
+- 更新示例工程
+## 1.2.2(2023-05-08)
+- 修复 change 事件执行顺序错误的问题
+## 1.2.1(2021-11-22)
+- 修复 vue3中某些scss变量无法找到的问题
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-number-box](https://uniapp.dcloud.io/component/uniui/uni-number-box)
+## 1.1.2(2021-11-09) 
+- 新增 提供组件设计资源,组件样式调整
+## 1.1.1(2021-07-30)
+- 优化 vue3下事件警告的问题
+## 1.1.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.0.7(2021-05-12)
+- 新增 组件示例地址
+## 1.0.6(2021-04-20)
+- 修复 uni-number-box 浮点数运算不精确的 bug
+- 修复 uni-number-box change 事件触发不正确的 bug
+- 新增 uni-number-box v-model 双向绑定
+## 1.0.5(2021-02-05)
+- 调整为uni_modules目录规范
+
+## 1.0.7(2021-02-05)
+- 调整为uni_modules目录规范
+- 新增 支持 v-model
+- 新增 支持 focus、blur 事件
+- 新增 支持 PC 端

+ 232 - 0
card/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue

@@ -0,0 +1,232 @@
+<template>
+	<view class="uni-numbox">
+		<view @click="_calcValue('minus')" class="uni-numbox__minus uni-numbox-btns" :style="{background}">
+			<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }"
+				:style="{color}">-</text>
+		</view>
+		<input :disabled="disabled" @focus="_onFocus" @blur="_onBlur" class="uni-numbox__value"
+			:type="step<1?'digit':'number'" v-model="inputValue" :style="{background, color, width:widthWithPx}" />
+		<view @click="_calcValue('plus')" class="uni-numbox__plus uni-numbox-btns" :style="{background}">
+			<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }"
+				:style="{color}">+</text>
+		</view>
+	</view>
+</template>
+<script>
+	/**
+	 * NumberBox 数字输入框
+	 * @description 带加减按钮的数字输入框
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=31
+	 * @property {Number} value 输入框当前值
+	 * @property {Number} min 最小值
+	 * @property {Number} max 最大值
+	 * @property {Number} step 每次点击改变的间隔大小
+	 * @property {String} background 背景色
+	 * @property {String} color 字体颜色(前景色)
+	 * @property {Number} width 输入框宽度(单位:px)
+	 * @property {Boolean} disabled = [true|false] 是否为禁用状态
+	 * @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
+	 * @event {Function} focus 输入框聚焦时触发的事件,参数为 event 对象
+	 * @event {Function} blur 输入框失焦时触发的事件,参数为 event 对象
+	 */
+
+	export default {
+		name: "UniNumberBox",
+		emits: ['change', 'input', 'update:modelValue', 'blur', 'focus'],
+		props: {
+			value: {
+				type: [Number, String],
+				default: 1
+			},
+			modelValue: {
+				type: [Number, String],
+				default: 1
+			},
+			min: {
+				type: Number,
+				default: 0
+			},
+			max: {
+				type: Number,
+				default: 100
+			},
+			step: {
+				type: Number,
+				default: 1
+			},
+			background: {
+				type: String,
+				default: '#f5f5f5'
+			},
+			color: {
+				type: String,
+				default: '#333'
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			width: {
+				type: Number,
+				default: 40,
+			}
+		},
+		data() {
+			return {
+				inputValue: 0
+			};
+		},
+		watch: {
+			value(val) {
+				this.inputValue = +val;
+			},
+			modelValue(val) {
+				this.inputValue = +val;
+			}
+		},
+		computed: {
+			widthWithPx() {
+				return this.width + 'px';
+			}
+		},
+		created() {
+			if (this.value === 1) {
+				this.inputValue = +this.modelValue;
+			}
+			if (this.modelValue === 1) {
+				this.inputValue = +this.value;
+			}
+		},
+		methods: {
+			_calcValue(type) {
+				if (this.disabled) {
+					return;
+				}
+				const scale = this._getDecimalScale();
+				let value = this.inputValue * scale;
+				let step = this.step * scale;
+				if (type === "minus") {
+					value -= step;
+					if (value < (this.min * scale)) {
+						return;
+					}
+					if (value > (this.max * scale)) {
+						value = this.max * scale
+					}
+				}
+
+				if (type === "plus") {
+					value += step;
+					if (value > (this.max * scale)) {
+						return;
+					}
+					if (value < (this.min * scale)) {
+						value = this.min * scale
+					}
+				}
+
+				this.inputValue = (value / scale).toFixed(String(scale).length - 1);
+				// TODO vue2 兼容
+				this.$emit("input", +this.inputValue);
+				// TODO vue3 兼容
+				this.$emit("update:modelValue", +this.inputValue);
+				this.$emit("change", +this.inputValue);
+			},
+			_getDecimalScale() {
+
+				let scale = 1;
+				// 浮点型
+				if (~~this.step !== this.step) {
+					scale = Math.pow(10, String(this.step).split(".")[1].length);
+				}
+				return scale;
+			},
+			_onBlur(event) {
+				this.$emit('blur', event)
+				let value = event.detail.value;
+				if (isNaN(value)) {
+					this.inputValue = this.value;
+					return;
+				}
+				value = +value;
+				if (value > this.max) {
+					value = this.max;
+				} else if (value < this.min) {
+					value = this.min;
+				}
+				const scale = this._getDecimalScale();
+				this.inputValue = value.toFixed(String(scale).length - 1);
+				this.$emit("input", +this.inputValue);
+				this.$emit("update:modelValue", +this.inputValue);
+				this.$emit("change", +this.inputValue);
+			},
+			_onFocus(event) {
+				this.$emit('focus', event)
+			}
+		}
+	};
+</script>
+<style lang="scss">
+	$box-height: 26px;
+	$bg: #f5f5f5;
+	$br: 2px;
+	$color: #333;
+
+	.uni-numbox {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-numbox-btns {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		padding: 0 8px;
+		background-color: $bg;
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-numbox__value {
+		margin: 0 2px;
+		background-color: $bg;
+		width: 40px;
+		height: $box-height;
+		text-align: center;
+		font-size: 14px;
+		border-width: 0;
+		color: $color;
+	}
+
+	.uni-numbox__minus {
+		border-top-left-radius: $br;
+		border-bottom-left-radius: $br;
+	}
+
+	.uni-numbox__plus {
+		border-top-right-radius: $br;
+		border-bottom-right-radius: $br;
+	}
+
+	.uni-numbox--text {
+		// fix nvue
+		line-height: 20px;
+		margin-bottom: 2px;
+		font-size: 20px;
+		font-weight: 300;
+		color: $color;
+	}
+
+	.uni-numbox .uni-numbox--disabled {
+		color: #c0c0c0 !important;
+		/* #ifdef H5 */
+		cursor: not-allowed;
+		/* #endif */
+	}
+</style>

+ 83 - 0
card/uni_modules/uni-number-box/package.json

@@ -0,0 +1,83 @@
+{
+  "id": "uni-number-box",
+  "displayName": "uni-number-box 数字输入框",
+  "version": "1.2.8",
+  "description": "NumberBox 带加减按钮的数字输入框组件,用户可以控制每次点击增加的数值,支持小数。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "数字输入框"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y",
+        "alipay": "n"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 13 - 0
card/uni_modules/uni-number-box/readme.md

@@ -0,0 +1,13 @@
+
+
+## NumberBox 数字输入框
+> **组件名:uni-number-box**
+> 代码块: `uNumberBox`
+
+
+带加减按钮的数字输入框。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-number-box)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
+
+