api.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /**
  2. * 请任何自动化工具每次更新本文档,要再下面的版本号自动加1,并更新日期
  3. *
  4. * 版本号:1
  5. * 日期:2025-12-4 16:14:00
  6. */
  7. (function (window) {
  8. 'use strict';
  9. // Logger Utility
  10. const Logger = {
  11. _isDev: false,
  12. init: function(isDev) {
  13. this._isDev = isDev;
  14. },
  15. log: function() {
  16. if (this._isDev) {
  17. console.log.apply(console, arguments);
  18. }
  19. },
  20. warn: function() {
  21. if (this._isDev) {
  22. console.warn.apply(console, arguments);
  23. }
  24. },
  25. error: function() {
  26. console.error.apply(console, arguments); // Always log errors
  27. }
  28. };
  29. // Determine _isDev status from main window's URL query params
  30. function getQueryParam(name) {
  31. const params = new URLSearchParams(window.location.search);
  32. return params.get(name);
  33. }
  34. const env = (getQueryParam('env') || '').toLowerCase();
  35. Logger.init(env === 'mock'); // Initialize Logger
  36. /**
  37. * ColorMapRun API SDK (Full Version with Mock)
  38. * 封装了与后端服务器的所有交互
  39. * 依赖: bridge.js (用于 401 跳转登录)
  40. */
  41. // 基础配置
  42. var Config = {
  43. baseUrl: 'https://colormaprun.com/api/card/',
  44. ossUrl: 'http://oss-card.colormaprun.com/card/',
  45. token: '',
  46. useMock: false
  47. };
  48. // 模拟数据定义 (涵盖所有接口)
  49. var MOCK_DB = {
  50. 'CardBaseQuery': {
  51. ecName: '[Mock]跑向大明湖卡片',
  52. ecDesc: '欢迎参加彩图奔跑活动!',
  53. beginSecond: Date.now() / 1000 - 86400 * 5,
  54. endSecond: Date.now() / 1000 + 86400 * 10,
  55. secondCardName: '地图导航'
  56. },
  57. 'CardDetailQuery': {
  58. mcId: 101,
  59. mcName: '[Mock]线上马拉松',
  60. nickName: '[Mock]用户昵称', // Added as per user request
  61. mcType: 1,
  62. beginSecond: Date.now() / 1000 - 86400 * 2,
  63. endSecond: Date.now() / 1000 + 86400 * 5,
  64. teamNum: 0,
  65. coiId: 1, // Mocked coiId
  66. coiName: '个人组',
  67. ocaId: 201 // Mocked ocaId
  68. },
  69. 'MatchRsDetailQuery': {
  70. id: 1,
  71. name: '[Mock]活动1',
  72. status: 1,
  73. totalSysPoint: 100,
  74. mcType: 1,
  75. mcId: 101,
  76. mcName: '[Mock]线上马拉松',
  77. beginSecond: Date.now() / 1000 - 86400 * 2,
  78. endSecond: Date.now() / 1000 + 86400 * 5,
  79. nickName: '[Mock]用户昵称',
  80. totalNum: 10,
  81. totalDistanct: 5000,
  82. totalDistanctRankNum: 5,
  83. totalCp: 20,
  84. totalCpRankNum: 3,
  85. totalSysPointRankNum: 10,
  86. fastPace: 300,
  87. fastPaceRankNum: 8
  88. },
  89. 'CardRankDetailQuery': {
  90. totalRankRs: [
  91. { nickName: 'Mock张三', score: 10000, headUrl: 'https://picsum.photos/40/40?random=1', rankNum: 1 },
  92. { nickName: 'Mock李四', score: 9500, headUrl: 'https://picsum.photos/40/40?random=2', rankNum: 2 },
  93. { nickName: 'Mock王五', score: 8800, headUrl: 'https://picsum.photos/40/40?random=3', rankNum: 3 },
  94. { nickName: 'Mock赵六', score: 7200, headUrl: 'https://picsum.photos/40/40?random=4', rankNum: 4 },
  95. { nickName: 'Mock小明', score: 6500, headUrl: 'https://picsum.photos/40/40?random=5', rankNum: 5 }
  96. ],
  97. teamRankRs: [],
  98. inTeamRs: [],
  99. otherRs: [],
  100. // Added for ranklist.html mapping
  101. teamCpRs: [
  102. { teamName: 'Mock飞虎队', inRankNum: 500, rankNum: 1, isSelf: true },
  103. { teamName: 'Mock火箭队', inRankNum: 450, rankNum: 2, isSelf: false },
  104. { teamName: 'Mock摸鱼队', inRankNum: 400, rankNum: 3, isSelf: false }
  105. ],
  106. teamDistanceRs: [
  107. { teamName: 'Mock火箭队', inRankNum: 120000, rankNum: 1, isSelf: false },
  108. { teamName: 'Mock飞虎队', inRankNum: 115000, rankNum: 2, isSelf: true }
  109. ],
  110. teamRightAnswerPerRs: [
  111. { teamName: 'Mock学霸队', inRankNum: 98, rankNum: 1, isSelf: false },
  112. { teamName: 'Mock飞虎队', inRankNum: 95, rankNum: 2, isSelf: true }
  113. ],
  114. teamTodayPaceRs: [
  115. { teamName: 'Mock闪电队', inRankNum: 300, rankNum: 1, isSelf: false }, // 5'00"
  116. { teamName: 'Mock飞虎队', inRankNum: 330, rankNum: 2, isSelf: true } // 5'30"
  117. ],
  118. regionCpRs: [
  119. { userName: 'Mock个人A', inRankNum: 50, headUrl: 'https://picsum.photos/40/40?random=6', rankNum: 1, isSelf: false },
  120. { userName: 'Mock个人B', inRankNum: 45, headUrl: 'https://picsum.photos/40/40?random=7', rankNum: 2, isSelf: false },
  121. { userName: 'Mock我', inRankNum: 40, headUrl: 'https://picsum.photos/40/40?random=8', rankNum: 3, isSelf: true }
  122. ],
  123. regionDistanceRs: [
  124. { userName: 'Mock跑神', inRankNum: 21000, headUrl: 'https://picsum.photos/40/40?random=9', rankNum: 1, isSelf: false },
  125. { userName: 'Mock我', inRankNum: 15000, headUrl: 'https://picsum.photos/40/40?random=10', rankNum: 2, isSelf: true }
  126. ],
  127. regionRightAnswerPerRs: [
  128. { userName: 'Mock智多星', inRankNum: 100, headUrl: 'https://picsum.photos/40/40?random=11', rankNum: 1, isSelf: false },
  129. { userName: 'Mock我', inRankNum: 90, headUrl: 'https://picsum.photos/40/40?random=12', rankNum: 2, isSelf: true }
  130. ],
  131. regionTodayPaceRs: [
  132. { userName: 'Mock飞人', inRankNum: 240, headUrl: 'https://picsum.photos/40/40?random=13', rankNum: 1, isSelf: false },
  133. { userName: 'Mock我', inRankNum: 360, headUrl: 'https://picsum.photos/40/40?random=14', rankNum: 2, isSelf: true }
  134. ]
  135. },
  136. 'UserCurrentRankNumQuery': { rankNum: 5 },
  137. 'UserJoinCardQuery': { isJoin: false }, // 默认未报名
  138. 'IsNewUserInCardComp': { isNew: true },
  139. 'OnlineMcSignUpDetail': {
  140. teamList: [
  141. { teamId: 1, teamName: '[Mock]个人组' },
  142. { teamId: 2, teamName: '[Mock]团队组' }
  143. ],
  144. signupFields: [
  145. { name: 'realName', label: '真实姓名', type: 'text', required: true, value: '' },
  146. { name: 'phone', label: '手机号码', type: 'tel', required: true, value: '' }
  147. ]
  148. },
  149. 'OnlineMcSignUp': {}, // 报名成功返回空 data
  150. 'IsAllowMcSignUp': { allowSignUp: true },
  151. 'CurrentMonthlyChallengeQuery': {
  152. year: '2023',
  153. monthRs: [{ month: 11, realNum: 10, targetNum: 20 }]
  154. },
  155. 'CardConfigQuery': {
  156. configJson: JSON.stringify({
  157. css: ".custom-header { background-color: #f0f8ff; }",
  158. tabActiveColor: "#007bff",
  159. teamType: 0, // Mocked teamType
  160. popupRuleConfig: { height: "60%", theme: "light" },
  161. popupMessageConfig: {}, // Mocked popupMessageConfig
  162. popupRuleList: [
  163. { type: 1, data: { title: '规则1', content: '这是<b>Mock</b>的活动规则内容。' } },
  164. { type: 1, data: { title: '规则2', content: '第二条规则。', logo: { src: 'https://picsum.photos/100/50', width: '100px', height: '50px' } } }
  165. ],
  166. popupDataList: [ // Mocked popupDataList
  167. { type: 1, data: { title: '通用弹窗1', content: '通用弹窗内容1。' } }
  168. ]
  169. })
  170. },
  171. 'UserConfigQuery': {
  172. configJson: JSON.stringify({
  173. tplInfo: { tplTypeId: 1, ssctId: 1 },
  174. mapInfo: [
  175. { activityList: [{ showName: '迷你路线', pathImg: '', ocaId: 1, matchType: 1, point: { longitude: 117.0, latitude: 36.6, name: '起点' } }] }
  176. ],
  177. popupRuleList: [{ type: 1, data: { title: '用户个性化', content: '这是用户专属内容' } }]
  178. })
  179. },
  180. 'MonthlyChallengeQuery': [
  181. { year: '2023', monthRs: [{ month: 10, realNum: 15, targetNum: 20 }] },
  182. { year: '2022', monthRs: [{ month: 12, realNum: 25, targetNum: 20 }] }
  183. ],
  184. 'MonthRankDetailQuery': [
  185. { nickName: '月榜冠军', score: 500, headUrl: 'https://picsum.photos/40/40?random=6' }
  186. ],
  187. 'AchievementQuery': [
  188. {
  189. year: '2023',
  190. aiRs: [
  191. { aiName: '初次登场', aiTime: Date.now() / 1000 - 86400 * 30, iconUrl: 'https://picsum.photos/60/60?random=7' },
  192. { aiName: '跑步达人', aiTime: Date.now() / 1000 - 86400 * 10, iconUrl: 'https://picsum.photos/60/60?random=8' }
  193. ]
  194. }
  195. ],
  196. 'ExchangeListQuery': [
  197. { exchangeId: 1, goodsName: '[Mock]运动手环', createTime: Date.now() / 1000 - 86400 * 7, status: 1 },
  198. { exchangeId: 2, goodsName: '[Mock]定制水杯', createTime: Date.now() / 1000 - 86400 * 15, status: 0 }
  199. ],
  200. 'ExchangeDetailQuery': {
  201. exchangeId: 1, goodsName: '[Mock]运动手环', createTime: Date.now() / 1000 - 86400 * 7, status: 1,
  202. address: 'Mock地址', receiver: 'Mock收件人', phone: '138****8888'
  203. },
  204. 'UnReadMessageQuery': [
  205. { mqId: 1, mqType: 1, mqTitle: '[Mock]新成就', mqMessage: '恭喜您解锁新成就!', iconUrl: 'https://picsum.photos/50/50?random=9' }
  206. ],
  207. 'ReadMessage': {},
  208. 'MapListQuery': [
  209. { mapId: 1, mapName: '[Mock]公园地图', latitude: 36.6, longitude: 117.0, activityList: [] }
  210. ],
  211. 'CompStatisticQuery': { totalDistance: 123.45, totalPeople: 1000 },
  212. 'WarnMessageQuery': [
  213. { warnType: 1, warnTitle: '[Mock]黄牌警告', warnMessage: '您的成绩异常', iconUrl: 'https://picsum.photos/50/50?random=10' }
  214. ],
  215. 'CertStyleQuery': {
  216. styleId: 1, styleName: '简约风', templateUrl: 'https://picsum.photos/600/400?random=11',
  217. elements: [
  218. { type: 'text', field: 'userName', x: 100, y: 150, fontSize: 24, color: '#333' }
  219. ]
  220. },
  221. 'UserBaseQueryInCertificate': { userName: '[Mock]证书用户', activityName: '[Mock]活动名', completionTime: Date.now() / 1000 },
  222. 'CertificateCreateByUserAi': { certUrl: 'https://picsum.photos/600/400?random=12' },
  223. 'OnlineScoreQuery': { score: 880, extTime: Date.now() / 1000 + 86400 * 30 },
  224. 'CanExchangeGoodsList': [
  225. { goodsId: 1, goodsName: '[Mock]运动手环', goodsPic: 'https://picsum.photos/100/100?random=13', goodsLeftNum: 50, corrScore: 500 },
  226. { goodsId: 2, goodsName: '[Mock]定制水杯', goodsPic: 'https://picsum.photos/100/100?random=14', goodsLeftNum: 0, corrScore: 300 }
  227. ],
  228. 'CanExchangeGoodsDetail': {
  229. goodsId: 1, goodsName: '[Mock]运动手环', goodsPic: 'https://picsum.photos/200/200?random=15', corrScore: 500,
  230. desc: '这是<b>Mock</b>的运动手环详情,功能强大,是您运动的好伙伴!'
  231. },
  232. 'ScoreExchangeGoods': {},
  233. 'UserBasicInformationQuery': { nickName: 'Mock用户', headUrl: 'https://picsum.photos/50/50?random=16' },
  234. 'GridsQuery': {
  235. compId: 201, compName: '[Mock]网格挑战', widthNum: 3, heightNum: 3,
  236. maskImgPic: 'https://picsum.photos/300/300?random=17',
  237. actualImgPic: 'https://picsum.photos/300/300?random=18',
  238. state: 2, // 1:未开始 2:进行中 3:已结束
  239. detailRs: [
  240. { orderNum: 1, isComplete: 1, showName: '完成1', relationType: 1, ocaId: 10, longitude: 117.1, latitude: 36.6, popupImg: 'https://picsum.photos/100/100?random=19' },
  241. { orderNum: 2, isComplete: 0, showName: '未完2', relationType: 1, ocaId: 11, longitude: 117.2, latitude: 36.7, popupImg: 'https://picsum.photos/100/100?random=20' }
  242. ]
  243. },
  244. 'CardUrlQuery': { url: 'https://mock-uri.colormaprun.com/card/nanning1/index.html'}, // 暂无 Mock 结构
  245. 'MatchFininshInfoQuery': {}, // 暂无 Mock 结构
  246. 'RedisRebulid': {} // 暂无 Mock 结构
  247. };
  248. var API = {
  249. init: function(options) {
  250. if (options.baseUrl) Config.baseUrl = options.baseUrl;
  251. if (options.ossUrl) Config.ossUrl = options.ossUrl;
  252. if (options.token) Config.token = options.token;
  253. if (typeof options.useMock === 'boolean') Config.useMock = options.useMock;
  254. if (Config.useMock) Logger.warn('%c [API] Mock 模式已开启 ', 'background: orange; color: white;');
  255. },
  256. getOssUrl: function() {
  257. return Config.ossUrl;
  258. },
  259. setToken: function(token) {
  260. Config.token = token;
  261. },
  262. request: function(endpoint, data) {
  263. if (Config.useMock) {
  264. return new Promise(function(resolve, reject) {
  265. Logger.log('[API-Mock] Request:', endpoint, data);
  266. setTimeout(function() {
  267. var mockData = MOCK_DB[endpoint];
  268. if (endpoint === 'OnlineMcSignUp') MOCK_DB['UserJoinCardQuery'].isJoin = true;
  269. if (endpoint === 'ScoreExchangeGoods') MOCK_DB['OnlineScoreQuery'].score -= 100;
  270. Logger.log('[API-Mock] Response:', endpoint, mockData || {});
  271. resolve(mockData || {});
  272. }, 300);
  273. });
  274. }
  275. var url = Config.baseUrl + endpoint;
  276. var headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'token': Config.token };
  277. var formData = new URLSearchParams();
  278. for (var key in data) { if (data.hasOwnProperty(key)) formData.append(key, data[key]); }
  279. Logger.log('[API] Request:', endpoint, data);
  280. return fetch(url, { method: 'POST', headers: headers, body: formData, mode: 'cors', credentials: 'omit' })
  281. .then(function(response) { return response.json(); })
  282. .then(function(res) {
  283. Logger.log('[API] Response:', endpoint, res);
  284. if (res.code === 0) return res.data;
  285. if (res.code === 401 || res.statusCode === 401) {
  286. Logger.warn('[API] Token invalid');
  287. if (window.Bridge && window.Bridge._post) window.Bridge._post('toLogin');
  288. else alert('登录已过期');
  289. throw new Error('Unauthorized');
  290. }
  291. var msg = res.message || '请求失败';
  292. if (window.Bridge && window.Bridge.showToast) {
  293. window.Bridge.showToast(msg, 'none');
  294. } else {
  295. alert(msg);
  296. }
  297. throw new Error(msg);
  298. })
  299. .catch(function(err) { Logger.error('[API] Error:', err); throw err; });
  300. },
  301. // ==============================
  302. // 完整业务接口封装 (按原始 api.js 顺序)
  303. // ==============================
  304. // 1. 卡片基本信息查询
  305. getCardBase: function(ecId) { return this.request('CardBaseQuery', { ecId: ecId }); },
  306. // 2. 卡片对应活动或赛事详情查询
  307. getCardDetail: function(ecId) { return this.request('CardDetailQuery', { ecId: ecId }); },
  308. // 3. 卡片对应线上赛多个活动查询
  309. getMatchRsDetail: function(ecId, ocaId) { return this.request('MatchRsDetailQuery', { ecId: ecId, ocaId: ocaId }); },
  310. // 4. 排名查询
  311. getRankDetail: function(mcIdListStr, mcType, ocaId, dispArrStr) { return this.request('CardRankDetailQuery', { mcIdListStr: mcIdListStr, mcType: mcType, ocaId: ocaId, dispArrStr: dispArrStr }); },
  312. // 5. 卡片用户当前排名查询
  313. getUserCurrentRank: function(ecId) { return this.request('UserCurrentRankNumQuery', { ecId: ecId }); },
  314. // 6. 用户是否已经报名卡片对应赛事查询
  315. getUserJoinStatus: function(ecId) { return this.request('UserJoinCardQuery', { ecId: ecId }); },
  316. // 7. 用户在卡片对应赛事是否新用户
  317. isNewUserInCardComp: function(ecId) { return this.request('IsNewUserInCardComp', { ecId: ecId }); },
  318. // 8. 线上赛报名页面信息详情
  319. getOnlineMcSignUpDetail: function(ecId, mcId) { return this.request('OnlineMcSignUpDetail', { ecId: ecId, mcId: mcId }); },
  320. // 9. 线上赛报名(重新分组)
  321. signUpOnline: function(mcId, coiId, selectTeam, nickName) {
  322. return this.request('OnlineMcSignUp', { mcId: mcId, coiId: coiId, selectTeam: selectTeam, nickName: nickName });
  323. },
  324. // 10. 是否允许重新分组(报名)
  325. isAllowMcSignUp: function(ecId) { return this.request('IsAllowMcSignUp', { ecId: ecId }); },
  326. // 11. 玩家当前月挑战记录查询
  327. getCurrentMonthlyChallenge: function(year, month) { return this.request('CurrentMonthlyChallengeQuery', {year: year, month: month}); },
  328. // 12. 卡片配置信息查询
  329. getCardConfig: function(ecId, pageName) { return this.request('CardConfigQuery', { ecId: ecId, pageName: pageName }); },
  330. // 13. 用户自定义配置信息查询
  331. getUserConfig: function(ecId, pageName) { return this.request('UserConfigQuery', { ecId: ecId, pageName: pageName }); },
  332. // 14. 玩家所有月挑战记录查询
  333. getMonthlyChallenge: function() { return this.request('MonthlyChallengeQuery', {}); },
  334. // 15. 月挑战排名查询
  335. getMonthRankDetail: function(year, month, dispArrStr) { return this.request('MonthRankDetailQuery', {year: year, month: month, dispArrStr: dispArrStr}); },
  336. // 16. 玩家活动成就查询
  337. getAchievement: function() { return this.request('AchievementQuery', {}); },
  338. // 17. 玩家兑换记录查询
  339. getExchangeList: function() { return this.request('ExchangeListQuery', {}); },
  340. // 18. 玩家兑换详情查询
  341. getExchangeDetail: function(oarId) { return this.request('ExchangeDetailQuery', { oarId: oarId}); },
  342. // 19. 未读消息列表查询
  343. getUnReadMessages: function(relationId, relationType) { return this.request('UnReadMessageQuery', { relationId: relationId, relationType: relationType || 2 }); },
  344. // 20. 标记消息已读
  345. readMessage: function(mqIdListStr) { return this.request('ReadMessage', { mqIdListStr: mqIdListStr }); },
  346. // 21. 卡片对应地图列表详情查询
  347. getMapList: function(mcId) { return this.request('MapListQuery', { mcId: mcId }); },
  348. // 22. 赛事总成绩统计查询
  349. getCompStatistic: function(mcId) { return this.request('CompStatisticQuery', { mcId: mcId }); },
  350. // 23. 警告列表查询
  351. getWarnMessage: function(ecId) { return this.request('WarnMessageQuery', { ecId: ecId }); },
  352. // 24. 查询电子证书样式
  353. getCertStyle: function(certStyleType) { return this.request('CertStyleQuery', { certStyleType: certStyleType }); },
  354. // 25. 查询电子证书成就对应用户基本信息
  355. getUserBaseInCertificate: function(oarId) { return this.request('UserBaseQueryInCertificate', { oarId: oarId }); },
  356. // 26. 根据成就信息确认生成电子证书
  357. createCertificate: function(nickName,oarId) { return this.request('CertificateCreateByUserAi', {nickName: nickName, oarId: oarId}); },
  358. // 27. 卡片内可用积分查询
  359. getScore: function(ecId) { return this.request('OnlineScoreQuery', { ecId: ecId }); },
  360. // 28. 积分可兑换商品列表查询
  361. getGoodsList: function(ecId) { return this.request('CanExchangeGoodsList', { ecId: ecId }); },
  362. // 29. 积分可兑换商品详情
  363. getGoodsDetail: function(ecId, goodsId) { return this.request('CanExchangeGoodsDetail', {ecid: ecId, goodsId: goodsId }); },
  364. // 30. 积分兑换商品
  365. exchangeGoods: function(ecId, goodsId, exchNum) { return this.request('ScoreExchangeGoods', { ecId: ecId, goodsId: goodsId, exchNum: exchNum }); },
  366. // 31. 用户基本信息查询
  367. getUserInfo: function() { return this.request('UserBasicInformationQuery', {}); },
  368. // 32. 网格卡片信息查询
  369. getGrids: function(ecId) { return this.request('GridsQuery', { ecId: ecId }); },
  370. // 33. 卡片URI查询
  371. getCardUrl: function(actId, matchType) { return this.request('CardUrlQuery', { actId: actId, matchType: matchType }); },
  372. // 34. 赛事完赛信息查询
  373. getMatchFinishInfo: function(actId, matchType) { return this.request('MatchFininshInfoQuery', { actId, matchType }); },
  374. // 35. Redis 重建 (管理接口)
  375. redisRebuild: function(compId) { return this.request('RedisRebulid', { compId: compId }); }
  376. };
  377. window.API = API;
  378. })(window);