From f457c1d04cdaa7195775c5c92254e51319fdedc9 Mon Sep 17 00:00:00 2001 From: honghefly Date: Wed, 14 Jan 2026 15:33:15 +0800 Subject: [PATCH] first commit --- app.js | 219 ++++++++++ app.json | 53 +++ app.wxss | 60 +++ images/README.md | 55 +++ images/icons/about.png | Bin 0 -> 779 bytes images/icons/arrow-right.png | Bin 0 -> 566 bytes images/icons/clear.png | Bin 0 -> 619 bytes images/icons/download-menu.png | Bin 0 -> 531 bytes images/icons/empty-download.png | Bin 0 -> 1960 bytes images/icons/search.png | Bin 0 -> 1330 bytes images/icons/share.png | Bin 0 -> 811 bytes images/tabbar/category-active.png | Bin 0 -> 821 bytes images/tabbar/category.png | Bin 0 -> 725 bytes images/tabbar/download-active.png | Bin 0 -> 550 bytes images/tabbar/download.png | Bin 0 -> 504 bytes images/tabbar/home-active.png | Bin 0 -> 1353 bytes images/tabbar/home.png | Bin 0 -> 1267 bytes images/tabbar/mine-active.png | Bin 0 -> 1879 bytes images/tabbar/mine.png | Bin 0 -> 1741 bytes images/tmpclaude-ecff-cwd | 1 + miniprogram_npm/@jdmini/api/index.d.ts | 295 +++++++++++++ miniprogram_npm/@jdmini/api/index.js | 4 + .../@jdmini/components/icons/home-active.png | Bin 0 -> 1575 bytes .../@jdmini/components/icons/home.png | Bin 0 -> 1070 bytes .../@jdmini/components/icons/link-active.png | Bin 0 -> 2796 bytes .../@jdmini/components/icons/link.png | Bin 0 -> 1798 bytes .../@jdmini/components/jdwx-ad/index.js | 22 + .../@jdmini/components/jdwx-ad/index.json | 3 + .../@jdmini/components/jdwx-ad/index.wxml | 5 + .../@jdmini/components/jdwx-ad/index.wxss | 7 + .../@jdmini/components/jdwx-link/index.js | 37 ++ .../@jdmini/components/jdwx-link/index.json | 3 + .../@jdmini/components/jdwx-link/index.wxml | 11 + .../@jdmini/components/jdwx-link/index.wxss | 63 +++ node_modules/.package-lock.json | 20 + node_modules/@jdmini/api/README.md | 252 ++++++++++++ .../@jdmini/api/miniprogram_dist/index.d.ts | 295 +++++++++++++ .../@jdmini/api/miniprogram_dist/index.js | 4 + node_modules/@jdmini/api/package.json | 24 ++ node_modules/@jdmini/components/README.md | 31 ++ .../miniprogram_dist/icons/home-active.png | Bin 0 -> 1575 bytes .../miniprogram_dist/icons/home.png | Bin 0 -> 1070 bytes .../miniprogram_dist/icons/link-active.png | Bin 0 -> 2796 bytes .../miniprogram_dist/icons/link.png | Bin 0 -> 1798 bytes .../miniprogram_dist/jdwx-ad/index.js | 22 + .../miniprogram_dist/jdwx-ad/index.json | 3 + .../miniprogram_dist/jdwx-ad/index.wxml | 5 + .../miniprogram_dist/jdwx-ad/index.wxss | 7 + .../miniprogram_dist/jdwx-link/index.js | 37 ++ .../miniprogram_dist/jdwx-link/index.json | 3 + .../miniprogram_dist/jdwx-link/index.wxml | 11 + .../miniprogram_dist/jdwx-link/index.wxss | 63 +++ node_modules/@jdmini/components/package.json | 20 + package-lock.json | 26 ++ package.json | 6 + pages/category-detail/category-detail.js | 90 ++++ pages/category-detail/category-detail.json | 5 + pages/category-detail/category-detail.wxml | 30 ++ pages/category-detail/category-detail.wxss | 77 ++++ pages/category/category.js | 131 ++++++ pages/category/category.json | 6 + pages/category/category.wxml | 56 +++ pages/category/category.wxss | 155 +++++++ pages/detail/detail.js | 388 ++++++++++++++++++ pages/detail/detail.json | 4 + pages/detail/detail.wxml | 59 +++ pages/detail/detail.wxss | 213 ++++++++++ pages/download/download.js | 150 +++++++ pages/download/download.json | 4 + pages/download/download.wxml | 36 ++ pages/download/download.wxss | 129 ++++++ pages/index/index.js | 60 +++ pages/index/index.json | 5 + pages/index/index.wxml | 48 +++ pages/index/index.wxss | 130 ++++++ pages/login/login.js | 220 ++++++++++ pages/login/login.json | 4 + pages/login/login.wxml | 55 +++ pages/login/login.wxss | 218 ++++++++++ pages/mine/mine.js | 231 +++++++++++ pages/mine/mine.json | 4 + pages/mine/mine.wxml | 104 +++++ pages/mine/mine.wxss | 186 +++++++++ pages/search/search.js | 160 ++++++++ pages/search/search.json | 4 + pages/search/search.wxml | 92 +++++ pages/search/search.wxss | 192 +++++++++ project.config.json | 41 ++ project.private.config.json | 24 ++ sitemap.json | 7 + utils/api.js | 140 +++++++ utils/auth.js | 269 ++++++++++++ utils/config.js | 40 ++ utils/httpClient.js | 88 ++++ utils/index.js | 27 ++ utils/request.js | 278 +++++++++++++ 96 files changed, 5827 insertions(+) create mode 100644 app.js create mode 100644 app.json create mode 100644 app.wxss create mode 100644 images/README.md create mode 100644 images/icons/about.png create mode 100644 images/icons/arrow-right.png create mode 100644 images/icons/clear.png create mode 100644 images/icons/download-menu.png create mode 100644 images/icons/empty-download.png create mode 100644 images/icons/search.png create mode 100644 images/icons/share.png create mode 100644 images/tabbar/category-active.png create mode 100644 images/tabbar/category.png create mode 100644 images/tabbar/download-active.png create mode 100644 images/tabbar/download.png create mode 100644 images/tabbar/home-active.png create mode 100644 images/tabbar/home.png create mode 100644 images/tabbar/mine-active.png create mode 100644 images/tabbar/mine.png create mode 100644 images/tmpclaude-ecff-cwd create mode 100644 miniprogram_npm/@jdmini/api/index.d.ts create mode 100644 miniprogram_npm/@jdmini/api/index.js create mode 100644 miniprogram_npm/@jdmini/components/icons/home-active.png create mode 100644 miniprogram_npm/@jdmini/components/icons/home.png create mode 100644 miniprogram_npm/@jdmini/components/icons/link-active.png create mode 100644 miniprogram_npm/@jdmini/components/icons/link.png create mode 100644 miniprogram_npm/@jdmini/components/jdwx-ad/index.js create mode 100644 miniprogram_npm/@jdmini/components/jdwx-ad/index.json create mode 100644 miniprogram_npm/@jdmini/components/jdwx-ad/index.wxml create mode 100644 miniprogram_npm/@jdmini/components/jdwx-ad/index.wxss create mode 100644 miniprogram_npm/@jdmini/components/jdwx-link/index.js create mode 100644 miniprogram_npm/@jdmini/components/jdwx-link/index.json create mode 100644 miniprogram_npm/@jdmini/components/jdwx-link/index.wxml create mode 100644 miniprogram_npm/@jdmini/components/jdwx-link/index.wxss create mode 100644 node_modules/.package-lock.json create mode 100644 node_modules/@jdmini/api/README.md create mode 100644 node_modules/@jdmini/api/miniprogram_dist/index.d.ts create mode 100644 node_modules/@jdmini/api/miniprogram_dist/index.js create mode 100644 node_modules/@jdmini/api/package.json create mode 100644 node_modules/@jdmini/components/README.md create mode 100644 node_modules/@jdmini/components/miniprogram_dist/icons/home-active.png create mode 100644 node_modules/@jdmini/components/miniprogram_dist/icons/home.png create mode 100644 node_modules/@jdmini/components/miniprogram_dist/icons/link-active.png create mode 100644 node_modules/@jdmini/components/miniprogram_dist/icons/link.png create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.js create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.json create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxml create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxss create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.js create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.json create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxml create mode 100644 node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxss create mode 100644 node_modules/@jdmini/components/package.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pages/category-detail/category-detail.js create mode 100644 pages/category-detail/category-detail.json create mode 100644 pages/category-detail/category-detail.wxml create mode 100644 pages/category-detail/category-detail.wxss create mode 100644 pages/category/category.js create mode 100644 pages/category/category.json create mode 100644 pages/category/category.wxml create mode 100644 pages/category/category.wxss create mode 100644 pages/detail/detail.js create mode 100644 pages/detail/detail.json create mode 100644 pages/detail/detail.wxml create mode 100644 pages/detail/detail.wxss create mode 100644 pages/download/download.js create mode 100644 pages/download/download.json create mode 100644 pages/download/download.wxml create mode 100644 pages/download/download.wxss create mode 100644 pages/index/index.js create mode 100644 pages/index/index.json create mode 100644 pages/index/index.wxml create mode 100644 pages/index/index.wxss create mode 100644 pages/login/login.js create mode 100644 pages/login/login.json create mode 100644 pages/login/login.wxml create mode 100644 pages/login/login.wxss create mode 100644 pages/mine/mine.js create mode 100644 pages/mine/mine.json create mode 100644 pages/mine/mine.wxml create mode 100644 pages/mine/mine.wxss create mode 100644 pages/search/search.js create mode 100644 pages/search/search.json create mode 100644 pages/search/search.wxml create mode 100644 pages/search/search.wxss create mode 100644 project.config.json create mode 100644 project.private.config.json create mode 100644 sitemap.json create mode 100644 utils/api.js create mode 100644 utils/auth.js create mode 100644 utils/config.js create mode 100644 utils/httpClient.js create mode 100644 utils/index.js create mode 100644 utils/request.js diff --git a/app.js b/app.js new file mode 100644 index 0000000..b66271e --- /dev/null +++ b/app.js @@ -0,0 +1,219 @@ +import { injectApp, waitLogin, gatewayHttpClient } from '@jdmini/api' +App(injectApp()({ + globalData: { + userInfo: null, + openid: null, + wxUserInfo: null, + inviterId: null // 邀请人ID + }, + async onLaunch(options) { + // 保存邀请人ID + if (options && options.query && options.query.inviter) { + this.globalData.inviterId = options.query.inviter + wx.setStorageSync('inviterId', options.query.inviter) + } + if (wx.canIUse('getUpdateManager')) { + const updateManager = wx.getUpdateManager(); + updateManager.onCheckForUpdate(function (res) { + if (res.hasUpdate) { + updateManager.onUpdateReady(function () { + wx.showModal({ + title: '更新提示', + content: '新版本已经准备好,是否重启应用?', + success(res) { + if (res.confirm) { + updateManager.applyUpdate(); + } + } + }); + }); + } + }); + } + const accountInfo = wx.getExtConfigSync() + //console.log('#1',accountInfo) + const appId = accountInfo.appId; + // 等待登陆完成以后请求自动携带token + await waitLogin() + // 三方登录本地缓存 + console.log(wx.getStorageSync('jdwx-userinfo')) + const wxUserInfo = wx.getStorageSync('jdwx-userinfo'); + wx.setStorageSync('sfUserId', wxUserInfo.id) + wx.setStorageSync('appId',appId) + // 保存到全局 + this.globalData.openid = wxUserInfo.openId; + this.globalData.wxUserInfo = wxUserInfo; + const userId = wx.getStorageSync('userId'); + if (userId) { + console.log('用户已登录', userId); + } else { + console.log('用户未设置个人信息,需要跳转登录页'); + } + //this.getqrcode('logo=1') + }, + // 生成小程序二维码 + async getqrcode(value) { + try { + // 构建场景值:仓库ID和仓库code + const scene = value; + + console.log('生成二维码请求参数:', { + scene, + page: "pages/index/index" + }); + + const result = await gatewayHttpClient.request('/wx/v1/api/app/qrcode', 'POST', { + "scene": scene, + "page": "pages/index/index", // 跳转到index页面 + "check_path": true, + "env_version": "release" + },{ + responseType: 'arraybuffer' + }); + + console.log('二维码接口返回:', result); + console.log('返回数据类型:', typeof result); + console.log('是否为ArrayBuffer:', result instanceof ArrayBuffer); + + // 当设置 responseType: 'arraybuffer' 时,返回的直接就是 ArrayBuffer + if (result instanceof ArrayBuffer) { + console.log('检测到 ArrayBuffer,转换为 base64'); + const base64Data = wx.arrayBufferToBase64(result); + console.log('转换后的 base64 数据长度:', base64Data?.length); + console.log('base64 数据前50个字符:', base64Data?.substring(0, 50)); + + return { + success: true, + data: base64Data // 二维码图片数据(base64字符串) + }; + } else { + console.error('二维码生成失败,返回数据不是ArrayBuffer:', result); + return { + success: false, + message: '生成二维码失败:数据格式错误' + }; + } + } catch (error) { + console.error('生成二维码异常:', error); + return { + success: false, + message: error.message || '生成二维码失败' + }; + } + }, + //内容安全 + async checkdata(txt = '', checkType, mediaUrl = '') { + try { + if (!checkType || (checkType !== 2 && checkType !== 3)) { + throw new Error('checkType必须为2(图片检测)或3(文本检测)') + } + + if (checkType === 3 && !txt) { + throw new Error('文本检测时content不能为空') + } + + if (checkType === 2 && !mediaUrl) { + throw new Error('图片检测时mediaUrl不能为空') + } + + const postdata = { + content: txt, + checkType: checkType, + mediaUrl: mediaUrl + } + + const data = await gatewayHttpClient.request('/wx/v1/api/app/content/check', 'post', postdata) + + if (data.code === 200) { + if (checkType == 3) { + return data.data.suggest === 'pass' ? 1 : 2 + } + + if (checkType == 2) { + return data.data.id || null + } + } else { + console.error('内容检测API调用失败:', data) + return 2 + } + } catch (error) { + console.error('checkdata函数执行错误:', error) + return 2 + } + }, +// 轮询检查图片是否合规 +//imgurl 为本地图片地址 +async checkimage(imgurl){ + wx.showLoading({ + title: '正在检查图片...', + mask: true + }); + try{ + const upfileData = await gatewayHttpClient.uploadFile(imgurl, 'image'); + const checkid = await this.checkdata('',2,upfileData.data.url) + let retryCount = 0; + const maxRetries = 5; + while (retryCount < maxRetries) { + await new Promise(resolve => setTimeout(resolve, 1000)); // 间隔1秒 + + const passcode = await this.checkSafetyResults(checkid); + + // 检查具体的错误码 + switch (passcode) { + case 100: + wx.hideLoading(); + return upfileData.data; + case 20001: + wx.hideLoading(); + await gatewayHttpClient.deleteFile(upfileData.data.id); + this.showwarning('图片时政内容,请重新选择'); + return null; + case 20002: + wx.hideLoading(); + await gatewayHttpClient.deleteFile(upfileData.data.id); + this.showwarning('图片含有色情内容,请重新选择'); + return null; + case 20006: + wx.hideLoading(); + await gatewayHttpClient.deleteFile(upfileData.data.id); + this.showwarning('图片含有违法犯罪内容,请重新选择'); + return null; + case 21000: + wx.hideLoading(); + await gatewayHttpClient.deleteFile(upfileData.data.id); + this.showwarning('图片非法内容,请重新选择'); + return null; + default: + break + } + retryCount++; + } + + // 5次超时,返回失败 + wx.hideLoading(); + await gatewayHttpClient.deleteFile(upfileData.data.id)//删除图片 + wx.showToast({ + title: '图片检查超时,请重试', + icon: 'none' + }); + return null; + } catch (error) { + wx.hideLoading(); + console.error('图片检查失败:', error); + wx.showToast({ + title: '检查失败,请重试', + icon: 'none' + }); + return null; + } +}, +showwarning(txt){ + wx.showModal({ + title: '提示', + content: txt, + showCancel: false, + success: () => { + } + }); +} +})) diff --git a/app.json b/app.json new file mode 100644 index 0000000..067ee53 --- /dev/null +++ b/app.json @@ -0,0 +1,53 @@ +{ + "pages": [ + "pages/index/index", + "pages/category/category", + "pages/category-detail/category-detail", + "pages/detail/detail", + "pages/download/download", + "pages/mine/mine", + "pages/search/search", + "pages/login/login" + ], + "window": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "儿童练习表", + "navigationBarBackgroundColor": "#ffffff" + }, + "tabBar": { + "color": "#999999", + "selectedColor": "#4CAF50", + "backgroundColor": "#ffffff", + "borderStyle": "black", + "list": [ + { + "pagePath": "pages/index/index", + "text": "首页", + "iconPath": "images/tabbar/home.png", + "selectedIconPath": "images/tabbar/home-active.png" + }, + { + "pagePath": "pages/category/category", + "text": "分类", + "iconPath": "images/tabbar/category.png", + "selectedIconPath": "images/tabbar/category-active.png" + }, + { + "pagePath": "pages/download/download", + "text": "下载", + "iconPath": "images/tabbar/download.png", + "selectedIconPath": "images/tabbar/download-active.png" + }, + { + "pagePath": "pages/mine/mine", + "text": "我的", + "iconPath": "images/tabbar/mine.png", + "selectedIconPath": "images/tabbar/mine-active.png" + } + ] + }, + "style": "v2", + "componentFramework": "glass-easel", + "sitemapLocation": "sitemap.json", + "lazyCodeLoading": "requiredComponents" +} diff --git a/app.wxss b/app.wxss new file mode 100644 index 0000000..e7ad8ec --- /dev/null +++ b/app.wxss @@ -0,0 +1,60 @@ +/* 全局样式 */ +page { + background-color: #f5f5f5; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: 28rpx; + color: #333; + box-sizing: border-box; +} + +/* 清除默认边距 */ +view, text, image { + box-sizing: border-box; +} + +/* 常用flex布局 */ +.flex { + display: flex; +} + +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-wrap { + display: flex; + flex-wrap: wrap; +} + +.flex-column { + display: flex; + flex-direction: column; +} + +/* 文本省略 */ +.text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-ellipsis-2 { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +/* 安全区域底部内边距 */ +.safe-area-bottom { + padding-bottom: env(safe-area-inset-bottom); +} diff --git a/images/README.md b/images/README.md new file mode 100644 index 0000000..ea258ad --- /dev/null +++ b/images/README.md @@ -0,0 +1,55 @@ +# 图片资源说明 + +## 需要准备的图片资源 + +### TabBar 图标 (images/tabbar/) +- home.png / home-active.png - 首页图标 +- category.png / category-active.png - 分类图标 +- download.png / download-active.png - 下载图标 +- mine.png / mine-active.png - 我的图标 + +建议尺寸: 81x81px (实际显示约 27x27pt) + +### 功能图标 (images/icons/) +- search.png - 搜索图标 +- arrow-right.png - 右箭头 +- preview.png - 预览图标 +- download.png - 下载图标 +- print.png - 打印图标 +- open.png - 打开文件图标 +- delete.png - 删除图标 +- clear.png - 清除图标 +- empty.png - 空状态图标 +- empty-download.png - 下载空状态图标 +- empty-search.png - 搜索空状态图标 +- download-menu.png - 菜单下载图标 +- share.png - 分享图标 +- feedback.png - 反馈图标 +- about.png - 关于图标 + +建议尺寸: 48x48px 或 64x64px + +### Banner 图片 (images/banner/) +- banner1.png - 轮播图1 +- banner2.png - 轮播图2 +- banner3.png - 轮播图3 + +建议尺寸: 690x280rpx (可适当调整) + +### 默认图片 +- default-avatar.png - 默认头像 + +建议尺寸: 120x120px + +## 图标建议 + +可使用以下免费图标库: +1. iconfont.cn (阿里图标库) +2. Material Design Icons +3. Feather Icons +4. Heroicons + +图标风格建议: +- 线性图标,线宽 2px +- 颜色: 未选中 #999999, 选中 #4CAF50 +- 格式: PNG (带透明背景) 或 SVG diff --git a/images/icons/about.png b/images/icons/about.png new file mode 100644 index 0000000000000000000000000000000000000000..dc04e459c9bf9bdf8c8193f0ca82021c26289b41 GIT binary patch literal 779 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 literal 0 HcmV?d00001 diff --git a/images/icons/search.png b/images/icons/search.png new file mode 100644 index 0000000000000000000000000000000000000000..f5de58970522448ae1898937d49672a1f3be35a9 GIT binary patch literal 1330 zcmV-21k-O_(2iDFZK@=ENWvB@Ajm3mmC%2UuYEtMKJv!*naR2 zrdqAq=I+wm-XRfd6+tnI_(7xwHAM=sv5M9dArNcY+ueD-*~{+jT*Eb+DlYSzd4Ff# z&dz&tC*Xg6&N_f>;n?|RsdjU^Q0p;^_71aPA25r~8)nh%O%2+slJ86}Y4wh06+pV= z+*`v-zXK9q6N)_m_#`0S0RRsG&@Pntxri^COcm`HSvF@|Z~$h>eTfLFJnPkrmJI>7 zri#ul>7u=+*)VbdYP-#%LqN2pd9qeK0$fRepRw=#VY|oy@OFO^o?w>V4}gy$(At@r z(FU$_4Iv(siq&?Fk2Rt7%^1alB7C@W)JK>)vo)a&>R zZCCuf0}ui?D2D(D`>Gi{6!2#tgp^&H2~ke*K>*1TtyHJf!=9OCN*M;9_|6c2LQ3P= z5PwFp+@1Kg=}cUv))Hx6jKA%$Aa{)TlzNE8kC-Kg^IDttrUplcsyRH;;O9pn;*ViV zDPfHO!%(@oF|6=tf*!L}yG7CF0A0)DVn)3oj@5~XJsAhX9w>0NAR$(Vg&IwwUGYNJ zf={J*t^vorF=994!0(z1!h}HE-Udx+;u~>#4Jvvle~#L^lZy60(CLj4yYmuG=reM$ zcIjtP&M%r+WQ%qp`H(s!3IN#4izDdIXut|w8PE|30&#uVT*PqP+uFT(JXy&lcB#Ul zz(wWFu%V)nppkL}%eXE)rQ}h-_504i9>F1LjClbB!q&uBVKtxV?a9HKwxwZxGo32b z9^@@uAIK&3TUk6f?*PiV*xP{caWDZBLW*z90%_aI;?r7c!0tm7ulouD_!FetuXrzj zWYJ#FwP1*$ZmR(4qO&i#bMgXZs2~$YR*f3a{hsgp=aXE>!t!KgERiZY8_kmY1Er||*wL_Lir{hXwdQ4kCixec6+ej} z^}J~bR+;=0-lJ!`|1^NUK9qo{e|HkvE_eYXE3|}D=wWr*qk?@eoPC)&2Wh(lO^S4S zh?e>?!R7P@QIlVAzV1{7BPzrxcZ&0VlRsheSj{I!o3n)+=Lz3Vpo{Bx@fE&cK_kL# zt^9-lOV?xY83Yh4)|Ct32%J`4C;pmzm)Dt?%SNaaVg>)P;C?}k4wHc4R<8Sdm;Q~y z;D1DT*t6q`=#AWYhtSH#x`g9ir;18cSlM_-WmD`!o@>tL3(Fb&lxstWm5pJzBK`va o0RR6fPON$W000I_L_t&o0DTt`HmW@kQvd(}07*qoM6N<$g6>ax-T(jq literal 0 HcmV?d00001 diff --git a/images/icons/share.png b/images/icons/share.png new file mode 100644 index 0000000000000000000000000000000000000000..2044c0fa2f436f35e99db3403d535f972b492e79 GIT binary patch literal 811 zcmV+`1JwM9P)E*|)Ir20mI@ifRY8U1l0^j3WDq0}@(;zqA>bgSXw4=f zwu_mZ3W`b{geuyl#KDS~#Lw^XPVT(BYA-JehTk{u_q%uZ`+ncc-8Yx8{_*Gk72r5d z2fWwlL~Zm~b^y{LglRCPk=Dm3{b5`HL~Tt`N+y&01=J;AdFG^4FfM?uM}WjqrBZn* zpjZMD?wsTb;}D>Fvhp>>z?UcQQ~E-k>^m|-k;~=M!{P9=wCMNy8Mb^zAh{d^&w($D z2!LcFpU>a5ZM$RJ_OxcW#<-{Q5T; zklW;eh%F$S&F*m=XAz>Cblt}ID3wa>Y&04-n$70hdc8ge>7+)6b3!AQ09IW}Boc4v zI0Nxl#?ug>CLjQ!C?}gxS6J3{i1$^i)yKLXX$T0Qo9k9# zPPWQ+U*Q~M5hg&nmB+m-PyXE)vu+e7K)Dsqa+s4D*>=sXhJXOJ*mWy$FQ1cf#X0Vg z`#cSM$&ExnfOJKKxYj(mIN1wE`8z$!xB=#P8eSu$)^4|x)FVZx0O^Q`3=k(fz*q!# zz;@=!KqA-ecE2E`AVpk63W#cmh)dj*yO@6fQlNr}72MJN={WS~v1I|$nwN}s2swor z9iv_b)0opkT=FSG4pPh8&jP~6`%Z`%JweDJ$S*>=fuN)0%MhIeeq%TCu&)A%Fei0gR^}CA-2>{%=r7fi8+FY+#gq5lWA3O@;a%wsrh$8A=(UxaGi8g@~CsivIAbS-{n65 p00960&Y~|d00006Nkl}_JrsF<@00xNxFJ#pEWFnr)UDhqc zx%iFjuBjjXao;bt%fL1D9r1Mb5YLOE%s_+VzGdQJhKe_(^gLTgF~K(v2-#M#kO={j zMBK?9U{i4m0}1C-D+25g$|4gAcubZ9#H7sW;s8j?I%ljQ)P#Ye?>B~I)e&K%l7qNb z;VKZd2CWMvVIU;__qb|c<#d1$9R7>%KPcxS1_ha`6k~CAI@Cl5yi{_W{f19Np~`qo z(S7`=a2f)Uj!mT6W0fo;z*5EZoJnGrjZCUertiiyb&k3PlWo<5| zbk+bE@Vc#MbMgG6%mC-SRBNx7vYOZ?sKMUJ?&JfWr^8gIHC*Vj9#7iITMAOv-MpO~ zkn0o!a>7!_s|J(~nCYQjGwqI>j!QLo)sV>@sq|U6W@g+snmV>tZp3u5eR6G?fw<#7 z0VEeeci5CF_aLSBO~*Z13KyR7bgqYM><`+#z(!}Z830f57lgavhKJ>=Wzi zv<7sE|H@UX`waj9|Np|8Y0Urt00v1!K~w_(oH;^J!)liv00000NkvXXu0mjf`Y3;< literal 0 HcmV?d00001 diff --git a/images/tabbar/category.png b/images/tabbar/category.png new file mode 100644 index 0000000000000000000000000000000000000000..237d84339ce607cfd46a63eb3785267fd0054cf4 GIT binary patch literal 725 zcmV;`0xJE9P)KDHEGxPwDiuVlD|Hh{;&(&l*?Dtk5|P+0Qts*Fo^$TZ$(v4J8=hF>uk{%qm&@%= zr_)E%7T~7_ZCE0am}G8RL0p^l>;QxJ+wJxu=4UqGSJ1D+tCdQn4#wm0^N1%D%rbil z-c_yu7+#~E20?WNpC^ReJQND;4H`t7%49OfSxuP$Two{celSd(vrXK5hs|kyx7*#8 zOeQxvnhC%KrX6yKb&KNJpl~V`i^V1#!31!K|Gka_te+hShr>&M@%-iYgv-uP_jeZ& z(6m~uAC7Expj0X?Lp}z>RK+&&0p8l2))Vw&qtW>3XeNLVl}hC#>oxERX!VlU^(?+y zwpcEg7o*YWQ}i#k*6)9HI-O>RUc!V>yJ=58vu zU8~ia_M9hxZ5VYw4&d1VPP#l;23-X&IL*%ZJr>cJc?GoD_wYCDSyuoIk1)&u?XZEK zILvEw7vDu`R)f>@vS@IJfL2=}FEN{EHDv;1v)LI~`9p$bV5Mybuipim^XcaZAz^y5 z3BWtLy~hmqZI5Exr*M9hH-MjW)V$e&QQye#`r`m*2Y7`(`)!lLYlqbb#ivq{NaU*{ zm;jto?;t&g1)NrQ6!L+>Y1BfoSbXCMCV&u}Uwe*UJn00000NkvXX Hu0mjfw1!B% literal 0 HcmV?d00001 diff --git a/images/tabbar/download-active.png b/images/tabbar/download-active.png new file mode 100644 index 0000000000000000000000000000000000000000..6d4c0611ee704f8ccf04cd41d8b39f8920eb4c7c GIT binary patch literal 550 zcmV+>0@?kEP)3Q$=zI)C)y=?)7{`iv{NYw$gV@P1NWz>`2$y5fCZaT}$4dfm8 zza3a_4L9(5d6<227l}O{W@ydhgm!@6?I&4KUX=pf1M|pHd2j(}K{KpAU_k8}yIfdf zgdQx^2wjLy)?Dphl>q}}SzD-(M1zr57Qp1RJV4Ifyr;H}9i02Y>pivmc8NSV-~!M> z&eOJIcL*AA7yJ{8UQmCn^S)`v?!fC{U}}{+NreOM z#0SoQ5cA4CUTgy(jj>R$N;!zv-E69c@+1agFB1F1|TxgD=%># zIB@((Wxy|%HNV;;Cvm2oa zZNFl`018aKd@O`jw5GJ31)v2@X!{Xr07YiA`ibB4EC4NNhGDLOu;OLpmTn-*^fv$i o0RR8O=%9oE000I_L_t&o01i$VP{ql0IsgCw07*qoM6N<$f;yY^NB{r; literal 0 HcmV?d00001 diff --git a/images/tabbar/download.png b/images/tabbar/download.png new file mode 100644 index 0000000000000000000000000000000000000000..c5bad93b10c65ce40f3d7840c0a9d5a70f9555ff GIT binary patch literal 504 zcmV zWW0{B*NLDodc(f`nR(+EJMl*EkN-LYtvWE7Op+V}*H5yQ0oS5MUT2`|!2j*Qcs$#RtI$MMP8(6u%yXRYXL@kDcIC@B)Cm0`dwN5rNY3=IwG*e@AZUhvY`*wh^TGEti0ao&b{}`1x`Edb0z>p#XDmEJGFIc_p!e&blPd2QwM;GCvYDVcP}&F(LMS9 z$YJ5mLJiKm3zZ`PGnQ&m1=TrdT;^9F{t$yGYVj`8$rS$LF!^bc6#w5{B8~`SsQ1XzaD(?p+ zjzO@uyMM5MjF$z)iuQZ`5(R*ZrwFH&mtJULeV93<9Onck;4+UD)+P0l>rqrB4`YWif05Vf`@(>~Z@|soB0E z%$&jiaPjb@sa4+!vv3?=3<3e^>)Dom69|5U0N~>7r%g?HQ*gb++%O0>g|%{X;}(&{ zh62FFlV?n=_J+tpY4~6eD3CAZmVWE7bx{DAcuMI3Q>z>rwm{nB7zCj3MX9-Q2bM!1 z09-sgXKB^fv5B)B!yphKpUyV*yMyu>2mljL0G_ur<+Y#*N7au(umL_PH#Y7Wl$#Gg zskM10bM(EpXMJopN?tHj-y1HZ@Ub)d;5V4s>UHHE|&sogqiVfX^USA&o?qJj_2MUxZ z1jv4%_Q9UT4`)6^Qhbh)D%lI~T*kA{?%qDUf-aZ0#}C>imiN7011^4FtfQ z{?RHOgKck3t;U`JwFTZ6#rWMM0HNJXK${6i_`)5)^(L<@2lnR^97{akv#yW$0<+mnGI!rj08aDDvgdh!YgOKjTw^9q=@I~rC`UIAf=O`CtL3gCP8VMd8`qZ=A;2gUF)QD65Q?wGLG zu>kNryNJ1y8s;Cy*O|*56ZSe5fUt{=qkj${%w3@XUrmCA(Ii{}{N;tEDmPDp@mxhA z2m=tEaMJv80F!pdlFoPHDj`00v1!K~w_(p&i1X&Nm)300000 LNkvXXu0mjfLSujS literal 0 HcmV?d00001 diff --git a/images/tabbar/home.png b/images/tabbar/home.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa7332eb53dcb7217133b5de2f67be82981876d GIT binary patch literal 1267 zcmVoLfD;oFUy8-z zFK*Z397~h{AMoo32M4bY4-en)B4K=de2mBBuW=*hr!W z$4Y<%o`p99@G@2ud^}0u?eSfAclRuDf`AueBftmzmZ71cOQyL=B($})z2V0Am+4%7 zez6eX1AZIeFZv~-UzbSW#`ua#_*ah!(~nGm5BM&?UkH;Lk-#PQ^+jPzld_dV2m>eql8<0TOr;+c`2aayD#IdYnW;YisK>9+9*47}I_z z0wnM(+)ct~u(1krdOjUk=qblx4mPj^Ccp=LfwDgxI19r7i3F8HmIZpy4@JzfH@W-Wz)4V2;KpwZjvKs3ut^^Pu?fgE@{4v+rG8~jh zV8d=v32WTat092j+$#yF`EFkc-p|e-t+eaTxRKyU@+KQwQwcQ$@D9J-5#($h0Q?c> zKs~l464=;1dd^h@P$ufL(<1DrxQdK0bKY8b(0c-WnJAIvl!F! zQhj#Y>llj!yvzbXnrlMLE9ZJu6+oGPZE;%C)j&uE>#4m3!I-F*w~-( z+<`kNO?8w^)c9SVTunuNoH?XB*ASo_sDvV=@;B`Qs)B5#8(aWB%6#ff9wgdCM^y~I zVa3i@*)ArT1*R_aE$u3CB|v#lDz6DRg{mh&NsWb3fT-P68Z>;7s+yodw!fV>_8eLR z-1hS%z;=kih5=AnkdPsW2M_fk$Oy?2>~9t zWHOu+kO}Q|v}j%db+j3=q!M(JyaIwUHq-p`3Ycj-8Wc}n0YMp?Y5tKaz_P5V-|dox zu!X2o7JB*PWbEslgSgNVi2znrKXpwM*J<(dpLV3-=WAcbHJn5xAl#~T6qOSYa5Em@ z-v~;YW@20cm5&#Tv5la{-!P2{FypiFymA5>Z_nb*xZx`xDjnYe00960w0Yx~00006 dNklPpz literal 0 HcmV?d00001 diff --git a/images/tabbar/mine-active.png b/images/tabbar/mine-active.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5d03339cb89812a8d68e4b2e669d0f7d5e30e5 GIT binary patch literal 1879 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 literal 0 HcmV?d00001 diff --git a/images/tmpclaude-ecff-cwd b/images/tmpclaude-ecff-cwd new file mode 100644 index 0000000..067cda1 --- /dev/null +++ b/images/tmpclaude-ecff-cwd @@ -0,0 +1 @@ +/d/code/youerqimeng-server/miniapp/images diff --git a/miniprogram_npm/@jdmini/api/index.d.ts b/miniprogram_npm/@jdmini/api/index.d.ts new file mode 100644 index 0000000..e06e58d --- /dev/null +++ b/miniprogram_npm/@jdmini/api/index.d.ts @@ -0,0 +1,295 @@ +// Generated by dts-bundle v0.7.3 + +declare module '@jdmini/api' { + import { onLoginReady, waitLogin } from '@jdmini/api/app'; + import HttpClient, { gatewayHttpClient, baseHttpClient, apiHttpClient } from '@jdmini/api/httpClient'; + import { injectApp, injectPage, injectComponent, hijackApp, hijackAllPage } from '@jdmini/api/injector'; + import adManager from '@jdmini/api/adManager'; + export { onLoginReady, waitLogin, injectApp, injectPage, injectComponent, hijackApp, hijackAllPage, gatewayHttpClient, baseHttpClient, apiHttpClient, HttpClient, adManager, }; +} + +declare module '@jdmini/api/app' { + export interface AppOptions { + gatewayUrl?: string; + baseUrl?: string; + apiUrl?: string; + } + export interface PageOptions { + showInterstitialAd?: boolean; + } + export function initApp(options?: AppOptions): Promise; + export function showPage(options: PageOptions | undefined, pageId: string): Promise; + export const checkTokenValid: () => boolean; + /** + * 确保登录完成 + * @param {Function} callback - 回调函数 + * @returns {void} + */ + export function onLoginReady(callback: (...args: any[]) => void): void; + /** + * 等待登录完成 + * @returns {Promise} + */ + export function waitLogin(): Promise; + export function login(): Promise; + export function fetchEchoData(): Promise; + export function trackVisit(): Promise; +} + +declare module '@jdmini/api/httpClient' { + import { HttpClientOptions, RequestOptions, ApiResponse } from '@jdmini/api/types'; + class HttpClient { + constructor({ baseURL, timeout }: HttpClientOptions); + setBaseURL(baseURL: string): void; + /** + * 请求 + * @param {string} path 路径 + * @param {string} method 方法, 默认GET + * @param {Object} data 数据, 默认{} + * @param {Object} options 传入wx.request的其他配置, 默认{} + * @returns {Promise} 返回一个Promise对象 + */ + request(path: string, method?: WechatMiniprogram.RequestOption['method'], data?: Record, options?: RequestOptions): Promise>; + /** + * 上传文件 + * @param {string} filePath 文件路径 + * @param {Object} data 数据, 默认{} + * @param {'avatar' | 'file'} type 类型, 默认'file' + * @returns {Promise} 返回一个Promise对象 + */ + uploadFile(filePath: string, data?: Record, type?: 'avatar' | 'file'): Promise>; + /** + * 上传文件 + * @param {string} filePath 文件路径 + * @param {Object} data 数据, 默认{} + * @param {'avatar' | 'file'} type 类型, 默认'file' + * @returns {Promise} 返回一个Promise对象 + */ + upload(path: string, filePath: string, data?: Record): Promise>; + /** + * 删除文件 + * @param {number} fileId 文件id + * @returns {Promise} 返回一个Promise对象 + */ + deleteFile(fileId: number): Promise>; + /** + * 上传头像 + * @param {string} filePath 文件路径 + * @returns {Promise} 返回一个Promise对象 + */ + uploadAvatar(filePath: string): Promise>; + } + export const gatewayHttpClient: HttpClient; + export const baseHttpClient: HttpClient; + export const apiHttpClient: HttpClient; + export default HttpClient; +} + +declare module '@jdmini/api/injector' { + interface AppConfig { + onLaunch?: (...args: any[]) => void | Promise; + [key: string]: any; + } + interface PageConfig { + onShow?: (...args: any[]) => void | Promise; + [key: string]: any; + } + interface ComponentConfig { + methods?: { + onLoad?: (...args: any[]) => void | Promise; + onShow?: (...args: any[]) => void | Promise; + [key: string]: any; + }; + [key: string]: any; + } + interface InjectAppOptions { + gatewayUrl?: string; + baseUrl?: string; + apiUrl?: string; + } + interface InjectPageOptions { + showInterstitialAd?: boolean; + } + /** + * 注入应用配置 + * @param {Object} options - 配置选项 + * @param {string} [options.gatewayUrl] - 网关地址,默认使用CONFIG.API.GATEWAY_URL + * @param {string} [options.baseUrl] - 基础地址,默认使用CONFIG.API.BASE_URL + * @param {string} [options.apiUrl] - api地址,默认使用CONFIG.API.API_URL + * @returns {Function} 返回一个接收应用配置的函数 + */ + export function injectApp(options?: InjectAppOptions): (appConfig: AppConfig) => AppConfig; + /** + * 注入页面配置 + * @param {InjectPageOptions} options - 配置选项 + * @param {boolean} [options.showInterstitialAd] - 是否在onShow显示插屏广告,默认不显示 + * @returns {Function} 返回一个接收页面配置的函数 + */ + export function injectPage(options?: InjectPageOptions): (pageConfig?: PageConfig) => PageConfig; + /** + * 注入组件配置 + * @param {InjectPageOptions} options - 配置选项 + * @param {boolean} [options.showInterstitialAd] - 是否在onShow显示插屏广告,默认不显示 + * @returns {Function} 返回一个接收组件配置的函数 + */ + export function injectComponent(options?: InjectPageOptions): (pageConfig?: PageConfig) => ComponentConfig; + /** + * 劫持App + * @param {InjectAppOptions} options - 配置选项 + * @param {string} [options.gatewayUrl] - 网关地址,默认使用CONFIG.API.GATEWAY_URL + * @param {string} [options.baseUrl] - 基础地址,默认使用CONFIG.API.BASE_URL + * @param {string} [options.apiUrl] - api地址,默认使用CONFIG.API.API_URL + * @returns {void} + */ + export const hijackApp: (options?: InjectAppOptions) => void; + /** + * 劫持所有Page + * @param {InjectPageOptions} options - 配置选项 + * @param {boolean} [options.showInterstitialAd] - 是否在onShow显示插屏广告,默认不显示 + * @returns {void} + */ + export const hijackAllPage: (options?: InjectPageOptions) => void; + export {}; +} + +declare module '@jdmini/api/adManager' { + import { AdData, LinkData, TopData } from '@jdmini/api/types'; + type Ads = Partial>; + class AdManager { + /** + * 广告数据 + */ + ads: Ads; + /** + * 友情链接数据 + */ + link: LinkData[]; + /** + * 友情链接顶部广告数据 + */ + top: TopData | null; + constructor(); + /** + * 确保广告数据就绪 + * @param {Function} callback - 回调函数 + * @returns {void} + */ + onDataReady: (callback: (...args: any[]) => void) => void; + /** + * 等待广告数据加载完成 + * @returns {Promise} + */ + waitAdData: () => Promise; + /** + * 初始化广告数据 + * @returns {void} + */ + init: () => void; + /** + * 创建并展示插屏广告 + * @returns {Promise} + */ + createAndShowInterstitialAd: () => Promise; + /** + * 创建并展示激励视频广告 + * @param {any} context - 页面上下文 + * @param {string} [pageId] - 页面ID + * @returns {Promise} 是否完成播放 + */ + createAndShowRewardedVideoAd: (context: any, pageId?: string) => Promise; + } + const _default: AdManager; + export default _default; +} + +declare module '@jdmini/api/types' { + export interface Config { + API: { + GATEWAY_URL: string; + BASE_URL: string; + API_URL: string; + }; + APP: { + APP_ID: number; + LOGIN_MAX_RETRY: number; + }; + HTTP: { + TIMEOUT: number; + }; + DATA: { + PAGE_ID: string; + }; + STORAGE_KEYS: { + TOKEN: string; + USER_INFO: string; + SPA_DATA: string; + LINK_DATA: string; + TOP_DATA: string; + }; + EVENT_KEYS: { + LOGIN_SUCCESS: string; + AD_DATA_READY: string; + REWARDED_VIDEO_AD_CLOSE: string; + }; + } + export interface HttpClientOptions { + baseURL: string; + timeout?: number; + } + export interface RequestOptions { + headers?: Record; + [key: string]: any; + } + export interface LoginData { + appId: number; + code: string; + brand: string; + model: string; + platform: string; + } + export interface ApiResponse { + code: number; + message?: string; + data?: T; + } + export interface UserInfo { + token: string; + user: { + id: number; + name: string; + avatar: string; + openId: string; + }; + } + export interface EchoData { + isPublished: boolean; + spads: AdData[]; + links: LinkData[]; + top: TopData; + version: number; + } + export interface AdData { + id: number; + status: number; + appPage: 'banner' | 'custom' | 'video' | 'interstitial' | 'rewarded'; + ads: { + id: number; + type: number; + adId: number; + adUnitId: string; + }[]; + } + export interface LinkData { + appId: string; + appLogo: string; + linkName: string; + linkPage: string; + } + export interface TopData { + appId: string; + appLogo: string; + linkName: string; + appDsc: string; + } +} + diff --git a/miniprogram_npm/@jdmini/api/index.js b/miniprogram_npm/@jdmini/api/index.js new file mode 100644 index 0000000..fc11843 --- /dev/null +++ b/miniprogram_npm/@jdmini/api/index.js @@ -0,0 +1,4 @@ +/*! + * @jdmini/api v1.0.10 + * + */(()=>{"use strict";var t={616:function(t,e,n){var r=this&&this.__awaiter||function(t,e,n,r){return new(n||(n=Promise))((function(o,a){function i(t){try{u(r.next(t))}catch(t){a(t)}}function s(t){try{u(r.throw(t))}catch(t){a(t)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(i,s)}u((r=r.apply(t,e||[])).next())}))},o=this&&this.__generator||function(t,e){var n,r,o,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=s(0),i.throw=s(1),i.return=s(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(s){return function(u){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,s[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0?e.ads[0].adUnitId:"",t}),{}));var n=wx.getStorageSync(i.default.STORAGE_KEYS.LINK_DATA);n&&(t.link=n);var r=wx.getStorageSync(i.default.STORAGE_KEYS.TOP_DATA);r&&r.appDsc.length>40&&(r.appDsc=r.appDsc.substring(0,40)+"...",t.top=r),t.adDataReady=!0,s.default.emit(i.default.EVENT_KEYS.AD_DATA_READY)},this.createAndShowInterstitialAd=function(){return r(t,void 0,void 0,(function(){var t;return o(this,(function(e){switch(e.label){case 0:return this.interstitialAd&&this.interstitialAd.destroy(),[4,this.waitAdData()];case 1:e.sent(),e.label=2;case 2:return e.trys.push([2,5,,6]),[4,this.createInterstitialAd()];case 3:return e.sent(),[4,this.showInterstitialAd()];case 4:return e.sent(),[3,6];case 5:return t=e.sent(),console.error("创建插屏广告失败:",t),[3,6];case 6:return[2]}}))}))},this.createInterstitialAd=function(){return r(t,void 0,void 0,(function(){var t=this;return o(this,(function(e){return[2,new Promise((function(e,n){t.ads.interstitial?(t.interstitialAd=wx.createInterstitialAd({adUnitId:t.ads.interstitial}),t.interstitialAd.onLoad((function(){console.log("插屏广告加载成功"),e()})),t.interstitialAd.onError((function(t){console.error(t),n(new Error("插屏广告加载失败"))})),t.interstitialAd.onClose((function(){console.log("插屏广告关闭")}))):n(new Error("插屏广告未配置"))}))]}))}))},this.showInterstitialAd=function(){return r(t,void 0,void 0,(function(){var t,e;return o(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),console.log("开始展示插屏广告"),[4,null===(e=this.interstitialAd)||void 0===e?void 0:e.show()];case 1:return[2,n.sent()];case 2:return t=n.sent(),console.error("插屏广告展示失败:",t),[3,3];case 3:return[2]}}))}))},this.createAndShowRewardedVideoAd=function(e,n){return r(t,void 0,void 0,(function(){var t,r,a;return o(this,(function(o){switch(o.label){case 0:return[4,this.waitAdData()];case 1:o.sent(),t=n||(null===(a=null==e?void 0:e.data)||void 0===a?void 0:a[i.default.DATA.PAGE_ID]),o.label=2;case 2:if(o.trys.push([2,6,,7]),!t)throw new Error("未指定pageId或者context");return this.rewardedVideoAds[t]?[3,4]:[4,this.createRewardedVideoAd(t)];case 3:o.sent(),o.label=4;case 4:return[4,this.showRewardedVideoAd(t)];case 5:return o.sent(),[3,7];case 6:return r=o.sent(),console.error("创建激励视频广告失败:",r),[3,7];case 7:return[2,new Promise((function(e){s.default.on(i.default.EVENT_KEYS.REWARDED_VIDEO_AD_CLOSE,(function(n,r){n===t&&e(r)}))}))]}}))}))},this.createRewardedVideoAd=function(e){return r(t,void 0,void 0,(function(){var t=this;return o(this,(function(n){return[2,new Promise((function(n,r){t.ads.rewarded?(t.rewardedVideoAds[e]=wx.createRewardedVideoAd({adUnitId:t.ads.rewarded}),t.rewardedVideoAds[e].onLoad((function(){console.log("激励视频广告加载成功"),n()})),t.rewardedVideoAds[e].onError((function(t){console.error(t),r(new Error("激励视频广告加载失败"))})),t.rewardedVideoAds[e].onClose((function(t){s.default.emit(i.default.EVENT_KEYS.REWARDED_VIDEO_AD_CLOSE,e,t.isEnded),console.log("激励视频广告关闭")}))):r(new Error("激励视频广告未配置"))}))]}))}))},this.showRewardedVideoAd=function(e){return r(t,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),console.log("开始展示激励视频广告"),[4,null===(n=this.rewardedVideoAds[e])||void 0===n?void 0:n.show()];case 1:return[2,r.sent()];case 2:return t=r.sent(),console.error("激励视频广告展示失败:",t),[3,3];case 3:return[2]}}))}))}};e.default=new u},859:function(t,e,n){var r=this&&this.__awaiter||function(t,e,n,r){return new(n||(n=Promise))((function(o,a){function i(t){try{u(r.next(t))}catch(t){a(t)}}function s(t){try{u(r.throw(t))}catch(t){a(t)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(i,s)}u((r=r.apply(t,e||[])).next())}))},o=this&&this.__generator||function(t,e){var n,r,o,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=s(0),i.throw=s(1),i.return=s(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(s){return function(u){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,s[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]s.default.APP.LOGIN_MAX_RETRY)throw wx.showToast({title:"网络异常,无法初始化",icon:"none",duration:2e3}),new Error("网络异常,无法初始化");o.label=1;case 1:return o.trys.push([1,7,,9]),t=wx.getDeviceInfo(),[4,wx.login()];case 2:return e=o.sent().code,n={appId:s.default.APP.APP_ID,code:e,brand:t.brand,model:t.model,platform:t.platform},[4,i.gatewayHttpClient.request("/wx/v1/api/login","POST",n)];case 3:return 200===(r=o.sent()).code&&r.data?(wx.setStorageSync(s.default.STORAGE_KEYS.TOKEN,r.data.token),wx.setStorageSync(s.default.STORAGE_KEYS.USER_INFO,r.data.user),l=0,u.default.emit(s.default.EVENT_KEYS.LOGIN_SUCCESS),[3,6]):[3,4];case 4:return l++,[4,h()];case 5:o.sent(),o.label=6;case 6:return[3,9];case 7:return o.sent(),l++,[4,h()];case 8:return o.sent(),[3,9];case 9:return[2]}}))}))}function p(){return r(this,void 0,void 0,(function(){var t;return o(this,(function(n){switch(n.label){case 0:return wx.removeStorageSync(s.default.STORAGE_KEYS.SPA_DATA),wx.removeStorageSync(s.default.STORAGE_KEYS.LINK_DATA),wx.removeStorageSync(s.default.STORAGE_KEYS.TOP_DATA),(0,e.checkTokenValid)()?[4,i.gatewayHttpClient.request("/wx/v1/api/echo","GET")]:[2];case 1:return 200===(t=n.sent()).code&&t.data?(t.data.spads&&wx.setStorageSync(s.default.STORAGE_KEYS.SPA_DATA,t.data.spads),t.data.links&&wx.setStorageSync(s.default.STORAGE_KEYS.LINK_DATA,t.data.links),t.data.top&&wx.setStorageSync(s.default.STORAGE_KEYS.TOP_DATA,t.data.top),[3,5]):[3,2];case 2:return 401!==t.code?[3,5]:[4,h()];case 3:return n.sent(),[4,p()];case 4:n.sent(),n.label=5;case 5:return[2]}}))}))}function w(){return r(this,void 0,void 0,(function(){var t;return o(this,(function(e){switch(e.label){case 0:return e.trys.push([0,5,,6]),[4,i.gatewayHttpClient.request("/wx/v1/api/visit","POST")];case 1:return 401!==e.sent().code?[3,4]:[4,h()];case 2:return e.sent(),[4,w()];case 3:e.sent(),e.label=4;case 4:return[3,6];case 5:return t=e.sent(),console.error("访问统计失败:",t),[3,6];case 6:return[2]}}))}))}e.checkTokenValid=function(){var t=wx.getStorageSync(s.default.STORAGE_KEYS.TOKEN);return!(!t||t.length<32)}},28:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0});var n={API:{GATEWAY_URL:"https://ca.miniappapi.com",BASE_URL:"https://app.jd027.com/v1/api",API_URL:"https://cp.miniappapi.com"},APP:{APP_ID:wx.getExtConfigSync().appId||313,LOGIN_MAX_RETRY:2},HTTP:{TIMEOUT:5e3},DATA:{PAGE_ID:"jdwx-page-id"},STORAGE_KEYS:{TOKEN:"jdwx-token",USER_INFO:"jdwx-userinfo",SPA_DATA:"jdwx-spadata",LINK_DATA:"jdwx-linkdata",TOP_DATA:"jdwx-topdata"},EVENT_KEYS:{LOGIN_SUCCESS:"jdwx-login-success",AD_DATA_READY:"jdwx-ad-data-ready",REWARDED_VIDEO_AD_CLOSE:"jdwx-rewarded-video-ad-close"}};e.default=n},144:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(){this.events={}}return t.prototype.on=function(t,e){if("string"!=typeof t)throw new TypeError("eventName must be a string");if("function"!=typeof e)throw new TypeError("callback must be a function");return this.events[t]||(this.events[t]=[]),this.events[t].push(e),this},t.prototype.emit=function(t){for(var e=[],n=1;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=200&&d.statusCode<300)return[2,d.data];if(401===d.statusCode)return[2,{code:401,message:"未授权"}];throw new Error(d.data.message||"请求失败");case 4:throw f=a.sent(),console.error("网络错误:",f),wx.showToast({title:f instanceof Error?f.message:"网络错误",icon:"none",duration:2e3}),f;case 5:return[2]}}))}))},t.prototype.uploadFile=function(t){return o(this,arguments,void 0,(function(t,e,n){var r,o,i,u;return void 0===e&&(e={}),void 0===n&&(n="file"),a(this,(function(a){r=this.baseURL===s.default.API.GATEWAY_URL,o=wx.getStorageSync(s.default.STORAGE_KEYS.TOKEN),i="avatar"===n?"/avatar":"/file/new",u=this.joinURL(this.baseURL,"".concat(r?"/wx/v1/api":"").concat(i));try{return[2,new Promise((function(n,r){wx.uploadFile({url:u,name:"file",filePath:t,formData:e,header:{"Content-Type":"application/x-www-form-urlencoded",Authorization:o},success:function(t){if(t.statusCode>=200&&t.statusCode<300)n(JSON.parse(t.data));else{if(401!==t.statusCode)throw new Error(t.data.message||"上传失败");n(JSON.parse(t.data))}},fail:function(){throw new Error("网络错误")}}).onProgressUpdate((function(t){console.log("上传进度",t.progress),console.log("已经上传的数据长度",t.totalBytesSent),console.log("预期需要上传的数据总长度",t.totalBytesExpectedToSend)}))}))]}catch(t){throw console.error("上传失败:",t),wx.showToast({title:t instanceof Error?t.message:"上传失败",icon:"none",duration:2e3}),t}return[2]}))}))},t.prototype.upload=function(t,e){return o(this,arguments,void 0,(function(t,e,n){var r,o,i;return void 0===n&&(n={}),a(this,(function(a){r=this.baseURL===s.default.API.GATEWAY_URL,o=wx.getStorageSync(s.default.STORAGE_KEYS.TOKEN),i=this.joinURL(this.baseURL,"".concat(r?"/wx/v1/api":"").concat(t));try{return[2,new Promise((function(t,r){wx.uploadFile({url:i,name:"file",filePath:e,formData:n,header:{"Content-Type":"application/x-www-form-urlencoded",Authorization:o},success:function(e){if(e.statusCode>=200&&e.statusCode<300)t(JSON.parse(e.data));else{if(401!==e.statusCode)throw new Error(e.data.message||"上传失败");t(JSON.parse(e.data))}},fail:function(){throw new Error("网络错误")}}).onProgressUpdate((function(t){console.log("上传进度",t.progress),console.log("已经上传的数据长度",t.totalBytesSent),console.log("预期需要上传的数据总长度",t.totalBytesExpectedToSend)}))}))]}catch(t){throw console.error("上传失败:",t),wx.showToast({title:t instanceof Error?t.message:"上传失败",icon:"none",duration:2e3}),t}return[2]}))}))},t.prototype.deleteFile=function(t){return o(this,void 0,void 0,(function(){var e;return a(this,(function(n){return e=this.baseURL===s.default.API.GATEWAY_URL,[2,this.request("".concat(e?"/wx/v1/api":"","/file/del"),"GET",{id:t})]}))}))},t.prototype.uploadAvatar=function(t){return o(this,void 0,void 0,(function(){return a(this,(function(e){return[2,this.uploadFile(t,{},"avatar")]}))}))},t}();e.gatewayHttpClient=new u({baseURL:s.default.API.GATEWAY_URL,timeout:s.default.HTTP.TIMEOUT}),e.baseHttpClient=new u({baseURL:s.default.API.BASE_URL,timeout:s.default.HTTP.TIMEOUT}),e.apiHttpClient=new u({baseURL:s.default.API.API_URL,timeout:s.default.HTTP.TIMEOUT}),e.default=u},156:function(t,e,n){var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var o=Object.getOwnPropertyDescriptor(e,n);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,o)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),o=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),a=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&r(e,t,n);return o(e,t),e},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.adManager=e.HttpClient=e.apiHttpClient=e.baseHttpClient=e.gatewayHttpClient=e.hijackAllPage=e.hijackApp=e.injectComponent=e.injectPage=e.injectApp=e.waitLogin=e.onLoginReady=void 0;var s=n(859);Object.defineProperty(e,"onLoginReady",{enumerable:!0,get:function(){return s.onLoginReady}}),Object.defineProperty(e,"waitLogin",{enumerable:!0,get:function(){return s.waitLogin}});var u=a(n(161));e.HttpClient=u.default,Object.defineProperty(e,"gatewayHttpClient",{enumerable:!0,get:function(){return u.gatewayHttpClient}}),Object.defineProperty(e,"baseHttpClient",{enumerable:!0,get:function(){return u.baseHttpClient}}),Object.defineProperty(e,"apiHttpClient",{enumerable:!0,get:function(){return u.apiHttpClient}});var c=n(718);Object.defineProperty(e,"injectApp",{enumerable:!0,get:function(){return c.injectApp}}),Object.defineProperty(e,"injectPage",{enumerable:!0,get:function(){return c.injectPage}}),Object.defineProperty(e,"injectComponent",{enumerable:!0,get:function(){return c.injectComponent}}),Object.defineProperty(e,"hijackApp",{enumerable:!0,get:function(){return c.hijackApp}}),Object.defineProperty(e,"hijackAllPage",{enumerable:!0,get:function(){return c.hijackAllPage}});var l=i(n(616));e.adManager=l.default},718:function(t,e,n){var r=this&&this.__assign||function(){return r=Object.assign||function(t){for(var e,n=1,r=arguments.length;ntYL|T)GRIfA+a?~DfhW-E^|pqGM7?rA;~3FgrXyt zB9zu>bam-iB$B2?OfD%karB($Ip_cLJfHXbKJWYg_f28YZ4~9zga`M&@4ARg(+bb1(t`477g<5>-x(1OV88Eoi}vDv}hr2Rr#;9<6k{o*CVZ(+Us# z&7+tQYiP7ns7?yHS7*#ZH>$7>@@w@&BYg!X7+bgjpXNCu2KtJXo5(fH)41> z&7wvzvVkY|+nh~jO(yXg{bIX=;o}RITkMwxa0dh4oTAr2x^oKC65flQ#oE$C%Fx%=He1A*c=hr?eOUL#AEhZT%N8lXd@9`N>%JEvlb#PTG42uC z@mhJJ`8~51aVFnW8I>JTk!FSUh6gbtTvfGckGQhZfG-_|hEuOUNJrn-lakxQzRzqN zEG`HLKcvn3=C6BO9ZYk#G&COl&>F%%RuaE!U&{uN0vU2JLA}#0KA@C%VChhF`-&q9 z>B^cdFPKnh>gH3w*GKcIx0VuJ>2dnYz!sj|$d8*0h}@HM7DZ< zom_lPRBTMw!qG{%*{K7xUg|i9XDej_jIDw2Cn z=lX45DekndIx{>ahvQjxF7Omlr>;$shDLH3R`}=v{vHRqI^Lj8ZM8yiPl2vQ!P3Kk z0TherS~QvuvehIF!vj41Rb(C-ltgS5H7;{l4FMsgp{&}*6>RMvP)1e1Hz~Ba%XpiT zO>X+dbt7Btw58YV%Qt;K$uS?wNMc5qiGHNJ(o3aXUiZ8FlJd~r+#7%LY8?2)^FQOa z)eDhHCO&K855g?ZzE5x6;evuki~EtNA=Q}v>U$QPuy zLzNefra8UqbMhOjl&=h%)D!h@hLc?Am*?0oQLr6im7;LWv~2#T;pu$PE_^DK4XFbEV;K$#8+LHdIXAB^KVWM`2dgc;PW}x9 CY1HNb literal 0 HcmV?d00001 diff --git a/miniprogram_npm/@jdmini/components/icons/home.png b/miniprogram_npm/@jdmini/components/icons/home.png new file mode 100644 index 0000000000000000000000000000000000000000..11016daa08d27f848fe96b9b380b9c335fb72a3e GIT binary patch literal 1070 zcmeAS@N?(olHy`uVBq!ia0vp^fgsGm1|(PYdzk?#&H|6fVg?31We{epSZZI!z`*># z)5S5QV$R#yXS@9ZB@Wb=+a+}bWvOU#KT-Bwlwz9JXdz3F1k+6@b*u>Oqr!9FE& z2JddkrI88Fy;ocWjk&#?vW%oAJ&c-ToqFufcf0TRp5G~sx7}~Gx77Oi&htON-m9Mf zeeZLZLYd?#i&aaQ{xhouGEQF6;2Cm2B}?FVVH)=lu?J=b^J?-qC$^mM_f?f+UDo(x z{Wn>Qz*42N8ij%db9a7vww-ao;#DoPd>=(6OgYAsemmpRee1I-8>N3V_&mt@&GY8- z+z4Zjo`%f_cz&{++cq<$=85*fUEVQ#cNWgr^D~zt;-T_MEAKZe#BoDgc1rfQ%hu{e(Hs_&vDHJy$3J@q~Q`GCG7&sL)qenQt7 z`cKaLBT}HE942p|+3{5*BHZlbE{`{EVf*&5m~khsW1q*FG)=GB$-Ae)_{6E{(FZQg z`DooB-E?DrqqoSP&8kaWGJk#&R^P?*_W=LFom^8L7pjyg>c^Ko=bd+m??LZ_Ir)uq zE1B9SL@cSfc*t8aWa*JjPrJolr3QownVuHcy>e=SmP*;;_7ndC{14crx>Mt&M`4?oFk3NKl6#C^y8Q)kbZ(h-qQgF5d%% zCGk7_nam68%R;<3I;$IYA4uZg=+DGnpcbf^A-^^6{>v$?h#r(zpvld(r*4w#a@{#NV%`$&?uk3W$JxA1_6KWA65osj| z5fl#U+#l4pJxf(+#+J$@XRG;wI_C%V<%{;Mx6WLxX6MD`t?IW#<~{Q_uH-9I|1Q~I zB|DvE!oG4jwsX1#dqlrqs%5BXiq?sn5|`lI_pkTYH|}HGADI1>QT}_{M8{Lb>*j9r z?ZG0h2P7E9xLH$pZpd-7cWb*JRN{3U;@HKwk}D~EC2 zm4r!lw?+T$a;o*1^Hkv7Nztl9v)-B(6#R2hty{uBt(9;4j~CK@kB9p9lQ> YvrilkX{>z)EC3ihUHx3vIVCg!0C%_Ik^lez literal 0 HcmV?d00001 diff --git a/miniprogram_npm/@jdmini/components/icons/link-active.png b/miniprogram_npm/@jdmini/components/icons/link-active.png new file mode 100644 index 0000000000000000000000000000000000000000..433ec7bf24d3f2a0367fcaf00069d9c7ca3105ed GIT binary patch literal 2796 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 { + this.setData({ ads: adManager.ads }) + }) + }, + }, + methods: { + } +}) diff --git a/miniprogram_npm/@jdmini/components/jdwx-ad/index.json b/miniprogram_npm/@jdmini/components/jdwx-ad/index.json new file mode 100644 index 0000000..32640e0 --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-ad/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/miniprogram_npm/@jdmini/components/jdwx-ad/index.wxml b/miniprogram_npm/@jdmini/components/jdwx-ad/index.wxml new file mode 100644 index 0000000..1b24adf --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-ad/index.wxml @@ -0,0 +1,5 @@ + + + + + diff --git a/miniprogram_npm/@jdmini/components/jdwx-ad/index.wxss b/miniprogram_npm/@jdmini/components/jdwx-ad/index.wxss new file mode 100644 index 0000000..de627db --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-ad/index.wxss @@ -0,0 +1,7 @@ +.jdwx-ad-component { + padding: 10rpx; +} + +.jdwx-ad-item { + bottom: 10rpx; +} \ No newline at end of file diff --git a/miniprogram_npm/@jdmini/components/jdwx-link/index.js b/miniprogram_npm/@jdmini/components/jdwx-link/index.js new file mode 100644 index 0000000..887d749 --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-link/index.js @@ -0,0 +1,37 @@ +import { adManager } from '@jdmini/api' + +Component({ + properties: { + }, + data: { + link: [], + top: {}, + }, + pageLifetimes: { + show: function () { + adManager.onDataReady(() => { + this.setData({ + link: adManager.link, + top: adManager.top + }) + }) + }, + }, + methods: { + gotopLink: function () { + wx.vibrateShort() + wx.openEmbeddedMiniProgram({ + appId: this.data.top.appId, + path: this.data.top.linkPage + }); + }, + goLink: function (e) { + let index = e.currentTarget.id + wx.vibrateShort() + wx.openEmbeddedMiniProgram({ + appId: this.data.link[index].appId, + path: this.data.link[index].linkPage + }); + }, + } +}) diff --git a/miniprogram_npm/@jdmini/components/jdwx-link/index.json b/miniprogram_npm/@jdmini/components/jdwx-link/index.json new file mode 100644 index 0000000..fba482a --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-link/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/miniprogram_npm/@jdmini/components/jdwx-link/index.wxml b/miniprogram_npm/@jdmini/components/jdwx-link/index.wxml new file mode 100644 index 0000000..30ff009 --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-link/index.wxml @@ -0,0 +1,11 @@ + + + + {{top.linkName}} + {{top.appDsc}} + + + + {{item.linkName}} + + \ No newline at end of file diff --git a/miniprogram_npm/@jdmini/components/jdwx-link/index.wxss b/miniprogram_npm/@jdmini/components/jdwx-link/index.wxss new file mode 100644 index 0000000..111b9f1 --- /dev/null +++ b/miniprogram_npm/@jdmini/components/jdwx-link/index.wxss @@ -0,0 +1,63 @@ +/* 页面容器 */ +.jdwx-link-component { + /* background-image: linear-gradient(to right, #4F9863, #4F9863); */ + background-attachment: fixed; + background-size: cover; /* 确保背景图像覆盖整个元素 */ + /* height: 100vh; */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 20rpx; + box-sizing: border-box; + overflow: auto; /* 允许内容滚动 */ +} + +/* 列表项样式 */ +.jdwx-applink-list { + display: flex; + align-items: center; + width: 95%; + /* 假设我们想要每个view的高度约为屏幕高度的1/8 */ + /* 使用小程序的wx.getSystemInfo API动态计算并设置这个值会更准确 */ + height: calc((100vh - 40rpx) / 10); /* 减去容器padding的影响 */ + padding: 20rpx; + background-color: rgba(248, 250, 252, 1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 8rpx; + margin-bottom: 10rpx; +} +.jdwx-applink-top { + display: flex; + align-items: center; + width: 95%; + /* 假设我们想要每个view的高度约为屏幕高度的1/8 */ + /* 使用小程序的wx.getSystemInfo API动态计算并设置这个值会更准确 */ + height: calc((100vh - 40rpx) / 6); /* 减去容器padding的影响 */ + padding: 20rpx; + border-radius: 8rpx; + margin-bottom: 30rpx; + background-color: rgba(248, 250, 252, 1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} +.jdwx-applink-top-linkname{ + display: flex; + font-size: 36rpx; + color: rgb(39, 37, 37); + padding-bottom: 10rpx; +} +/* 图标样式 */ +.jdwx-applink-icon { + width: 120rpx; + height: 120rpx; + border-radius: 50%; + margin-right: 50rpx; + margin-left: 30rpx; +} + +/* 文本样式 */ +.jdwx-applink-text { + flex: 1; + font-size: 32rpx; + color: rgb(39, 37, 37); +} \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..e3fe0ce --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "template", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/@jdmini/api": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/@jdmini/api/-/api-1.0.10.tgz", + "integrity": "sha512-bVFU0awuY033mUT4QqArrYbrnPkBaBFKHoqCMHTVnRCk4b6gTs+cCGDH8uyf2t8ybCgWITKxaaH4Vjzyq8VF8g==" + }, + "node_modules/@jdmini/components": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@jdmini/components/-/components-1.0.6.tgz", + "integrity": "sha512-ndva1nlZ1QJqDVgHfB0GPxMGmXsZ7SbWjUkRm/WoQIkow75fFbaQCW/xhtQQ+bPbJLjXmCg2p2356klsLLib8A==", + "peerDependencies": { + "@jdmini/api": ">=1.0.8" + } + } + } +} diff --git a/node_modules/@jdmini/api/README.md b/node_modules/@jdmini/api/README.md new file mode 100644 index 0000000..a82f0ec --- /dev/null +++ b/node_modules/@jdmini/api/README.md @@ -0,0 +1,252 @@ +## 安装/更新 + +1、终端使用命令安装 npm 包 + +```bash +npm install --save @jdmini/api@latest +``` + +> ```bash +> npm install --save @jdmini/components@latest +> ``` + +2、在小程序开发者工具中:菜单选择工具 -> 构建 npm + +## 使用 + +```js +import { + onLoginReady, + waitLogin, + + injectApp, + injectPage, + injectComponent, + hijackApp, + hijackAllPage, + + gatewayHttpClient, + baseHttpClient, + apiHttpClient, + HttpClient, + + adManager, +} from '@jdmini/api' +``` + +### `waitLogin`/`onLoginReady` - 确保登录完成 + +- 同步的写法 + +```js +async function onLoad() { + await waitLogin() + // 此处代码将在已登录或登陆完成后执行。请求将自动携带Token + await gatewayHttpClient.request('/xxx', 'GET', {}) +} +``` + +- 异步的写法 + +```js +function onLoad() { + onLoginReady(() => { + // 此处代码将在已登录或登陆完成后执行。请求将自动携带Token + gatewayHttpClient.request('/xxx', 'GET', {}) + }) +} +``` + +### `injectApp` - 向App注入基础代码 + +- 注入之后实现自动登录、广告初始化等功能 + +```js +// app.js +App(injectApp()({ + // 业务代码 + onLaunch() { + + } +})) +``` + +### `injectPage` - 向Page注入基础代码 + +- 注入之后实现页面自动统计、自动展示插屏广告以及激励视频广告的调用支持 +- 参数: + - showInterstitialAd: 是否自动展示插屏广告 + +```js +// pages/xxx/xxx.js +Page(injectPage({ + showInterstitialAd: true +})({ + // 业务代码 + onLoad() { + + } +})) +``` + +### `injectComponent` - 向Component注入基础代码 + +- 适用于使用Component构造页面的场景 +- 注入之后实现页面自动统计、自动展示插屏广告以及激励视频广告的调用支持 +- 参数: + - showInterstitialAd: 是否自动展示插屏广告 + +```js +// pages/xxx/xxx.js +Component(injectComponent({ + showInterstitialAd: true +})({ + // 业务代码 + methods: { + onLoad() { + + } + } +})) +``` + +### `hijackApp` - 劫持全局App方法,注入基础代码 + +- 在不方便使用injectApp时使用(如解包后代码复杂,难以修改App调用) +- 此方法会修改全局App方法,存在未知风险,使用时请进行完整测试 +- 不可与injectApp同时使用 + +```js +// app.js +hijackApp() +``` + +### `hijackAllPage` - 劫持全局Page方法,注入基础代码 + +- 在不方便使用injectPage/injectComponent时使用(如解包后代码复杂,难以修改Page/Component调用) +- 此方法会修改全局Page方法,并影响所有的页面,存在未知风险,使用时请进行完整测试 +- 参数同injectPage/injectComponent方法,不可与这些方法同时使用 + +```js +// app.js +hijackAllPage({ + showInterstitialAd: true +}) +``` + +### `gatewayHttpClient` - 网关API调用封装 + +- 同步的写法 + +```js +async function onLoad() { + try { + // 网关请求。参数:路径、方法、数据、其他选项(如headers、responseType) + const data = await gatewayHttpClient.request(path, method, data,options) + + // 头像上传。参数:文件路径 + const data = await gatewayHttpClient.uploadAvatar(filePath) + + // 文件上传。参数:文件路径、数据 + const data = await gatewayHttpClient.uploadFile(filePath, data) + + // 文件删除。参数:文件ID + const data = await gatewayHttpClient.deleteFile(fileId) + } catch(err) { + // 响应HTTP状态码非200时自动showToast并抛出异常 + } +} +``` + +- 所有方法均支持异步的写法 + +```js +function onLoad() { + gatewayHttpClient.request('/xxx') + .then(data => { + console.log(data) + }) + .catch(err => {}) +} +``` + +### `baseHttpClient`/`apiHttpClient` - 为老版本兼容保留,不推荐使用 + +### `HttpClient` - API底层类,用于封装自定义请求 + +- 示例:封装一个百度的请求客户端,并调用百度搜索 + +```js +const baiduHttpClient = new HttpClient({ + baseURL: 'https://www.baidu.com', + timeout: 5000, +}); + +baiduHttpClient.request('/s', 'GET', { wd: '测试' }, { responseType: 'text' }) + .then(data => console.log(data)) +``` + +### `adManager` - 广告管理器 + +- 确保广告数据加载完成,支持同步/异步的写法 + +```js +// 同步的写法 +async function onLoad() { + await adManager.waitAdData() + // 此处代码将在广告数据加载完成后执行 + await adManager.createAndShowInterstitialAd() +} + +// 异步的写法 +function onLoad () { + adManager.onDataReady(() => { + // 此处代码将在广告数据加载完成后执行 + adManager.createAndShowInterstitialAd() + }) +} +``` + +- 广告数据 + +```js +// 格式化之后的广告数据对象,如{banner: "adunit-f7709f349de05edc", custom: "adunit-34c76b0c3e4a6ed0", ...} +const ads = adManager.ads + +// 友情链接顶部广告数据 +const top = adManager.top + +// 友情链接数据 +const link = adManager.link +``` + +- 创建并展示插屏广告 + +```js +function onLoad() { + adManager.createAndShowInterstitialAd() +} +``` + +- 创建并展示激励视频广告 + - 传入当前页面的上下文this,返回用户是否已看完广告 + - 由于微信的底层限制,需要先在调用的页面上进行injectPage注入,且该方法必须放在用户的点击事件里调用 + - 使用示例可参考[jdwx-demo](https://code.miniappapi.com/wx/jdwx-demo) + +```js +// 同步的写法 +page({ + async handleClick() { + const isEnded = await adManager.createAndShowRewardedVideoAd(this) + } +}) + +// 异步的写法 +page({ + handleClick() { + adManager.createAndShowRewardedVideoAd(this).then((isEnded) => { + + }) + } +}) +``` diff --git a/node_modules/@jdmini/api/miniprogram_dist/index.d.ts b/node_modules/@jdmini/api/miniprogram_dist/index.d.ts new file mode 100644 index 0000000..e06e58d --- /dev/null +++ b/node_modules/@jdmini/api/miniprogram_dist/index.d.ts @@ -0,0 +1,295 @@ +// Generated by dts-bundle v0.7.3 + +declare module '@jdmini/api' { + import { onLoginReady, waitLogin } from '@jdmini/api/app'; + import HttpClient, { gatewayHttpClient, baseHttpClient, apiHttpClient } from '@jdmini/api/httpClient'; + import { injectApp, injectPage, injectComponent, hijackApp, hijackAllPage } from '@jdmini/api/injector'; + import adManager from '@jdmini/api/adManager'; + export { onLoginReady, waitLogin, injectApp, injectPage, injectComponent, hijackApp, hijackAllPage, gatewayHttpClient, baseHttpClient, apiHttpClient, HttpClient, adManager, }; +} + +declare module '@jdmini/api/app' { + export interface AppOptions { + gatewayUrl?: string; + baseUrl?: string; + apiUrl?: string; + } + export interface PageOptions { + showInterstitialAd?: boolean; + } + export function initApp(options?: AppOptions): Promise; + export function showPage(options: PageOptions | undefined, pageId: string): Promise; + export const checkTokenValid: () => boolean; + /** + * 确保登录完成 + * @param {Function} callback - 回调函数 + * @returns {void} + */ + export function onLoginReady(callback: (...args: any[]) => void): void; + /** + * 等待登录完成 + * @returns {Promise} + */ + export function waitLogin(): Promise; + export function login(): Promise; + export function fetchEchoData(): Promise; + export function trackVisit(): Promise; +} + +declare module '@jdmini/api/httpClient' { + import { HttpClientOptions, RequestOptions, ApiResponse } from '@jdmini/api/types'; + class HttpClient { + constructor({ baseURL, timeout }: HttpClientOptions); + setBaseURL(baseURL: string): void; + /** + * 请求 + * @param {string} path 路径 + * @param {string} method 方法, 默认GET + * @param {Object} data 数据, 默认{} + * @param {Object} options 传入wx.request的其他配置, 默认{} + * @returns {Promise} 返回一个Promise对象 + */ + request(path: string, method?: WechatMiniprogram.RequestOption['method'], data?: Record, options?: RequestOptions): Promise>; + /** + * 上传文件 + * @param {string} filePath 文件路径 + * @param {Object} data 数据, 默认{} + * @param {'avatar' | 'file'} type 类型, 默认'file' + * @returns {Promise} 返回一个Promise对象 + */ + uploadFile(filePath: string, data?: Record, type?: 'avatar' | 'file'): Promise>; + /** + * 上传文件 + * @param {string} filePath 文件路径 + * @param {Object} data 数据, 默认{} + * @param {'avatar' | 'file'} type 类型, 默认'file' + * @returns {Promise} 返回一个Promise对象 + */ + upload(path: string, filePath: string, data?: Record): Promise>; + /** + * 删除文件 + * @param {number} fileId 文件id + * @returns {Promise} 返回一个Promise对象 + */ + deleteFile(fileId: number): Promise>; + /** + * 上传头像 + * @param {string} filePath 文件路径 + * @returns {Promise} 返回一个Promise对象 + */ + uploadAvatar(filePath: string): Promise>; + } + export const gatewayHttpClient: HttpClient; + export const baseHttpClient: HttpClient; + export const apiHttpClient: HttpClient; + export default HttpClient; +} + +declare module '@jdmini/api/injector' { + interface AppConfig { + onLaunch?: (...args: any[]) => void | Promise; + [key: string]: any; + } + interface PageConfig { + onShow?: (...args: any[]) => void | Promise; + [key: string]: any; + } + interface ComponentConfig { + methods?: { + onLoad?: (...args: any[]) => void | Promise; + onShow?: (...args: any[]) => void | Promise; + [key: string]: any; + }; + [key: string]: any; + } + interface InjectAppOptions { + gatewayUrl?: string; + baseUrl?: string; + apiUrl?: string; + } + interface InjectPageOptions { + showInterstitialAd?: boolean; + } + /** + * 注入应用配置 + * @param {Object} options - 配置选项 + * @param {string} [options.gatewayUrl] - 网关地址,默认使用CONFIG.API.GATEWAY_URL + * @param {string} [options.baseUrl] - 基础地址,默认使用CONFIG.API.BASE_URL + * @param {string} [options.apiUrl] - api地址,默认使用CONFIG.API.API_URL + * @returns {Function} 返回一个接收应用配置的函数 + */ + export function injectApp(options?: InjectAppOptions): (appConfig: AppConfig) => AppConfig; + /** + * 注入页面配置 + * @param {InjectPageOptions} options - 配置选项 + * @param {boolean} [options.showInterstitialAd] - 是否在onShow显示插屏广告,默认不显示 + * @returns {Function} 返回一个接收页面配置的函数 + */ + export function injectPage(options?: InjectPageOptions): (pageConfig?: PageConfig) => PageConfig; + /** + * 注入组件配置 + * @param {InjectPageOptions} options - 配置选项 + * @param {boolean} [options.showInterstitialAd] - 是否在onShow显示插屏广告,默认不显示 + * @returns {Function} 返回一个接收组件配置的函数 + */ + export function injectComponent(options?: InjectPageOptions): (pageConfig?: PageConfig) => ComponentConfig; + /** + * 劫持App + * @param {InjectAppOptions} options - 配置选项 + * @param {string} [options.gatewayUrl] - 网关地址,默认使用CONFIG.API.GATEWAY_URL + * @param {string} [options.baseUrl] - 基础地址,默认使用CONFIG.API.BASE_URL + * @param {string} [options.apiUrl] - api地址,默认使用CONFIG.API.API_URL + * @returns {void} + */ + export const hijackApp: (options?: InjectAppOptions) => void; + /** + * 劫持所有Page + * @param {InjectPageOptions} options - 配置选项 + * @param {boolean} [options.showInterstitialAd] - 是否在onShow显示插屏广告,默认不显示 + * @returns {void} + */ + export const hijackAllPage: (options?: InjectPageOptions) => void; + export {}; +} + +declare module '@jdmini/api/adManager' { + import { AdData, LinkData, TopData } from '@jdmini/api/types'; + type Ads = Partial>; + class AdManager { + /** + * 广告数据 + */ + ads: Ads; + /** + * 友情链接数据 + */ + link: LinkData[]; + /** + * 友情链接顶部广告数据 + */ + top: TopData | null; + constructor(); + /** + * 确保广告数据就绪 + * @param {Function} callback - 回调函数 + * @returns {void} + */ + onDataReady: (callback: (...args: any[]) => void) => void; + /** + * 等待广告数据加载完成 + * @returns {Promise} + */ + waitAdData: () => Promise; + /** + * 初始化广告数据 + * @returns {void} + */ + init: () => void; + /** + * 创建并展示插屏广告 + * @returns {Promise} + */ + createAndShowInterstitialAd: () => Promise; + /** + * 创建并展示激励视频广告 + * @param {any} context - 页面上下文 + * @param {string} [pageId] - 页面ID + * @returns {Promise} 是否完成播放 + */ + createAndShowRewardedVideoAd: (context: any, pageId?: string) => Promise; + } + const _default: AdManager; + export default _default; +} + +declare module '@jdmini/api/types' { + export interface Config { + API: { + GATEWAY_URL: string; + BASE_URL: string; + API_URL: string; + }; + APP: { + APP_ID: number; + LOGIN_MAX_RETRY: number; + }; + HTTP: { + TIMEOUT: number; + }; + DATA: { + PAGE_ID: string; + }; + STORAGE_KEYS: { + TOKEN: string; + USER_INFO: string; + SPA_DATA: string; + LINK_DATA: string; + TOP_DATA: string; + }; + EVENT_KEYS: { + LOGIN_SUCCESS: string; + AD_DATA_READY: string; + REWARDED_VIDEO_AD_CLOSE: string; + }; + } + export interface HttpClientOptions { + baseURL: string; + timeout?: number; + } + export interface RequestOptions { + headers?: Record; + [key: string]: any; + } + export interface LoginData { + appId: number; + code: string; + brand: string; + model: string; + platform: string; + } + export interface ApiResponse { + code: number; + message?: string; + data?: T; + } + export interface UserInfo { + token: string; + user: { + id: number; + name: string; + avatar: string; + openId: string; + }; + } + export interface EchoData { + isPublished: boolean; + spads: AdData[]; + links: LinkData[]; + top: TopData; + version: number; + } + export interface AdData { + id: number; + status: number; + appPage: 'banner' | 'custom' | 'video' | 'interstitial' | 'rewarded'; + ads: { + id: number; + type: number; + adId: number; + adUnitId: string; + }[]; + } + export interface LinkData { + appId: string; + appLogo: string; + linkName: string; + linkPage: string; + } + export interface TopData { + appId: string; + appLogo: string; + linkName: string; + appDsc: string; + } +} + diff --git a/node_modules/@jdmini/api/miniprogram_dist/index.js b/node_modules/@jdmini/api/miniprogram_dist/index.js new file mode 100644 index 0000000..fc11843 --- /dev/null +++ b/node_modules/@jdmini/api/miniprogram_dist/index.js @@ -0,0 +1,4 @@ +/*! + * @jdmini/api v1.0.10 + * + */(()=>{"use strict";var t={616:function(t,e,n){var r=this&&this.__awaiter||function(t,e,n,r){return new(n||(n=Promise))((function(o,a){function i(t){try{u(r.next(t))}catch(t){a(t)}}function s(t){try{u(r.throw(t))}catch(t){a(t)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(i,s)}u((r=r.apply(t,e||[])).next())}))},o=this&&this.__generator||function(t,e){var n,r,o,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=s(0),i.throw=s(1),i.return=s(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(s){return function(u){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,s[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0?e.ads[0].adUnitId:"",t}),{}));var n=wx.getStorageSync(i.default.STORAGE_KEYS.LINK_DATA);n&&(t.link=n);var r=wx.getStorageSync(i.default.STORAGE_KEYS.TOP_DATA);r&&r.appDsc.length>40&&(r.appDsc=r.appDsc.substring(0,40)+"...",t.top=r),t.adDataReady=!0,s.default.emit(i.default.EVENT_KEYS.AD_DATA_READY)},this.createAndShowInterstitialAd=function(){return r(t,void 0,void 0,(function(){var t;return o(this,(function(e){switch(e.label){case 0:return this.interstitialAd&&this.interstitialAd.destroy(),[4,this.waitAdData()];case 1:e.sent(),e.label=2;case 2:return e.trys.push([2,5,,6]),[4,this.createInterstitialAd()];case 3:return e.sent(),[4,this.showInterstitialAd()];case 4:return e.sent(),[3,6];case 5:return t=e.sent(),console.error("创建插屏广告失败:",t),[3,6];case 6:return[2]}}))}))},this.createInterstitialAd=function(){return r(t,void 0,void 0,(function(){var t=this;return o(this,(function(e){return[2,new Promise((function(e,n){t.ads.interstitial?(t.interstitialAd=wx.createInterstitialAd({adUnitId:t.ads.interstitial}),t.interstitialAd.onLoad((function(){console.log("插屏广告加载成功"),e()})),t.interstitialAd.onError((function(t){console.error(t),n(new Error("插屏广告加载失败"))})),t.interstitialAd.onClose((function(){console.log("插屏广告关闭")}))):n(new Error("插屏广告未配置"))}))]}))}))},this.showInterstitialAd=function(){return r(t,void 0,void 0,(function(){var t,e;return o(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),console.log("开始展示插屏广告"),[4,null===(e=this.interstitialAd)||void 0===e?void 0:e.show()];case 1:return[2,n.sent()];case 2:return t=n.sent(),console.error("插屏广告展示失败:",t),[3,3];case 3:return[2]}}))}))},this.createAndShowRewardedVideoAd=function(e,n){return r(t,void 0,void 0,(function(){var t,r,a;return o(this,(function(o){switch(o.label){case 0:return[4,this.waitAdData()];case 1:o.sent(),t=n||(null===(a=null==e?void 0:e.data)||void 0===a?void 0:a[i.default.DATA.PAGE_ID]),o.label=2;case 2:if(o.trys.push([2,6,,7]),!t)throw new Error("未指定pageId或者context");return this.rewardedVideoAds[t]?[3,4]:[4,this.createRewardedVideoAd(t)];case 3:o.sent(),o.label=4;case 4:return[4,this.showRewardedVideoAd(t)];case 5:return o.sent(),[3,7];case 6:return r=o.sent(),console.error("创建激励视频广告失败:",r),[3,7];case 7:return[2,new Promise((function(e){s.default.on(i.default.EVENT_KEYS.REWARDED_VIDEO_AD_CLOSE,(function(n,r){n===t&&e(r)}))}))]}}))}))},this.createRewardedVideoAd=function(e){return r(t,void 0,void 0,(function(){var t=this;return o(this,(function(n){return[2,new Promise((function(n,r){t.ads.rewarded?(t.rewardedVideoAds[e]=wx.createRewardedVideoAd({adUnitId:t.ads.rewarded}),t.rewardedVideoAds[e].onLoad((function(){console.log("激励视频广告加载成功"),n()})),t.rewardedVideoAds[e].onError((function(t){console.error(t),r(new Error("激励视频广告加载失败"))})),t.rewardedVideoAds[e].onClose((function(t){s.default.emit(i.default.EVENT_KEYS.REWARDED_VIDEO_AD_CLOSE,e,t.isEnded),console.log("激励视频广告关闭")}))):r(new Error("激励视频广告未配置"))}))]}))}))},this.showRewardedVideoAd=function(e){return r(t,void 0,void 0,(function(){var t,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),console.log("开始展示激励视频广告"),[4,null===(n=this.rewardedVideoAds[e])||void 0===n?void 0:n.show()];case 1:return[2,r.sent()];case 2:return t=r.sent(),console.error("激励视频广告展示失败:",t),[3,3];case 3:return[2]}}))}))}};e.default=new u},859:function(t,e,n){var r=this&&this.__awaiter||function(t,e,n,r){return new(n||(n=Promise))((function(o,a){function i(t){try{u(r.next(t))}catch(t){a(t)}}function s(t){try{u(r.throw(t))}catch(t){a(t)}}function u(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(i,s)}u((r=r.apply(t,e||[])).next())}))},o=this&&this.__generator||function(t,e){var n,r,o,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=s(0),i.throw=s(1),i.return=s(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(s){return function(u){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i&&(i=0,s[0]&&(a=0)),a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]s.default.APP.LOGIN_MAX_RETRY)throw wx.showToast({title:"网络异常,无法初始化",icon:"none",duration:2e3}),new Error("网络异常,无法初始化");o.label=1;case 1:return o.trys.push([1,7,,9]),t=wx.getDeviceInfo(),[4,wx.login()];case 2:return e=o.sent().code,n={appId:s.default.APP.APP_ID,code:e,brand:t.brand,model:t.model,platform:t.platform},[4,i.gatewayHttpClient.request("/wx/v1/api/login","POST",n)];case 3:return 200===(r=o.sent()).code&&r.data?(wx.setStorageSync(s.default.STORAGE_KEYS.TOKEN,r.data.token),wx.setStorageSync(s.default.STORAGE_KEYS.USER_INFO,r.data.user),l=0,u.default.emit(s.default.EVENT_KEYS.LOGIN_SUCCESS),[3,6]):[3,4];case 4:return l++,[4,h()];case 5:o.sent(),o.label=6;case 6:return[3,9];case 7:return o.sent(),l++,[4,h()];case 8:return o.sent(),[3,9];case 9:return[2]}}))}))}function p(){return r(this,void 0,void 0,(function(){var t;return o(this,(function(n){switch(n.label){case 0:return wx.removeStorageSync(s.default.STORAGE_KEYS.SPA_DATA),wx.removeStorageSync(s.default.STORAGE_KEYS.LINK_DATA),wx.removeStorageSync(s.default.STORAGE_KEYS.TOP_DATA),(0,e.checkTokenValid)()?[4,i.gatewayHttpClient.request("/wx/v1/api/echo","GET")]:[2];case 1:return 200===(t=n.sent()).code&&t.data?(t.data.spads&&wx.setStorageSync(s.default.STORAGE_KEYS.SPA_DATA,t.data.spads),t.data.links&&wx.setStorageSync(s.default.STORAGE_KEYS.LINK_DATA,t.data.links),t.data.top&&wx.setStorageSync(s.default.STORAGE_KEYS.TOP_DATA,t.data.top),[3,5]):[3,2];case 2:return 401!==t.code?[3,5]:[4,h()];case 3:return n.sent(),[4,p()];case 4:n.sent(),n.label=5;case 5:return[2]}}))}))}function w(){return r(this,void 0,void 0,(function(){var t;return o(this,(function(e){switch(e.label){case 0:return e.trys.push([0,5,,6]),[4,i.gatewayHttpClient.request("/wx/v1/api/visit","POST")];case 1:return 401!==e.sent().code?[3,4]:[4,h()];case 2:return e.sent(),[4,w()];case 3:e.sent(),e.label=4;case 4:return[3,6];case 5:return t=e.sent(),console.error("访问统计失败:",t),[3,6];case 6:return[2]}}))}))}e.checkTokenValid=function(){var t=wx.getStorageSync(s.default.STORAGE_KEYS.TOKEN);return!(!t||t.length<32)}},28:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0});var n={API:{GATEWAY_URL:"https://ca.miniappapi.com",BASE_URL:"https://app.jd027.com/v1/api",API_URL:"https://cp.miniappapi.com"},APP:{APP_ID:wx.getExtConfigSync().appId||313,LOGIN_MAX_RETRY:2},HTTP:{TIMEOUT:5e3},DATA:{PAGE_ID:"jdwx-page-id"},STORAGE_KEYS:{TOKEN:"jdwx-token",USER_INFO:"jdwx-userinfo",SPA_DATA:"jdwx-spadata",LINK_DATA:"jdwx-linkdata",TOP_DATA:"jdwx-topdata"},EVENT_KEYS:{LOGIN_SUCCESS:"jdwx-login-success",AD_DATA_READY:"jdwx-ad-data-ready",REWARDED_VIDEO_AD_CLOSE:"jdwx-rewarded-video-ad-close"}};e.default=n},144:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(){this.events={}}return t.prototype.on=function(t,e){if("string"!=typeof t)throw new TypeError("eventName must be a string");if("function"!=typeof e)throw new TypeError("callback must be a function");return this.events[t]||(this.events[t]=[]),this.events[t].push(e),this},t.prototype.emit=function(t){for(var e=[],n=1;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=200&&d.statusCode<300)return[2,d.data];if(401===d.statusCode)return[2,{code:401,message:"未授权"}];throw new Error(d.data.message||"请求失败");case 4:throw f=a.sent(),console.error("网络错误:",f),wx.showToast({title:f instanceof Error?f.message:"网络错误",icon:"none",duration:2e3}),f;case 5:return[2]}}))}))},t.prototype.uploadFile=function(t){return o(this,arguments,void 0,(function(t,e,n){var r,o,i,u;return void 0===e&&(e={}),void 0===n&&(n="file"),a(this,(function(a){r=this.baseURL===s.default.API.GATEWAY_URL,o=wx.getStorageSync(s.default.STORAGE_KEYS.TOKEN),i="avatar"===n?"/avatar":"/file/new",u=this.joinURL(this.baseURL,"".concat(r?"/wx/v1/api":"").concat(i));try{return[2,new Promise((function(n,r){wx.uploadFile({url:u,name:"file",filePath:t,formData:e,header:{"Content-Type":"application/x-www-form-urlencoded",Authorization:o},success:function(t){if(t.statusCode>=200&&t.statusCode<300)n(JSON.parse(t.data));else{if(401!==t.statusCode)throw new Error(t.data.message||"上传失败");n(JSON.parse(t.data))}},fail:function(){throw new Error("网络错误")}}).onProgressUpdate((function(t){console.log("上传进度",t.progress),console.log("已经上传的数据长度",t.totalBytesSent),console.log("预期需要上传的数据总长度",t.totalBytesExpectedToSend)}))}))]}catch(t){throw console.error("上传失败:",t),wx.showToast({title:t instanceof Error?t.message:"上传失败",icon:"none",duration:2e3}),t}return[2]}))}))},t.prototype.upload=function(t,e){return o(this,arguments,void 0,(function(t,e,n){var r,o,i;return void 0===n&&(n={}),a(this,(function(a){r=this.baseURL===s.default.API.GATEWAY_URL,o=wx.getStorageSync(s.default.STORAGE_KEYS.TOKEN),i=this.joinURL(this.baseURL,"".concat(r?"/wx/v1/api":"").concat(t));try{return[2,new Promise((function(t,r){wx.uploadFile({url:i,name:"file",filePath:e,formData:n,header:{"Content-Type":"application/x-www-form-urlencoded",Authorization:o},success:function(e){if(e.statusCode>=200&&e.statusCode<300)t(JSON.parse(e.data));else{if(401!==e.statusCode)throw new Error(e.data.message||"上传失败");t(JSON.parse(e.data))}},fail:function(){throw new Error("网络错误")}}).onProgressUpdate((function(t){console.log("上传进度",t.progress),console.log("已经上传的数据长度",t.totalBytesSent),console.log("预期需要上传的数据总长度",t.totalBytesExpectedToSend)}))}))]}catch(t){throw console.error("上传失败:",t),wx.showToast({title:t instanceof Error?t.message:"上传失败",icon:"none",duration:2e3}),t}return[2]}))}))},t.prototype.deleteFile=function(t){return o(this,void 0,void 0,(function(){var e;return a(this,(function(n){return e=this.baseURL===s.default.API.GATEWAY_URL,[2,this.request("".concat(e?"/wx/v1/api":"","/file/del"),"GET",{id:t})]}))}))},t.prototype.uploadAvatar=function(t){return o(this,void 0,void 0,(function(){return a(this,(function(e){return[2,this.uploadFile(t,{},"avatar")]}))}))},t}();e.gatewayHttpClient=new u({baseURL:s.default.API.GATEWAY_URL,timeout:s.default.HTTP.TIMEOUT}),e.baseHttpClient=new u({baseURL:s.default.API.BASE_URL,timeout:s.default.HTTP.TIMEOUT}),e.apiHttpClient=new u({baseURL:s.default.API.API_URL,timeout:s.default.HTTP.TIMEOUT}),e.default=u},156:function(t,e,n){var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var o=Object.getOwnPropertyDescriptor(e,n);o&&!("get"in o?!e.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,o)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),o=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),a=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&r(e,t,n);return o(e,t),e},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.adManager=e.HttpClient=e.apiHttpClient=e.baseHttpClient=e.gatewayHttpClient=e.hijackAllPage=e.hijackApp=e.injectComponent=e.injectPage=e.injectApp=e.waitLogin=e.onLoginReady=void 0;var s=n(859);Object.defineProperty(e,"onLoginReady",{enumerable:!0,get:function(){return s.onLoginReady}}),Object.defineProperty(e,"waitLogin",{enumerable:!0,get:function(){return s.waitLogin}});var u=a(n(161));e.HttpClient=u.default,Object.defineProperty(e,"gatewayHttpClient",{enumerable:!0,get:function(){return u.gatewayHttpClient}}),Object.defineProperty(e,"baseHttpClient",{enumerable:!0,get:function(){return u.baseHttpClient}}),Object.defineProperty(e,"apiHttpClient",{enumerable:!0,get:function(){return u.apiHttpClient}});var c=n(718);Object.defineProperty(e,"injectApp",{enumerable:!0,get:function(){return c.injectApp}}),Object.defineProperty(e,"injectPage",{enumerable:!0,get:function(){return c.injectPage}}),Object.defineProperty(e,"injectComponent",{enumerable:!0,get:function(){return c.injectComponent}}),Object.defineProperty(e,"hijackApp",{enumerable:!0,get:function(){return c.hijackApp}}),Object.defineProperty(e,"hijackAllPage",{enumerable:!0,get:function(){return c.hijackAllPage}});var l=i(n(616));e.adManager=l.default},718:function(t,e,n){var r=this&&this.__assign||function(){return r=Object.assign||function(t){for(var e,n=1,r=arguments.length;n 构建 npm + +`注意:依赖@jdmini/api,请确保小程序项目已安装@jdmini/api` + +## 使用 + +1、在页面的 json 文件中引入组件: + +```json +{ + "usingComponents": { + "jdwx-ad": "@jdmini/components/jdwx-ad", + "jdwx-link": "@jdmini/components/jdwx-link" + } +} +``` + +2、在页面的 wxml 文件中使用组件: + +```html + + +``` diff --git a/node_modules/@jdmini/components/miniprogram_dist/icons/home-active.png b/node_modules/@jdmini/components/miniprogram_dist/icons/home-active.png new file mode 100644 index 0000000000000000000000000000000000000000..127b0edbf4fac4c615b2ecef60217b0c3c361cb3 GIT binary patch literal 1575 zcmbW1dpOez9L9eeTf-r?oov*>tYL|T)GRIfA+a?~DfhW-E^|pqGM7?rA;~3FgrXyt zB9zu>bam-iB$B2?OfD%karB($Ip_cLJfHXbKJWYg_f28YZ4~9zga`M&@4ARg(+bb1(t`477g<5>-x(1OV88Eoi}vDv}hr2Rr#;9<6k{o*CVZ(+Us# z&7+tQYiP7ns7?yHS7*#ZH>$7>@@w@&BYg!X7+bgjpXNCu2KtJXo5(fH)41> z&7wvzvVkY|+nh~jO(yXg{bIX=;o}RITkMwxa0dh4oTAr2x^oKC65flQ#oE$C%Fx%=He1A*c=hr?eOUL#AEhZT%N8lXd@9`N>%JEvlb#PTG42uC z@mhJJ`8~51aVFnW8I>JTk!FSUh6gbtTvfGckGQhZfG-_|hEuOUNJrn-lakxQzRzqN zEG`HLKcvn3=C6BO9ZYk#G&COl&>F%%RuaE!U&{uN0vU2JLA}#0KA@C%VChhF`-&q9 z>B^cdFPKnh>gH3w*GKcIx0VuJ>2dnYz!sj|$d8*0h}@HM7DZ< zom_lPRBTMw!qG{%*{K7xUg|i9XDej_jIDw2Cn z=lX45DekndIx{>ahvQjxF7Omlr>;$shDLH3R`}=v{vHRqI^Lj8ZM8yiPl2vQ!P3Kk z0TherS~QvuvehIF!vj41Rb(C-ltgS5H7;{l4FMsgp{&}*6>RMvP)1e1Hz~Ba%XpiT zO>X+dbt7Btw58YV%Qt;K$uS?wNMc5qiGHNJ(o3aXUiZ8FlJd~r+#7%LY8?2)^FQOa z)eDhHCO&K855g?ZzE5x6;evuki~EtNA=Q}v>U$QPuy zLzNefra8UqbMhOjl&=h%)D!h@hLc?Am*?0oQLr6im7;LWv~2#T;pu$PE_^DK4XFbEV;K$#8+LHdIXAB^KVWM`2dgc;PW}x9 CY1HNb literal 0 HcmV?d00001 diff --git a/node_modules/@jdmini/components/miniprogram_dist/icons/home.png b/node_modules/@jdmini/components/miniprogram_dist/icons/home.png new file mode 100644 index 0000000000000000000000000000000000000000..11016daa08d27f848fe96b9b380b9c335fb72a3e GIT binary patch literal 1070 zcmeAS@N?(olHy`uVBq!ia0vp^fgsGm1|(PYdzk?#&H|6fVg?31We{epSZZI!z`*># z)5S5QV$R#yXS@9ZB@Wb=+a+}bWvOU#KT-Bwlwz9JXdz3F1k+6@b*u>Oqr!9FE& z2JddkrI88Fy;ocWjk&#?vW%oAJ&c-ToqFufcf0TRp5G~sx7}~Gx77Oi&htON-m9Mf zeeZLZLYd?#i&aaQ{xhouGEQF6;2Cm2B}?FVVH)=lu?J=b^J?-qC$^mM_f?f+UDo(x z{Wn>Qz*42N8ij%db9a7vww-ao;#DoPd>=(6OgYAsemmpRee1I-8>N3V_&mt@&GY8- z+z4Zjo`%f_cz&{++cq<$=85*fUEVQ#cNWgr^D~zt;-T_MEAKZe#BoDgc1rfQ%hu{e(Hs_&vDHJy$3J@q~Q`GCG7&sL)qenQt7 z`cKaLBT}HE942p|+3{5*BHZlbE{`{EVf*&5m~khsW1q*FG)=GB$-Ae)_{6E{(FZQg z`DooB-E?DrqqoSP&8kaWGJk#&R^P?*_W=LFom^8L7pjyg>c^Ko=bd+m??LZ_Ir)uq zE1B9SL@cSfc*t8aWa*JjPrJolr3QownVuHcy>e=SmP*;;_7ndC{14crx>Mt&M`4?oFk3NKl6#C^y8Q)kbZ(h-qQgF5d%% zCGk7_nam68%R;<3I;$IYA4uZg=+DGnpcbf^A-^^6{>v$?h#r(zpvld(r*4w#a@{#NV%`$&?uk3W$JxA1_6KWA65osj| z5fl#U+#l4pJxf(+#+J$@XRG;wI_C%V<%{;Mx6WLxX6MD`t?IW#<~{Q_uH-9I|1Q~I zB|DvE!oG4jwsX1#dqlrqs%5BXiq?sn5|`lI_pkTYH|}HGADI1>QT}_{M8{Lb>*j9r z?ZG0h2P7E9xLH$pZpd-7cWb*JRN{3U;@HKwk}D~EC2 zm4r!lw?+T$a;o*1^Hkv7Nztl9v)-B(6#R2hty{uBt(9;4j~CK@kB9p9lQ> YvrilkX{>z)EC3ihUHx3vIVCg!0C%_Ik^lez literal 0 HcmV?d00001 diff --git a/node_modules/@jdmini/components/miniprogram_dist/icons/link-active.png b/node_modules/@jdmini/components/miniprogram_dist/icons/link-active.png new file mode 100644 index 0000000000000000000000000000000000000000..433ec7bf24d3f2a0367fcaf00069d9c7ca3105ed GIT binary patch literal 2796 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 { + this.setData({ ads: adManager.ads }) + }) + }, + }, + methods: { + } +}) diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.json b/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.json new file mode 100644 index 0000000..32640e0 --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxml b/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxml new file mode 100644 index 0000000..1b24adf --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxml @@ -0,0 +1,5 @@ + + + + + diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxss b/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxss new file mode 100644 index 0000000..de627db --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-ad/index.wxss @@ -0,0 +1,7 @@ +.jdwx-ad-component { + padding: 10rpx; +} + +.jdwx-ad-item { + bottom: 10rpx; +} \ No newline at end of file diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.js b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.js new file mode 100644 index 0000000..887d749 --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.js @@ -0,0 +1,37 @@ +import { adManager } from '@jdmini/api' + +Component({ + properties: { + }, + data: { + link: [], + top: {}, + }, + pageLifetimes: { + show: function () { + adManager.onDataReady(() => { + this.setData({ + link: adManager.link, + top: adManager.top + }) + }) + }, + }, + methods: { + gotopLink: function () { + wx.vibrateShort() + wx.openEmbeddedMiniProgram({ + appId: this.data.top.appId, + path: this.data.top.linkPage + }); + }, + goLink: function (e) { + let index = e.currentTarget.id + wx.vibrateShort() + wx.openEmbeddedMiniProgram({ + appId: this.data.link[index].appId, + path: this.data.link[index].linkPage + }); + }, + } +}) diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.json b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.json new file mode 100644 index 0000000..fba482a --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxml b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxml new file mode 100644 index 0000000..30ff009 --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxml @@ -0,0 +1,11 @@ + + + + {{top.linkName}} + {{top.appDsc}} + + + + {{item.linkName}} + + \ No newline at end of file diff --git a/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxss b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxss new file mode 100644 index 0000000..111b9f1 --- /dev/null +++ b/node_modules/@jdmini/components/miniprogram_dist/jdwx-link/index.wxss @@ -0,0 +1,63 @@ +/* 页面容器 */ +.jdwx-link-component { + /* background-image: linear-gradient(to right, #4F9863, #4F9863); */ + background-attachment: fixed; + background-size: cover; /* 确保背景图像覆盖整个元素 */ + /* height: 100vh; */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 20rpx; + box-sizing: border-box; + overflow: auto; /* 允许内容滚动 */ +} + +/* 列表项样式 */ +.jdwx-applink-list { + display: flex; + align-items: center; + width: 95%; + /* 假设我们想要每个view的高度约为屏幕高度的1/8 */ + /* 使用小程序的wx.getSystemInfo API动态计算并设置这个值会更准确 */ + height: calc((100vh - 40rpx) / 10); /* 减去容器padding的影响 */ + padding: 20rpx; + background-color: rgba(248, 250, 252, 1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + border-radius: 8rpx; + margin-bottom: 10rpx; +} +.jdwx-applink-top { + display: flex; + align-items: center; + width: 95%; + /* 假设我们想要每个view的高度约为屏幕高度的1/8 */ + /* 使用小程序的wx.getSystemInfo API动态计算并设置这个值会更准确 */ + height: calc((100vh - 40rpx) / 6); /* 减去容器padding的影响 */ + padding: 20rpx; + border-radius: 8rpx; + margin-bottom: 30rpx; + background-color: rgba(248, 250, 252, 1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} +.jdwx-applink-top-linkname{ + display: flex; + font-size: 36rpx; + color: rgb(39, 37, 37); + padding-bottom: 10rpx; +} +/* 图标样式 */ +.jdwx-applink-icon { + width: 120rpx; + height: 120rpx; + border-radius: 50%; + margin-right: 50rpx; + margin-left: 30rpx; +} + +/* 文本样式 */ +.jdwx-applink-text { + flex: 1; + font-size: 32rpx; + color: rgb(39, 37, 37); +} \ No newline at end of file diff --git a/node_modules/@jdmini/components/package.json b/node_modules/@jdmini/components/package.json new file mode 100644 index 0000000..7d6ed6f --- /dev/null +++ b/node_modules/@jdmini/components/package.json @@ -0,0 +1,20 @@ +{ + "name": "@jdmini/components", + "version": "1.0.6", + "description": "", + "files": [ + "miniprogram_dist", + "resources" + ], + "scripts": { + "pub": "npm publish --access public" + }, + "miniprogram": "miniprogram_dist", + "author": "", + "peerDependencies": { + "@jdmini/api": ">=1.0.8" + }, + "devDependencies": { + "@types/wechat-miniprogram": "^3.4.8" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5e075aa --- /dev/null +++ b/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "template", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@jdmini/api": "^1.0.10", + "@jdmini/components": "^1.0.6" + } + }, + "node_modules/@jdmini/api": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/@jdmini/api/-/api-1.0.10.tgz", + "integrity": "sha512-bVFU0awuY033mUT4QqArrYbrnPkBaBFKHoqCMHTVnRCk4b6gTs+cCGDH8uyf2t8ybCgWITKxaaH4Vjzyq8VF8g==" + }, + "node_modules/@jdmini/components": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@jdmini/components/-/components-1.0.6.tgz", + "integrity": "sha512-ndva1nlZ1QJqDVgHfB0GPxMGmXsZ7SbWjUkRm/WoQIkow75fFbaQCW/xhtQQ+bPbJLjXmCg2p2356klsLLib8A==", + "peerDependencies": { + "@jdmini/api": ">=1.0.8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3a13fe8 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@jdmini/api": "^1.0.10", + "@jdmini/components": "^1.0.6" + } +} diff --git a/pages/category-detail/category-detail.js b/pages/category-detail/category-detail.js new file mode 100644 index 0000000..0232b2e --- /dev/null +++ b/pages/category-detail/category-detail.js @@ -0,0 +1,90 @@ +import { injectPage } from '@jdmini/api' +const { getWorksheets } = require('../../utils/api.js') +const { DATA_BASE_URL } = require('../../utils/config.js') + +Page(injectPage({})({ + data: { + dataBaseUrl: DATA_BASE_URL, + categoryId: null, + categoryName: '', + worksheets: [], + page: 1, + pageSize: 20, + hasMore: true, + loading: false + }, + + onLoad(options) { + if (options.id) { + this.setData({ categoryId: Number(options.id) }) + } + if (options.name) { + const name = decodeURIComponent(options.name) + this.setData({ categoryName: name }) + wx.setNavigationBarTitle({ title: name }) + } + this.loadWorksheets() + }, + + onPullDownRefresh() { + this.setData({ + page: 1, + hasMore: true, + worksheets: [] + }) + this.loadWorksheets().finally(() => { + wx.stopPullDownRefresh() + }) + }, + + onReachBottom() { + if (this.data.hasMore && !this.data.loading) { + this.loadMore() + } + }, + + // 加载练习表列表 + async loadWorksheets() { + if (this.data.loading) return + + try { + this.setData({ loading: true }) + + const res = await getWorksheets({ + category_id: this.data.categoryId, + page: this.data.page, + pageSize: this.data.pageSize + }) + + if (res.success) { + const newWorksheets = res.data.list || [] + this.setData({ + worksheets: this.data.page === 1 ? newWorksheets : [...this.data.worksheets, ...newWorksheets], + hasMore: newWorksheets.length >= this.data.pageSize, + loading: false + }) + } + } catch (error) { + console.error('加载练习表失败:', error) + this.setData({ loading: false }) + } + }, + + // 加载更多 + async loadMore() { + if (this.data.loading || !this.data.hasMore) return + + this.setData({ + page: this.data.page + 1 + }) + await this.loadWorksheets() + }, + + // 跳转详情页 + goDetail(e) { + const id = e.currentTarget.dataset.id + wx.navigateTo({ + url: `/pages/detail/detail?id=${id}` + }) + } +})) diff --git a/pages/category-detail/category-detail.json b/pages/category-detail/category-detail.json new file mode 100644 index 0000000..287657f --- /dev/null +++ b/pages/category-detail/category-detail.json @@ -0,0 +1,5 @@ +{ + "usingComponents": {}, + "enablePullDownRefresh": true, + "backgroundTextStyle": "dark" +} diff --git a/pages/category-detail/category-detail.wxml b/pages/category-detail/category-detail.wxml new file mode 100644 index 0000000..693ba9c --- /dev/null +++ b/pages/category-detail/category-detail.wxml @@ -0,0 +1,30 @@ + + + + + + + + + + {{item.title}} + {{item.e_title}} + + + + + + + 加载中... + 已加载全部 + + + + + 暂无练习表 + + diff --git a/pages/category-detail/category-detail.wxss b/pages/category-detail/category-detail.wxss new file mode 100644 index 0000000..b5ae588 --- /dev/null +++ b/pages/category-detail/category-detail.wxss @@ -0,0 +1,77 @@ +/* 分类详情页样式 */ +.container { + padding: 20rpx; + min-height: 100vh; + background: #f5f5f5; +} + +/* 练习表网格 */ +.worksheet-grid { + display: flex; + flex-wrap: wrap; + gap: 20rpx; +} + +.worksheet-item { + width: calc(50% - 20rpx); + background: #fff; + border-radius: 16rpx; + overflow: hidden; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); +} + +.worksheet-cover { + position: relative; + width: 100%; + padding-top: 75%; + background: #f5f5f5; +} + +.cover-img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.worksheet-info { + padding: 16rpx; +} + +.worksheet-title { + display: block; + font-size: 26rpx; + color: #333; + line-height: 1.4; + height: 72rpx; +} + +.worksheet-e-title { + display: block; + font-size: 22rpx; + color: #999; + margin-top: 8rpx; +} + +/* 加载状态 */ +.load-status { + text-align: center; + padding: 30rpx 0; + font-size: 26rpx; + color: #999; +} + +/* 空状态 */ +.empty-tip { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 100rpx 0; +} + +.empty-tip text { + font-size: 28rpx; + color: #999; +} diff --git a/pages/category/category.js b/pages/category/category.js new file mode 100644 index 0000000..3ca3cb3 --- /dev/null +++ b/pages/category/category.js @@ -0,0 +1,131 @@ +import { injectPage } from '@jdmini/api' +const { getCategories, getWorksheets } = require('../../utils/api.js') +const { DATA_BASE_URL } = require('../../utils/config.js') + +Page(injectPage({})({ + data: { + dataBaseUrl: DATA_BASE_URL, + categories: [], + currentCategory: 1, + currentCategoryName: '', + worksheets: [], + page: 1, + pageSize: 20, + total: 0, + hasMore: true, + loading: false + }, + + onLoad(options) { + // 如果传入了分类ID + if (options.id) { + this.setData({ + currentCategory: Number(options.id) + }) + } + this.loadCategories() + }, + + onPullDownRefresh() { + this.setData({ + page: 1, + hasMore: true, + worksheets: [] + }) + this.loadWorksheets().finally(() => { + wx.stopPullDownRefresh() + }) + }, + + // 加载分类列表 + async loadCategories() { + try { + const res = await getCategories() + if (res.success && res.data.length > 0) { + const categories = res.data + // 如果没有设置当前分类,默认选中第一个 + const currentCategory = this.data.currentCategory || categories[0].id + const currentCategoryObj = categories.find(c => c.id === currentCategory) || categories[0] + + this.setData({ + categories, + currentCategory: currentCategoryObj.id, + currentCategoryName: currentCategoryObj.name + }) + + // 加载该分类下的练习表 + await this.loadWorksheets() + } + } catch (error) { + console.error('加载分类失败:', error) + wx.showToast({ + title: '加载失败', + icon: 'none' + }) + } + }, + + // 选择分类 + async selectCategory(e) { + const categoryId = Number(e.currentTarget.dataset.id) + if (categoryId === this.data.currentCategory) return + + const category = this.data.categories.find(c => c.id === categoryId) + + this.setData({ + currentCategory: categoryId, + currentCategoryName: category?.name || '', + page: 1, + hasMore: true, + worksheets: [] + }) + + await this.loadWorksheets() + }, + + // 加载练习表列表 + async loadWorksheets() { + if (this.data.loading) return + + try { + this.setData({ loading: true }) + + const res = await getWorksheets({ + category_id: this.data.currentCategory, + page: this.data.page, + pageSize: this.data.pageSize + }) + + if (res.success) { + const newWorksheets = res.data.list || [] + this.setData({ + worksheets: this.data.page === 1 ? newWorksheets : [...this.data.worksheets, ...newWorksheets], + total: res.data.pagination?.total || 0, + hasMore: newWorksheets.length >= this.data.pageSize, + loading: false + }) + } + } catch (error) { + console.error('加载练习表失败:', error) + this.setData({ loading: false }) + } + }, + + // 加载更多 + async loadMore() { + if (this.data.loading || !this.data.hasMore) return + + this.setData({ + page: this.data.page + 1 + }) + await this.loadWorksheets() + }, + + // 跳转详情页 + goDetail(e) { + const id = e.currentTarget.dataset.id + wx.navigateTo({ + url: `/pages/detail/detail?id=${id}` + }) + } +})) diff --git a/pages/category/category.json b/pages/category/category.json new file mode 100644 index 0000000..7f8f211 --- /dev/null +++ b/pages/category/category.json @@ -0,0 +1,6 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "分类", + "enablePullDownRefresh": true, + "backgroundTextStyle": "dark" +} diff --git a/pages/category/category.wxml b/pages/category/category.wxml new file mode 100644 index 0000000..3e9dd9d --- /dev/null +++ b/pages/category/category.wxml @@ -0,0 +1,56 @@ + + + + + + + {{item.name}} + {{item.e_name}} + + + + + + + + {{currentCategoryName}} + 共{{total}}个练习表 + + + + + + + + + + {{item.title}} + + + + + + + 加载中... + 已加载全部 + + + + + + 暂无练习表 + + + diff --git a/pages/category/category.wxss b/pages/category/category.wxss new file mode 100644 index 0000000..db65939 --- /dev/null +++ b/pages/category/category.wxss @@ -0,0 +1,155 @@ +/* 分类页面样式 */ +.container { + display: flex; + height: 100vh; + background: #f5f5f5; +} + +/* 左侧分类列表 */ +.category-sidebar { + width: 200rpx; + height: 100%; + background: #fff; + flex-shrink: 0; +} + +.category-item { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 24rpx 16rpx; + background: #f8f8f8; + border-bottom: 1rpx solid #eee; +} + +.category-item.active { + background: #fff; +} + +.category-indicator { + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 6rpx; + height: 50rpx; + background: linear-gradient(180deg, #4CAF50, #8BC34A); + border-radius: 0 6rpx 6rpx 0; +} + +.category-name { + font-size: 26rpx; + color: #333; + text-align: center; + margin-bottom: 6rpx; +} + +.category-item.active .category-name { + color: #4CAF50; + font-weight: bold; +} + +.category-e-name { + font-size: 20rpx; + color: #999; + text-align: center; +} + +/* 右侧内容区 */ +.content-area { + flex: 1; + height: 100%; + padding: 20rpx; +} + +.content-header { + display: flex; + align-items: baseline; + margin-bottom: 20rpx; +} + +.content-title { + font-size: 32rpx; + font-weight: bold; + color: #333; + margin-right: 16rpx; +} + +.content-count { + font-size: 24rpx; + color: #999; +} + +/* 练习表网格 */ +.worksheet-grid { + display: flex; + flex-wrap: wrap; + margin: 0 -8rpx; +} + +.worksheet-item { + width: calc(50% - 16rpx); + margin: 0 8rpx 16rpx; + background: #fff; + border-radius: 12rpx; + overflow: hidden; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); +} + +.worksheet-cover { + position: relative; + width: 100%; + padding-top: 75%; + background: #f5f5f5; +} + +.cover-img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.worksheet-info { + padding: 12rpx; +} + +.worksheet-title { + display: block; + font-size: 24rpx; + color: #333; + line-height: 1.4; + height: 67rpx; +} + +/* 加载状态 */ +.load-status { + text-align: center; + padding: 20rpx 0; + font-size: 24rpx; + color: #999; +} + +/* 空状态 */ +.empty-tip { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 80rpx 0; +} + +.empty-img { + width: 160rpx; + height: 160rpx; + margin-bottom: 16rpx; + opacity: 0.5; +} + +.empty-tip text { + font-size: 26rpx; + color: #999; +} diff --git a/pages/detail/detail.js b/pages/detail/detail.js new file mode 100644 index 0000000..fac9369 --- /dev/null +++ b/pages/detail/detail.js @@ -0,0 +1,388 @@ +import { injectPage } from '@jdmini/api' +const { getWorksheetDetail, checkPoints, deductPoints } = require('../../utils/api.js') +const { DATA_BASE_URL } = require('../../utils/config.js') + +Page(injectPage({})({ + data: { + dataBaseUrl: DATA_BASE_URL, + id: null, + detail: {}, + recommended: [], + loading: false, + downloadedPath: '' + }, + + onLoad(options) { + if (options.id) { + this.setData({ id: Number(options.id) }) + this.loadDetail() + } + }, + + onShareAppMessage() { + const userId = wx.getStorageSync('userId') + let path = `/pages/detail/detail?id=${this.data.id}` + // 携带邀请人ID + if (userId) { + path += `&inviter=${userId}` + } + return { + title: this.data.detail.title || '儿童练习表', + path: path, + imageUrl: DATA_BASE_URL + this.data.detail.coverurl + } + }, + + // 加载详情 + async loadDetail() { + try { + this.setData({ loading: true }) + + const res = await getWorksheetDetail(this.data.id) + if (res.success) { + // 设置导航栏标题 + wx.setNavigationBarTitle({ + title: res.data.detail.title || '练习表详情' + }) + + this.setData({ + detail: res.data.detail || {}, + recommended: res.data.recommended || [], + loading: false + }) + } + } catch (error) { + console.error('加载详情失败:', error) + this.setData({ loading: false }) + wx.showToast({ + title: '加载失败', + icon: 'none' + }) + } + }, + + // 预览封面大图 + previewImage() { + const url = DATA_BASE_URL + this.data.detail.coverurl + wx.previewImage({ + current: url, + urls: [url] + }) + }, + + // 预览PDF + previewPdf() { + const pdfUrl = DATA_BASE_URL + this.data.detail.pdfurl + + wx.showLoading({ + title: '加载中...', + mask: true + }) + + // 先下载PDF文件 + wx.downloadFile({ + url: pdfUrl, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200) { + // 打开PDF文档 + wx.openDocument({ + filePath: res.tempFilePath, + fileType: 'pdf', + showMenu: true, + success: () => { + console.log('PDF打开成功') + }, + fail: (err) => { + console.error('打开PDF失败:', err) + wx.showToast({ + title: '打开失败', + icon: 'none' + }) + } + }) + } + }, + fail: (err) => { + wx.hideLoading() + console.error('下载PDF失败:', err) + wx.showToast({ + title: '加载失败', + icon: 'none' + }) + } + }) + }, + + // 检查是否已下载过 + hasDownloaded(worksheetId) { + const downloads = wx.getStorageSync('downloads') || [] + return downloads.some(d => d.id === worksheetId) + }, + + // 下载PDF + async downloadPdf() { + // 1. 检查是否登录 + const userId = wx.getStorageSync('userId') + if (!userId) { + wx.showModal({ + title: '提示', + content: '请先登录后再下载', + confirmText: '去登录', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + // 保存返回信息 + wx.setStorageSync('returnTo', { + type: 'navigateTo', + url: `/pages/detail/detail?id=${this.data.id}` + }) + wx.navigateTo({ + url: '/pages/login/login' + }) + } + } + }) + return + } + + // 2. 检查是否已下载过(已下载过的不扣积分) + const alreadyDownloaded = this.hasDownloaded(this.data.id) + + // 3. 如果未下载过,检查积分 + if (!alreadyDownloaded) { + try { + const pointsRes = await checkPoints(userId) + if (!pointsRes.success || !pointsRes.data.canDownload) { + wx.showModal({ + title: '积分不足', + content: `下载需要 ${pointsRes.data?.costPoints || 1} 积分,您当前积分为 ${pointsRes.data?.points || 0}。分享小程序可获得积分!`, + confirmText: '去分享', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + // 触发分享 + this.shareToGetPoints() + } + } + }) + return + } + } catch (error) { + console.error('检查积分失败:', error) + wx.showToast({ + title: '网络错误', + icon: 'none' + }) + return + } + } + + // 4. 开始下载 + const pdfUrl = DATA_BASE_URL + this.data.detail.pdfurl + const title = this.data.detail.e_title || this.data.detail.title || 'worksheet' + const fileName = `${title}.pdf` + + wx.showLoading({ + title: '下载中...', + mask: true + }) + + try { + let remainingPoints = null + + // 只有首次下载才扣积分 + if (!alreadyDownloaded) { + const deductRes = await deductPoints(userId, this.data.id) + if (!deductRes.success) { + wx.hideLoading() + wx.showToast({ + title: deductRes.message || '扣除积分失败', + icon: 'none' + }) + return + } + remainingPoints = deductRes.data.remainingPoints + } + + // 下载文件 + wx.downloadFile({ + url: pdfUrl, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200) { + const tempFilePath = res.tempFilePath + + // 保存到本地 + const fs = wx.getFileSystemManager() + const savedPath = `${wx.env.USER_DATA_PATH}/${fileName}` + + fs.saveFile({ + tempFilePath: tempFilePath, + filePath: savedPath, + success: (saveRes) => { + this.setData({ downloadedPath: saveRes.savedFilePath }) + + // 保存下载记录 + this.saveDownloadRecord() + + // 根据是否是首次下载显示不同提示 + let content = '文件已保存,是否立即打开?' + if (!alreadyDownloaded && remainingPoints !== null) { + content = `文件已保存,消耗1积分,剩余${remainingPoints}积分。是否立即打开?` + } else if (alreadyDownloaded) { + content = '文件已保存(已购买,无需扣分)。是否立即打开?' + } + + wx.showModal({ + title: '下载成功', + content: content, + confirmText: '打开', + cancelText: '稍后', + success: (modalRes) => { + if (modalRes.confirm) { + wx.openDocument({ + filePath: saveRes.savedFilePath, + fileType: 'pdf', + showMenu: true + }) + } + } + }) + }, + fail: (err) => { + console.error('保存文件失败:', err) + // 如果保存失败,直接打开临时文件 + wx.showModal({ + title: '下载成功', + content: '是否立即打开?', + confirmText: '打开', + cancelText: '稍后', + success: (modalRes) => { + if (modalRes.confirm) { + wx.openDocument({ + filePath: tempFilePath, + fileType: 'pdf', + showMenu: true + }) + } + } + }) + } + }) + } + }, + fail: (err) => { + wx.hideLoading() + console.error('下载失败:', err) + wx.showToast({ + title: '下载失败', + icon: 'none' + }) + } + }) + } catch (error) { + wx.hideLoading() + console.error('下载失败:', error) + } + }, + + // 分享获取积分 + shareToGetPoints() { + wx.showToast({ + title: '点击右上角分享给好友', + icon: 'none', + duration: 2000 + }) + }, + + // 保存下载记录 + saveDownloadRecord() { + try { + const downloads = wx.getStorageSync('downloads') || [] + const record = { + id: this.data.id, + title: this.data.detail.title, + e_title: this.data.detail.e_title, + coverurl: this.data.detail.coverurl, + pdfurl: this.data.detail.pdfurl, + category_name: this.data.detail.category_name, + downloadTime: Date.now(), + filePath: this.data.downloadedPath + } + + // 检查是否已存在 + const existIndex = downloads.findIndex(d => d.id === record.id) + if (existIndex > -1) { + downloads[existIndex] = record + } else { + downloads.unshift(record) + } + + // 最多保存100条 + if (downloads.length > 100) { + downloads.pop() + } + + wx.setStorageSync('downloads', downloads) + } catch (error) { + console.error('保存下载记录失败:', error) + } + }, + + // 打印PDF + printPdf() { + const pdfUrl = DATA_BASE_URL + this.data.detail.pdfurl + + wx.showLoading({ + title: '准备打印...', + mask: true + }) + + // 先下载PDF + wx.downloadFile({ + url: pdfUrl, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200) { + // 微信小程序没有直接打印API,引导用户通过打开文档后使用系统打印 + wx.openDocument({ + filePath: res.tempFilePath, + fileType: 'pdf', + showMenu: true, + success: () => { + wx.showToast({ + title: '请点击右上角菜单进行打印', + icon: 'none', + duration: 3000 + }) + }, + fail: (err) => { + console.error('打开PDF失败:', err) + wx.showToast({ + title: '打开失败', + icon: 'none' + }) + } + }) + } + }, + fail: (err) => { + wx.hideLoading() + console.error('下载失败:', err) + wx.showToast({ + title: '加载失败', + icon: 'none' + }) + } + }) + }, + + // 跳转到其他详情 + goDetail(e) { + const id = e.currentTarget.dataset.id + wx.redirectTo({ + url: `/pages/detail/detail?id=${id}` + }) + } +})) diff --git a/pages/detail/detail.json b/pages/detail/detail.json new file mode 100644 index 0000000..38d4d33 --- /dev/null +++ b/pages/detail/detail.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "练习表详情" +} diff --git a/pages/detail/detail.wxml b/pages/detail/detail.wxml new file mode 100644 index 0000000..9538ad9 --- /dev/null +++ b/pages/detail/detail.wxml @@ -0,0 +1,59 @@ + + + + + + 点击图片预览大图 + + + + + + {{detail.category_name}} + + 浏览 {{detail.view_count || 0}} + 下载 {{detail.download_count || 0}} + + + {{detail.title}} + {{detail.e_title}} + {{detail.des}} + + + + 💡 + Tips: 打开PDF点右上角'···'选择你打印机打印 + + + + + + 下载PDF + + + + + + + 相关推荐 + + + + + + {{item.title}} + + + + + + + + diff --git a/pages/detail/detail.wxss b/pages/detail/detail.wxss new file mode 100644 index 0000000..c0a322e --- /dev/null +++ b/pages/detail/detail.wxss @@ -0,0 +1,213 @@ +/* 详情页面样式 */ +.container { + padding-bottom: 40rpx; + min-height: 100vh; + background: #f5f5f5; +} + +/* 封面预览 */ +.preview-section { + background: #fff; + padding: 30rpx; + text-align: center; +} + +.preview-image { + width: 100%; + height: 500rpx; + border-radius: 16rpx; + background: #f5f5f5; +} + +.preview-tip { + margin-top: 16rpx; + font-size: 24rpx; + color: #999; +} + +/* 基本信息 */ +.info-section { + background: #fff; + padding: 30rpx; + margin-top: 20rpx; +} + +.info-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20rpx; +} + +.category-tag { + display: inline-block; + padding: 8rpx 20rpx; + background: linear-gradient(135deg, #4CAF50, #8BC34A); + border-radius: 20rpx; + font-size: 24rpx; + color: #fff; +} + +.stats { + display: flex; + align-items: center; +} + +.stat-item { + font-size: 24rpx; + color: #999; + margin-left: 24rpx; +} + +.info-title { + display: block; + font-size: 36rpx; + font-weight: bold; + color: #333; + line-height: 1.4; + margin-bottom: 12rpx; +} + +.info-e-title { + display: block; + font-size: 28rpx; + color: #666; + margin-bottom: 20rpx; +} + +.info-desc { + display: block; + font-size: 26rpx; + color: #999; + line-height: 1.6; +} + +/* 操作按钮 */ +.action-section { + display: flex; + justify-content: space-between; + padding: 30rpx; + background: #fff; + margin-top: 20rpx; +} + +.action-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + padding: 24rpx 0; + margin: 0 10rpx; + border-radius: 16rpx; + transition: all 0.3s; +} + +.action-btn:first-child { + margin-left: 0; +} + +.action-btn:last-child { + margin-right: 0; +} + +.preview-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.download-btn { + background: linear-gradient(135deg, #4CAF50, #8BC34A); +} + +.print-btn { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.action-icon { + width: 48rpx; + height: 48rpx; + margin-bottom: 12rpx; +} + +.action-btn text { + font-size: 26rpx; + color: #fff; +} + +/* 相关推荐 */ +.recommend-section { + background: #fff; + padding: 30rpx 0 30rpx 30rpx; + margin-top: 20rpx; +} + +.section-header { + margin-bottom: 20rpx; +} + +.section-title { + font-size: 32rpx; + font-weight: bold; + color: #333; +} + +.recommend-scroll { + width: 100%; +} + +.recommend-list { + display: flex; + white-space: nowrap; +} + +.recommend-item { + flex-shrink: 0; + width: 200rpx; + margin-right: 20rpx; +} + +.recommend-item:last-child { + margin-right: 30rpx; +} + +.recommend-cover { + width: 200rpx; + height: 150rpx; + border-radius: 12rpx; + background: #f5f5f5; +} + +.recommend-title { + display: block; + margin-top: 12rpx; + font-size: 24rpx; + color: #333; + white-space: normal; +} + +/* 提示信息 */ +.tips-box { + display: flex; + align-items: center; + justify-content: center; + margin: 20rpx 30rpx; + padding: 16rpx 24rpx; + background: rgba(76, 175, 80, 0.1); + border-radius: 8rpx; +} + +.tips-icon { + font-size: 28rpx; + margin-right: 8rpx; +} + +.tips-text { + font-size: 28rpx; + color: #4CAF50; + line-height: 1.4; +} + +/* 底部安全区域 */ +.safe-bottom { + height: env(safe-area-inset-bottom); +} diff --git a/pages/download/download.js b/pages/download/download.js new file mode 100644 index 0000000..1dd9116 --- /dev/null +++ b/pages/download/download.js @@ -0,0 +1,150 @@ +import { injectPage } from '@jdmini/api' +const { DATA_BASE_URL } = require('../../utils/config.js') + +Page(injectPage({})({ + data: { + dataBaseUrl: DATA_BASE_URL, + downloads: [] + }, + + onShow() { + this.loadDownloads() + }, + + // 加载下载记录 + loadDownloads() { + try { + const downloads = wx.getStorageSync('downloads') || [] + + // 格式化时间 + const formattedDownloads = downloads.map(item => ({ + ...item, + downloadTimeStr: this.formatTime(item.downloadTime) + })) + + this.setData({ downloads: formattedDownloads }) + } catch (error) { + console.error('加载下载记录失败:', error) + } + }, + + // 格式化时间 + formatTime(timestamp) { + const date = new Date(timestamp) + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + const hours = String(date.getHours()).padStart(2, '0') + const minutes = String(date.getMinutes()).padStart(2, '0') + return `${year}-${month}-${day} ${hours}:${minutes}` + }, + + // 打开文件 + openFile(e) { + const index = e.currentTarget.dataset.index + const item = this.data.downloads[index] + + if (item.filePath) { + // 尝试打开已保存的文件 + wx.openDocument({ + filePath: item.filePath, + fileType: 'pdf', + showMenu: true, + fail: (err) => { + console.error('打开文件失败:', err) + // 文件可能被删除,重新下载 + this.redownload(item) + } + }) + } else { + // 没有本地文件,重新下载 + this.redownload(item) + } + }, + + // 重新下载 + redownload(item) { + const pdfUrl = DATA_BASE_URL + item.pdfurl + + wx.showLoading({ + title: '加载中...', + mask: true + }) + + wx.downloadFile({ + url: pdfUrl, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200) { + wx.openDocument({ + filePath: res.tempFilePath, + fileType: 'pdf', + showMenu: true + }) + } + }, + fail: () => { + wx.hideLoading() + wx.showToast({ + title: '加载失败', + icon: 'none' + }) + } + }) + }, + + // 删除下载记录 + deleteItem(e) { + const index = e.currentTarget.dataset.index + const item = this.data.downloads[index] + + wx.showModal({ + title: '提示', + content: '确定删除此下载记录吗?', + success: (res) => { + if (res.confirm) { + // 删除本地文件 + if (item.filePath) { + try { + const fs = wx.getFileSystemManager() + fs.unlinkSync(item.filePath) + } catch (error) { + console.log('删除文件失败或文件不存在') + } + } + + // 更新记录 + const downloads = this.data.downloads.filter((_, i) => i !== index) + this.setData({ downloads }) + + // 更新存储 + const storageData = downloads.map(d => { + const { downloadTimeStr, ...rest } = d + return rest + }) + wx.setStorageSync('downloads', storageData) + + wx.showToast({ + title: '已删除', + icon: 'success' + }) + } + } + }) + }, + + // 跳转详情 + goDetail(e) { + const id = e.currentTarget.dataset.id + wx.navigateTo({ + url: `/pages/detail/detail?id=${id}` + }) + }, + + // 去浏览 + goHome() { + wx.switchTab({ + url: '/pages/index/index' + }) + } +})) diff --git a/pages/download/download.json b/pages/download/download.json new file mode 100644 index 0000000..cf513a3 --- /dev/null +++ b/pages/download/download.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "我的下载" +} diff --git a/pages/download/download.wxml b/pages/download/download.wxml new file mode 100644 index 0000000..a097708 --- /dev/null +++ b/pages/download/download.wxml @@ -0,0 +1,36 @@ + + + + + + + + + {{item.title}} + {{item.category_name}} + 下载时间: {{item.downloadTimeStr}} + + + + + 打开 + + + 删除 + + + + + + + + + 暂无下载记录 + 浏览练习表并下载PDF文件后,将在此处显示 + + 去浏览 + + + diff --git a/pages/download/download.wxss b/pages/download/download.wxss new file mode 100644 index 0000000..5a9a88e --- /dev/null +++ b/pages/download/download.wxss @@ -0,0 +1,129 @@ +/* 下载页面样式 */ +.container { + min-height: 100vh; + background: #f5f5f5; + padding-bottom: env(safe-area-inset-bottom); +} + +/* 下载列表 */ +.download-list { + padding: 20rpx; +} + +.download-item { + background: #fff; + border-radius: 16rpx; + margin-bottom: 20rpx; + overflow: hidden; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); +} + +.item-content { + display: flex; + padding: 20rpx; +} + +.item-cover { + width: 160rpx; + height: 120rpx; + border-radius: 8rpx; + background: #f5f5f5; + flex-shrink: 0; +} + +.item-info { + flex: 1; + margin-left: 20rpx; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.item-title { + font-size: 28rpx; + color: #333; + line-height: 1.4; +} + +.item-category { + font-size: 24rpx; + color: #4CAF50; +} + +.item-time { + font-size: 22rpx; + color: #999; +} + +.item-actions { + display: flex; + border-top: 1rpx solid #f0f0f0; +} + +.action-btn { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 20rpx 0; + border-right: 1rpx solid #f0f0f0; +} + +.action-btn:last-child { + border-right: none; +} + +.action-btn image { + width: 32rpx; + height: 32rpx; + margin-right: 8rpx; +} + +.action-btn text { + font-size: 26rpx; + color: #666; +} + +.action-btn.delete text { + color: #f56c6c; +} + +/* 空状态 */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 160rpx 60rpx; +} + +.empty-img { + width: 240rpx; + height: 240rpx; + margin-bottom: 40rpx; + opacity: 0.6; +} + +.empty-title { + font-size: 32rpx; + color: #333; + margin-bottom: 16rpx; +} + +.empty-desc { + font-size: 26rpx; + color: #999; + text-align: center; + margin-bottom: 40rpx; +} + +.empty-btn { + padding: 20rpx 60rpx; + background: linear-gradient(135deg, #4CAF50, #8BC34A); + border-radius: 40rpx; +} + +.empty-btn text { + font-size: 28rpx; + color: #fff; +} diff --git a/pages/index/index.js b/pages/index/index.js new file mode 100644 index 0000000..b6fa105 --- /dev/null +++ b/pages/index/index.js @@ -0,0 +1,60 @@ +import { injectPage } from '@jdmini/api' +const { getHomeData } = require('../../utils/api.js') +const { DATA_BASE_URL } = require('../../utils/config.js') + +Page(injectPage({})({ + data: { + dataBaseUrl: DATA_BASE_URL, + // 分类数据(每个分类的第一条作为封面) + worksheets: [], + loading: false + }, + + onLoad() { + this.loadHomeData() + }, + + onPullDownRefresh() { + this.loadHomeData().finally(() => { + wx.stopPullDownRefresh() + }) + }, + + // 加载首页数据 + async loadHomeData() { + try { + this.setData({ loading: true }) + const res = await getHomeData() + if (res.success) { + this.setData({ + worksheets: res.data.worksheets || [], + loading: false + }) + } + } catch (error) { + console.error('加载首页数据失败:', error) + this.setData({ loading: false }) + wx.showToast({ + title: '加载失败', + icon: 'none' + }) + } + }, + + // 跳转搜索页 + goSearch() { + wx.navigateTo({ + url: '/pages/search/search' + }) + }, + + // 跳转到分类详情页面 + goCategory(e) { + const categoryId = e.currentTarget.dataset.id + const item = this.data.worksheets.find(w => w.category_id === categoryId) + const categoryName = item ? item.category_name : '' + wx.navigateTo({ + url: `/pages/category-detail/category-detail?id=${categoryId}&name=${encodeURIComponent(categoryName)}` + }) + } +})) diff --git a/pages/index/index.json b/pages/index/index.json new file mode 100644 index 0000000..287657f --- /dev/null +++ b/pages/index/index.json @@ -0,0 +1,5 @@ +{ + "usingComponents": {}, + "enablePullDownRefresh": true, + "backgroundTextStyle": "dark" +} diff --git a/pages/index/index.wxml b/pages/index/index.wxml new file mode 100644 index 0000000..aa488fa --- /dev/null +++ b/pages/index/index.wxml @@ -0,0 +1,48 @@ + + + + + + + 搜索练习表... + + + + + + + + + + + {{item.category_name}} + {{item.category_e_name}} + + + + + diff --git a/pages/index/index.wxss b/pages/index/index.wxss new file mode 100644 index 0000000..a7150c3 --- /dev/null +++ b/pages/index/index.wxss @@ -0,0 +1,130 @@ +/* 首页样式 */ +.container { + padding-bottom: 20rpx; +} + +/* 搜索栏 */ +.search-bar { + padding: 20rpx 30rpx; + background: #fff; +} + +.search-input { + display: flex; + align-items: center; + padding: 16rpx 24rpx; + background: #f5f5f5; + border-radius: 40rpx; +} + +.search-icon { + width: 36rpx; + height: 36rpx; + margin-right: 16rpx; +} + +.search-placeholder { + color: #999; + font-size: 28rpx; +} + +/* Banner轮播图 */ +.banner-section { + padding: 0 30rpx 20rpx; +} + +.banner-swiper { + height: 280rpx; + border-radius: 20rpx; + overflow: hidden; +} + +.banner-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 30rpx 40rpx; + height: 100%; + border-radius: 20rpx; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.banner-content { + flex: 1; +} + +.banner-title { + display: block; + font-size: 40rpx; + font-weight: bold; + color: #fff; + margin-bottom: 16rpx; +} + +.banner-desc { + display: block; + font-size: 26rpx; + color: rgba(255, 255, 255, 0.8); +} + +.banner-img { + width: 180rpx; + height: 180rpx; +} + +/* 分类列表 */ +.category-list-section { + display: flex; + flex-wrap: wrap; + padding: 20rpx; + gap: 20rpx; +} + +.category-card { + width: calc(50% - 20rpx); + background: #fff; + border-radius: 16rpx; + overflow: hidden; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.category-cover { + position: relative; + width: 100%; + padding-top: 100%; + background: #f5f5f5; +} + +.category-cover .cover-img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.category-info { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 20rpx 16rpx; + background: rgba(76,175,80,0.85); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + text-align: center; +} + +.category-name { + display: block; + font-size: 30rpx; + font-weight: bold; + color: #fff; + margin-bottom: 6rpx; +} + +.category-e-name { + display: block; + font-size: 22rpx; + color: rgba(255,255,255,0.9); +} diff --git a/pages/login/login.js b/pages/login/login.js new file mode 100644 index 0000000..3564d01 --- /dev/null +++ b/pages/login/login.js @@ -0,0 +1,220 @@ +import { injectPage, gatewayHttpClient } from '@jdmini/api' +const api = require('../../utils/api.js') +const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0' + +Page(injectPage({})({ + data: { + avatarUrl: defaultAvatarUrl, + nickname: '', + isLoading: false, + loginSuccess: false, + btnText: '完成登录', + btnIcon: '✓', + canSubmit: false + }, + + onLoad(options) { + // 检查是否已登录 + const userId = wx.getStorageSync('userId'); + if (userId) { + // 已登录,检查是否有返回页面 + this.navigateBack(); + return; + } + + // 保存来源页面信息 + if (options.from) { + this.setData({ + fromPage: options.from + }); + } + + // 设置默认头像并检查提交状态 + this.checkCanSubmit(); + }, + + // 选择头像 + onChooseAvatar(e) { + const { avatarUrl } = e.detail; + this.setData({ + avatarUrl: avatarUrl + }, () => { + this.checkCanSubmit(); + }); + }, + + // 输入昵称 + onNicknameInput(e) { + this.setData({ + nickname: e.detail.value + }, () => { + this.checkCanSubmit(); + }); + }, + + // 检查是否可以提交 + checkCanSubmit() { + const { avatarUrl, nickname } = this.data; + // 检查是否选择了自定义头像(非默认头像)和填写了昵称 + const hasCustomAvatar = avatarUrl && avatarUrl !== defaultAvatarUrl; + const hasNickname = nickname && nickname.trim().length > 0; + const canSubmit = hasCustomAvatar && hasNickname; + this.setData({ + canSubmit: canSubmit + }); + }, + + // 处理登录 + async handleLogin() { + const { avatarUrl, nickname, isLoading } = this.data; + + if (isLoading) { + return; + } + + // 检查头像是否已授权(是否选择了非默认头像) + const hasCustomAvatar = avatarUrl && avatarUrl !== defaultAvatarUrl; + + // 检查昵称是否已填写 + const hasNickname = nickname && nickname.trim().length > 0; + + // 验证授权状态 + if (!hasCustomAvatar && !hasNickname) { + wx.showToast({ + title: '请先授权头像,再点授权昵称', + icon: 'none', + duration: 2500 + }); + return; + } + + if (!hasCustomAvatar) { + wx.showToast({ + title: '头像未授权', + icon: 'none', + duration: 2000 + }); + return; + } + + if (!hasNickname) { + wx.showToast({ + title: '昵称未授权', + icon: 'none', + duration: 2000 + }); + return; + } + + // 开始登录 + this.setData({ + isLoading: true, + btnText: '登录中...', + btnIcon: '⏳' + }); + + // 获取 app.js 中通过 waitLogin 已经获取的第三方登录信息 + const app = getApp(); + const openid = app.globalData.openid; + const wxUserInfo = app.globalData.wxUserInfo || wx.getStorageSync('jdwx-userinfo'); + + if (!openid && !wxUserInfo) { + this.handleLoginError('获取登录信息失败,请重启小程序'); + return; + } + //上传头像到图片服务 + + const JDavatarUrl= await gatewayHttpClient.uploadAvatar(avatarUrl) + + console.log(JDavatarUrl.data) + // 使用 openid 进行登录 + const finalOpenid = openid || wxUserInfo.openId; + this.performLogin(finalOpenid, nickname.trim(), JDavatarUrl.data); + }, + + // 执行登录请求 + performLogin(openid, nickname, avatarUrl) { + // 获取邀请人ID + const inviterId = wx.getStorageSync('inviterId') || getApp().globalData.inviterId || null + + // 调用后端登录接口 + api.userLogin(openid, nickname, avatarUrl, inviterId).then(result => { + // 清除邀请人ID(只在注册时使用一次) + wx.removeStorageSync('inviterId') + + // 保存用户信息 + wx.setStorageSync('userId', result.data.user.id); + wx.setStorageSync('token', result.data.token); + wx.setStorageSync('userInfo', { + nickname: result.data.user.nickname, + avatar: result.data.user.avatar, + points: result.data.user.points + }); + + // 更新按钮状态 + this.setData({ + isLoading: false, + loginSuccess: true, + btnText: '登录成功', + btnIcon: '✓' + }); + + // 延迟跳转 + setTimeout(() => { + this.navigateBack(); + }, 1000); + + }).catch(err => { + console.error('登录失败', err); + this.handleLoginError(err.message || '登录失败,请重试'); + }); + }, + + // 处理登录错误 + handleLoginError(message) { + wx.showToast({ + title: message, + icon: 'none' + }); + + this.setData({ + isLoading: false, + btnText: '完成登录', + btnIcon: '✓' + }); + }, + + // 导航返回 + navigateBack() { + // 检查是否有存储的返回信息 + const returnTo = wx.getStorageSync('returnTo'); + + if (returnTo) { + // 清除存储的返回信息 + wx.removeStorageSync('returnTo'); + + // 根据返回类型进行跳转 + if (returnTo.type === 'switchTab') { + wx.switchTab({ + url: returnTo.url + }); + } else if (returnTo.type === 'redirectTo') { + wx.redirectTo({ + url: returnTo.url + }); + } else { + wx.navigateTo({ + url: returnTo.url + }); + } + } else if (this.data.fromPage) { + // 使用URL参数中的来源页面 + wx.navigateBack(); + } else { + // 默认返回首页 + wx.switchTab({ + url: '/pages/index/index' + }); + } + } +})) diff --git a/pages/login/login.json b/pages/login/login.json new file mode 100644 index 0000000..40df7ae --- /dev/null +++ b/pages/login/login.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "登录", + "usingComponents": {} +} diff --git a/pages/login/login.wxml b/pages/login/login.wxml new file mode 100644 index 0000000..fd877fa --- /dev/null +++ b/pages/login/login.wxml @@ -0,0 +1,55 @@ + + + + + 微信登录授权 + 请授权获取您的头像和昵称信息 + + + + + + + + + + + + + + + + 授权中... + ✓ 已授权 + 立即授权 + + + + + 授权说明 + + + 获取您的公开信息(昵称、头像) + + + + 用于完善您的个人资料 + + + + 我们承诺保护您的隐私安全 + + + diff --git a/pages/login/login.wxss b/pages/login/login.wxss new file mode 100644 index 0000000..449b55c --- /dev/null +++ b/pages/login/login.wxss @@ -0,0 +1,218 @@ +page { + background-color: #f5f5f5; + min-height: 100vh; +} + +/* 主容器 */ +.container { + padding: 0; + min-height: 100vh; + background-color: #f5f5f5; + display: flex; + flex-direction: column; + align-items: center; +} + +/* 头部 */ +.header { + text-align: center; + padding: 80rpx 0 60rpx; + background-color: #f5f5f5; + width: 100%; +} + +.logo { + width: 160rpx; + height: 160rpx; + background: linear-gradient(135deg, #12b559 0%, #0aa750 100%); + border-radius: 32rpx; + margin: 0 auto 32rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 72rpx; + color: #fff; + box-shadow: 0 4rpx 16rpx rgba(18, 181, 89, 0.25); +} + +.title { + display: block; + font-size: 34rpx; + font-weight: 400; + color: #000; + margin-bottom: 16rpx; +} + +.subtitle { + display: block; + font-size: 26rpx; + color: #888; + line-height: 1.6; +} + +/* 用户信息卡片 */ +.user-card { + width: 620rpx; + background: #fff; + border-radius: 16rpx; + padding: 60rpx 0; + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 40rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); +} + +/* 头像选择器 */ +.avatar-picker { + background: none; + border: none; + padding: 0; + margin: 0 auto 24rpx; + line-height: normal; + display: block; + width: 172rpx; +} + +.avatar-picker::after { + border: none; +} + +.avatar-wrapper { + width: 160rpx; + height: 160rpx; + border-radius: 50%; + background: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border: 6rpx solid #07c160; + position: relative; + margin: 0 auto; +} + +.avatar-wrapper.has-avatar { + border-color: #07c160; +} + +.avatar-image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.avatar-icon { + font-size: 80rpx; + color: #c8c8c8; +} + +/* 昵称输入框(按钮样式) */ +.nickname-wrapper { + width: 100%; + display: flex; + justify-content: center; +} + +.nickname-btn-wrapper { + background: #f8f8f8; + border: 1rpx solid #e5e5e5; + border-radius: 8rpx; + padding: 20rpx 60rpx; + min-width: 400rpx; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s; +} + +.nickname-btn-wrapper:active { + background: #efefef; +} + +.nickname-btn-input { + width: 100%; + height: 40rpx; + font-size: 32rpx; + color: #000; + font-weight: 400; + text-align: center; + background: transparent; + border: none; +} + +.nickname-placeholder { + color: #999; + font-size: 32rpx; + text-align: center; +} + +/* 授权按钮 */ +.auth-btn { + width: 620rpx; + height: 88rpx; + background: #07c160; + border-radius: 8rpx; + border: none; + color: #fff; + font-size: 32rpx; + font-weight: 400; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 50rpx; + box-shadow: 0 2rpx 8rpx rgba(7, 193, 96, 0.3); + transition: all 0.3s; + cursor: pointer; +} + +.auth-btn:active { + opacity: 0.8; + transform: scale(0.98); +} + +.auth-btn.disabled { + background: #9ed99d; + color: rgba(255, 255, 255, 0.7); + box-shadow: none; + cursor: not-allowed; +} + +.auth-btn.disabled:active { + opacity: 1; + transform: scale(1); +} + +/* 授权说明 */ +.permissions { + width: 620rpx; + padding: 0; +} + +.permissions-title { + display: block; + font-size: 28rpx; + color: #000; + font-weight: 400; + margin-bottom: 24rpx; +} + +.permission-item { + display: flex; + align-items: flex-start; + margin-bottom: 14rpx; + font-size: 26rpx; + color: #666; + line-height: 1.8; +} + +.permission-item:last-child { + margin-bottom: 0; +} + +.dot { + margin-right: 12rpx; + color: #000; + font-size: 24rpx; + line-height: 1.8; +} diff --git a/pages/mine/mine.js b/pages/mine/mine.js new file mode 100644 index 0000000..e093fe5 --- /dev/null +++ b/pages/mine/mine.js @@ -0,0 +1,231 @@ +import { injectPage } from '@jdmini/api' +const { getUserInfo } = require('../../utils/api.js') + +const defaultAvatar = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0' + +Page(injectPage({})({ + data: { + isLoggedIn: false, + userInfo: {}, + defaultAvatar: defaultAvatar, + downloadCount: 0, + inviteCount: 0, + cacheSize: '0KB' + }, + + onShow() { + this.checkLoginStatus() + this.loadStats() + this.calculateCacheSize() + }, + + onShareAppMessage() { + const userId = wx.getStorageSync('userId') + let path = '/pages/index/index' + if (userId) { + path += `?inviter=${userId}` + } + return { + title: '儿童练习表 - 幼儿启蒙必备,快来一起学习吧!', + path: path + } + }, + + // 检查登录状态 + checkLoginStatus() { + const userId = wx.getStorageSync('userId') + const userInfo = wx.getStorageSync('userInfo') || {} + + if (userId) { + this.setData({ + isLoggedIn: true, + userInfo: userInfo + }) + // 刷新用户信息 + this.refreshUserInfo(userId) + } else { + this.setData({ + isLoggedIn: false, + userInfo: {} + }) + } + }, + + // 刷新用户信息 + async refreshUserInfo(userId) { + try { + const res = await getUserInfo(userId) + if (res.success) { + const userInfo = { + ...this.data.userInfo, + nickname: res.data.nickname, + avatar: res.data.avatar, + points: res.data.points + } + this.setData({ userInfo }) + wx.setStorageSync('userInfo', userInfo) + } + } catch (error) { + console.error('刷新用户信息失败:', error) + } + }, + + // 加载统计数据 + loadStats() { + try { + const downloads = wx.getStorageSync('downloads') || [] + // 邀请数暂时从本地存储获取,实际应该从服务端获取 + const inviteCount = wx.getStorageSync('inviteCount') || 0 + + this.setData({ + downloadCount: downloads.length, + inviteCount: inviteCount + }) + } catch (error) { + console.error('加载统计失败:', error) + } + }, + + // 计算缓存大小 + calculateCacheSize() { + try { + const res = wx.getStorageInfoSync() + const usedSize = res.currentSize // KB + let sizeStr = '' + + if (usedSize < 1024) { + sizeStr = usedSize + 'KB' + } else { + sizeStr = (usedSize / 1024).toFixed(2) + 'MB' + } + + this.setData({ cacheSize: sizeStr }) + } catch (error) { + console.error('计算缓存大小失败:', error) + } + }, + + // 跳转登录 + goLogin() { + wx.setStorageSync('returnTo', { + type: 'switchTab', + url: '/pages/mine/mine' + }) + wx.navigateTo({ + url: '/pages/login/login' + }) + }, + + // 跳转下载页面 + goDownloads() { + wx.switchTab({ + url: '/pages/download/download' + }) + }, + + // 跳转积分明细 + goPointsLog() { + if (!this.data.isLoggedIn) { + this.goLogin() + return + } + wx.showToast({ + title: '积分明细功能开发中', + icon: 'none' + }) + }, + + // 清除缓存 + clearCache() { + wx.showModal({ + title: '提示', + content: '确定清除所有缓存吗?这将删除下载记录(不会退出登录)。', + success: (res) => { + if (res.confirm) { + try { + // 保存登录信息 + const userId = wx.getStorageSync('userId') + const token = wx.getStorageSync('token') + const userInfo = wx.getStorageSync('userInfo') + + // 清除下载的文件 + const downloads = wx.getStorageSync('downloads') || [] + const fs = wx.getFileSystemManager() + + downloads.forEach(item => { + if (item.filePath) { + try { + fs.unlinkSync(item.filePath) + } catch (e) { + // 忽略文件不存在的错误 + } + } + }) + + // 清除存储 + wx.clearStorageSync() + + // 恢复登录信息 + if (userId) { + wx.setStorageSync('userId', userId) + wx.setStorageSync('token', token) + wx.setStorageSync('userInfo', userInfo) + } + + this.setData({ + downloadCount: 0, + inviteCount: 0, + cacheSize: '0KB' + }) + + wx.showToast({ + title: '清除成功', + icon: 'success' + }) + } catch (error) { + console.error('清除缓存失败:', error) + wx.showToast({ + title: '清除失败', + icon: 'none' + }) + } + } + } + }) + }, + + // 退出登录 + logout() { + wx.showModal({ + title: '提示', + content: '确定退出登录吗?', + success: (res) => { + if (res.confirm) { + wx.removeStorageSync('userId') + wx.removeStorageSync('token') + wx.removeStorageSync('userInfo') + + this.setData({ + isLoggedIn: false, + userInfo: {} + }) + + wx.showToast({ + title: '已退出登录', + icon: 'success' + }) + } + } + }) + }, + + // 关于我们 + about() { + wx.showModal({ + title: '关于儿童练习表', + content: '儿童练习表是一款专为幼儿设计的启蒙教育小程序,提供字母、数字、绘画、涂色等多种练习表,帮助孩子在快乐中学习成长。\n\n版本:v1.0.0', + showCancel: false, + confirmText: '确定' + }) + } +})) diff --git a/pages/mine/mine.json b/pages/mine/mine.json new file mode 100644 index 0000000..d8f6441 --- /dev/null +++ b/pages/mine/mine.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "我的" +} diff --git a/pages/mine/mine.wxml b/pages/mine/mine.wxml new file mode 100644 index 0000000..ab468fd --- /dev/null +++ b/pages/mine/mine.wxml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + {{userInfo.points || 0}} + 积分 + + + + {{downloadCount}} + 下载数 + + + + {{inviteCount}} + 邀请数 + + + + + + + + 📥 + 我的下载 + + + + + + + + + 🗑️ + 清除缓存 + + {{cacheSize}} + + + + + ℹ️ + 关于我们 + + v1.0.0 + + + + + + 🚪 + 退出登录 + + + + + + + diff --git a/pages/mine/mine.wxss b/pages/mine/mine.wxss new file mode 100644 index 0000000..c314b4a --- /dev/null +++ b/pages/mine/mine.wxss @@ -0,0 +1,186 @@ +/* 我的页面样式 */ +.container { + min-height: 100vh; + background: #f5f5f5; + padding-bottom: env(safe-area-inset-bottom); +} + +/* 用户信息 */ +.user-section { + background: linear-gradient(135deg, #4CAF50 0%, #8BC34A 100%); + padding: 60rpx 40rpx 80rpx; +} + +.user-section-unlogin { + padding-bottom: 80rpx; +} + +.user-info { + display: flex; + align-items: center; +} + +.user-avatar { + width: 120rpx; + height: 120rpx; + border-radius: 60rpx; + border: 4rpx solid rgba(255, 255, 255, 0.3); + background: #fff; +} + +.user-detail { + margin-left: 30rpx; + flex: 1; +} + +.user-name { + display: block; + font-size: 36rpx; + font-weight: bold; + color: #fff; + margin-bottom: 8rpx; +} + +.user-desc { + font-size: 26rpx; + color: rgba(255, 255, 255, 0.8); +} + +/* 积分显示 */ +.points-box { + display: flex; + align-items: center; + margin-top: 30rpx; + padding: 20rpx 24rpx; + background: rgba(255, 255, 255, 0.2); + border-radius: 12rpx; +} + +.points-label { + font-size: 26rpx; + color: rgba(255, 255, 255, 0.9); +} + +.points-value { + flex: 1; + text-align: right; + font-size: 36rpx; + font-weight: bold; + color: #fff; + margin-right: 10rpx; +} + +.points-arrow { + font-size: 32rpx; + color: rgba(255, 255, 255, 0.7); +} + +/* 统计信息 */ +.stats-section { + display: flex; + align-items: center; + justify-content: space-around; + background: #fff; + margin: -40rpx 30rpx 0; + padding: 30rpx 0; + border-radius: 16rpx; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08); +} + +.stat-item { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; +} + +.stat-num { + font-size: 40rpx; + font-weight: bold; + color: #4CAF50; +} + +.stat-label { + font-size: 24rpx; + color: #999; + margin-top: 8rpx; +} + +.stat-divider { + width: 1rpx; + height: 60rpx; + background: #eee; +} + +/* 功能列表 */ +.menu-section { + background: #fff; + margin: 30rpx; + border-radius: 16rpx; + overflow: hidden; +} + +.menu-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 30rpx; + border-bottom: 1rpx solid #f5f5f5; + background: #fff; +} + +.menu-item:last-child { + border-bottom: none; +} + +/* 分享按钮样式重置 */ +.menu-btn { + all: unset; + +} + +.menu-btn::after { + border: none; +} + +.menu-left { + display: flex; + align-items: center; +} + +.menu-icon { + width: 44rpx; + height: 44rpx; + margin-right: 20rpx; +} + +.menu-icon-emoji { + font-size: 40rpx; + margin-right: 20rpx; +} + +.menu-text { + font-size: 30rpx; + color: #333; +} + +.menu-arrow { + font-size: 32rpx; + color: #ccc; +} + +.menu-extra { + font-size: 26rpx; + color: #4CAF50; +} + +/* 版权信息 */ +.copyright { + text-align: center; + padding: 40rpx 0; +} + +.copyright text { + font-size: 24rpx; + color: #ccc; +} diff --git a/pages/search/search.js b/pages/search/search.js new file mode 100644 index 0000000..b285f9d --- /dev/null +++ b/pages/search/search.js @@ -0,0 +1,160 @@ +import { injectPage } from '@jdmini/api' +const { searchWorksheets } = require('../../utils/api.js') +const { DATA_BASE_URL } = require('../../utils/config.js') + +Page(injectPage({})({ + data: { + dataBaseUrl: DATA_BASE_URL, + keyword: '', + history: [], + hotKeywords: ['字母', '数字', '涂色', '迷宫', '折纸', '形状'], + worksheets: [], + page: 1, + pageSize: 20, + total: 0, + hasMore: true, + loading: false, + hasSearched: false + }, + + onLoad() { + this.loadHistory() + }, + + // 加载搜索历史 + loadHistory() { + try { + const history = wx.getStorageSync('searchHistory') || [] + this.setData({ history }) + } catch (error) { + console.error('加载搜索历史失败:', error) + } + }, + + // 保存搜索历史 + saveHistory(keyword) { + try { + let history = wx.getStorageSync('searchHistory') || [] + + // 去重 + history = history.filter(h => h !== keyword) + // 添加到开头 + history.unshift(keyword) + // 最多保存10条 + if (history.length > 10) { + history = history.slice(0, 10) + } + + wx.setStorageSync('searchHistory', history) + this.setData({ history }) + } catch (error) { + console.error('保存搜索历史失败:', error) + } + }, + + // 输入事件 + onInput(e) { + this.setData({ + keyword: e.detail.value + }) + }, + + // 清除关键词 + clearKeyword() { + this.setData({ + keyword: '', + hasSearched: false, + worksheets: [] + }) + }, + + // 执行搜索 + async doSearch() { + const keyword = this.data.keyword.trim() + if (!keyword) return + + // 保存搜索历史 + this.saveHistory(keyword) + + this.setData({ + page: 1, + hasMore: true, + worksheets: [], + hasSearched: true + }) + + await this.loadResults() + }, + + // 点击历史或热门关键词搜索 + searchHistory(e) { + const keyword = e.currentTarget.dataset.keyword + this.setData({ keyword }) + this.doSearch() + }, + + // 加载搜索结果 + async loadResults() { + if (this.data.loading) return + + try { + this.setData({ loading: true }) + + const res = await searchWorksheets({ + keyword: this.data.keyword, + page: this.data.page, + pageSize: this.data.pageSize + }) + + if (res.success) { + const newWorksheets = res.data.list || [] + this.setData({ + worksheets: this.data.page === 1 ? newWorksheets : [...this.data.worksheets, ...newWorksheets], + total: res.data.pagination?.total || 0, + hasMore: newWorksheets.length >= this.data.pageSize, + loading: false + }) + } + } catch (error) { + console.error('搜索失败:', error) + this.setData({ loading: false }) + } + }, + + // 加载更多 + async loadMore() { + if (this.data.loading || !this.data.hasMore) return + + this.setData({ + page: this.data.page + 1 + }) + await this.loadResults() + }, + + // 清除历史 + clearHistory() { + wx.showModal({ + title: '提示', + content: '确定清除搜索历史吗?', + success: (res) => { + if (res.confirm) { + wx.removeStorageSync('searchHistory') + this.setData({ history: [] }) + } + } + }) + }, + + // 返回 + goBack() { + wx.navigateBack() + }, + + // 跳转详情 + goDetail(e) { + const id = e.currentTarget.dataset.id + wx.navigateTo({ + url: `/pages/detail/detail?id=${id}` + }) + } +})) diff --git a/pages/search/search.json b/pages/search/search.json new file mode 100644 index 0000000..ab78991 --- /dev/null +++ b/pages/search/search.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "搜索" +} diff --git a/pages/search/search.wxml b/pages/search/search.wxml new file mode 100644 index 0000000..beaaf63 --- /dev/null +++ b/pages/search/search.wxml @@ -0,0 +1,92 @@ + + + + + + + + + + 取消 + + + + + + 搜索历史 + + + + + {{item}} + + + + + + + + 热门搜索 + + + + {{item}} + + + + + + + + 找到 {{total}} 个相关结果 + + + + + + + {{item.title}} + {{item.e_title}} + {{item.category_name}} + + + + + + + 加载中... + 已加载全部 + 加载更多 + + + + + + 未找到相关练习表 + 换个关键词试试吧 + + + diff --git a/pages/search/search.wxss b/pages/search/search.wxss new file mode 100644 index 0000000..a3c3ee9 --- /dev/null +++ b/pages/search/search.wxss @@ -0,0 +1,192 @@ +/* 搜索页面样式 */ +.container { + min-height: 100vh; + background: #f5f5f5; +} + +/* 搜索栏 */ +.search-bar { + display: flex; + align-items: center; + padding: 20rpx 30rpx; + background: #fff; +} + +.search-input-wrap { + flex: 1; + display: flex; + align-items: center; + padding: 16rpx 24rpx; + background: #f5f5f5; + border-radius: 40rpx; +} + +.search-icon { + width: 36rpx; + height: 36rpx; + margin-right: 16rpx; + opacity: 0.5; +} + +.search-input { + flex: 1; + font-size: 28rpx; + color: #333; +} + +.clear-icon { + width: 32rpx; + height: 32rpx; + margin-left: 16rpx; + opacity: 0.5; +} + +.cancel-btn { + margin-left: 20rpx; + font-size: 28rpx; + color: #4CAF50; +} + +/* 搜索历史 */ +.history-section, +.hot-section { + background: #fff; + padding: 30rpx; + margin-top: 20rpx; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20rpx; +} + +.section-title { + font-size: 30rpx; + font-weight: bold; + color: #333; +} + +.delete-icon { + width: 36rpx; + height: 36rpx; + opacity: 0.5; +} + +.history-tags, +.hot-tags { + display: flex; + flex-wrap: wrap; +} + +.history-tag, +.hot-tag { + padding: 12rpx 24rpx; + margin: 0 16rpx 16rpx 0; + background: #f5f5f5; + border-radius: 30rpx; + font-size: 26rpx; + color: #666; +} + +.hot-tag { + background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(139, 195, 74, 0.1)); + color: #4CAF50; +} + +/* 搜索结果 */ +.result-section { + padding: 20rpx 30rpx; +} + +.result-header { + margin-bottom: 20rpx; +} + +.result-header text { + font-size: 26rpx; + color: #999; +} + +.worksheet-list { + background: #fff; + border-radius: 16rpx; + overflow: hidden; +} + +.worksheet-item { + display: flex; + padding: 20rpx; + border-bottom: 1rpx solid #f5f5f5; +} + +.worksheet-item:last-child { + border-bottom: none; +} + +.item-cover { + width: 180rpx; + height: 135rpx; + border-radius: 12rpx; + background: #f5f5f5; + flex-shrink: 0; +} + +.item-info { + flex: 1; + margin-left: 20rpx; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.item-title { + font-size: 28rpx; + color: #333; + line-height: 1.4; +} + +.item-e-title { + font-size: 24rpx; + color: #999; +} + +.item-category { + font-size: 22rpx; + color: #4CAF50; +} + +/* 加载状态 */ +.load-status { + text-align: center; + padding: 30rpx 0; + font-size: 26rpx; + color: #999; +} + +/* 无结果 */ +.empty-result { + display: flex; + flex-direction: column; + align-items: center; + padding: 100rpx 0; +} + +.empty-img { + width: 200rpx; + height: 200rpx; + margin-bottom: 30rpx; + opacity: 0.5; +} + +.empty-text { + font-size: 30rpx; + color: #333; + margin-bottom: 12rpx; +} + +.empty-tip { + font-size: 26rpx; + color: #999; +} diff --git a/project.config.json b/project.config.json new file mode 100644 index 0000000..960d67c --- /dev/null +++ b/project.config.json @@ -0,0 +1,41 @@ +{ + "compileType": "miniprogram", + "libVersion": "3.10.1", + "packOptions": { + "ignore": [], + "include": [] + }, + "setting": { + "coverView": true, + "es6": true, + "postcss": true, + "minified": true, + "enhance": true, + "showShadowRootInWxmlPanel": true, + "packNpmRelationList": [], + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "compileWorklet": false, + "uglifyFileName": false, + "uploadWithSourceMap": true, + "packNpmManually": false, + "minifyWXSS": true, + "minifyWXML": true, + "localPlugins": false, + "condition": false, + "swc": false, + "disableSWC": true, + "disableUseStrict": false, + "useCompilerPlugins": false + }, + "condition": {}, + "editorSetting": { + "tabIndent": "auto", + "tabSize": 2 + }, + "appid": "wxe4ef1cc6e75de032", + "simulatorPluginLibVersion": {} +} \ No newline at end of file diff --git a/project.private.config.json b/project.private.config.json new file mode 100644 index 0000000..ea80ccd --- /dev/null +++ b/project.private.config.json @@ -0,0 +1,24 @@ +{ + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "projectname": "miniapp", + "setting": { + "compileHotReLoad": true, + "urlCheck": false, + "coverView": true, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": false, + "preloadBackgroundData": false, + "autoAudits": false, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": true, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "bigPackageSizeSupport": false, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true + }, + "libVersion": "3.10.1", + "condition": {} +} \ No newline at end of file diff --git a/sitemap.json b/sitemap.json new file mode 100644 index 0000000..ca02add --- /dev/null +++ b/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} \ No newline at end of file diff --git a/utils/api.js b/utils/api.js new file mode 100644 index 0000000..d171331 --- /dev/null +++ b/utils/api.js @@ -0,0 +1,140 @@ +/** + * API 接口定义 + * 所有业务 API 接口的统一管理 + */ + +const { get, post } = require('./request.js') + +/** + * =============================== + * 首页相关接口 + * =============================== + */ + +/** + * 获取首页数据(分类 + 最新工作表) + */ +function getHomeData() { + return get('/api/home') +} + +/** + * 获取所有分类 + */ +function getCategories() { + return get('/api/categories') +} + +/** + * =============================== + * 工作表相关接口 + * =============================== + */ + +/** + * 获取工作表列表 + * @param {Object} params + * @param {Number} params.category_id - 分类ID(可选) + * @param {Number} params.page - 页码 + * @param {Number} params.pageSize - 每页数量 + * @param {String} params.keyword - 搜索关键词 + */ +function getWorksheets(params = {}) { + return get('/api/worksheets', params) +} + +/** + * 获取工作表详情 + * @param {Number} id - 工作表ID + */ +function getWorksheetDetail(id) { + return get(`/api/worksheets/${id}`) +} + +/** + * 记录下载次数 + * @param {Number} id - 工作表ID + */ +function recordDownload(id) { + return post(`/api/worksheets/${id}/download`) +} + +/** + * 搜索工作表 + * @param {Object} params + * @param {String} params.keyword - 搜索关键词 + * @param {Number} params.page - 页码 + * @param {Number} params.pageSize - 每页数量 + */ +function searchWorksheets(params = {}) { + return get('/api/search', params) +} + +/** + * =============================== + * 用户相关接口 + * =============================== + */ + +/** + * 用户登录/注册 + * @param {String} openid - 微信 openid + * @param {String} nickname - 昵称 + * @param {String} avatar - 头像URL + * @param {Number} inviter_id - 邀请人ID(可选) + */ +function userLogin(openid, nickname, avatar, inviter_id) { + return post('/api/user/login', { openid, nickname, avatar, inviter_id }) +} + +/** + * 获取用户信息 + * @param {Number} user_id - 用户ID + */ +function getUserInfo(user_id) { + return get('/api/user/info', { user_id }) +} + +/** + * 检查积分是否足够下载 + * @param {Number} user_id - 用户ID + */ +function checkPoints(user_id) { + return get('/api/user/check-points', { user_id }) +} + +/** + * 扣除积分(下载时调用) + * @param {Number} user_id - 用户ID + * @param {Number} worksheet_id - 工作表ID + */ +function deductPoints(user_id, worksheet_id) { + return post('/api/user/deduct-points', { user_id, worksheet_id }) +} + +/** + * 获取积分记录 + * @param {Number} user_id - 用户ID + * @param {Number} page - 页码 + * @param {Number} pageSize - 每页数量 + */ +function getPointsLog(user_id, page = 1, pageSize = 20) { + return get('/api/user/points-log', { user_id, page, pageSize }) +} + +module.exports = { + // 首页 + getHomeData, + getCategories, + // 工作表 + getWorksheets, + getWorksheetDetail, + recordDownload, + searchWorksheets, + // 用户 + userLogin, + getUserInfo, + checkPoints, + deductPoints, + getPointsLog +} diff --git a/utils/auth.js b/utils/auth.js new file mode 100644 index 0000000..4d6437b --- /dev/null +++ b/utils/auth.js @@ -0,0 +1,269 @@ +/** + * 用户认证工具模块 + * 统一管理登录状态检查、登录跳转等 + */ + +/** + * 检查用户是否已登录 + * @returns {Boolean} 是否已登录 + */ +function isLoggedIn() { + const userId = wx.getStorageSync('userId') + const token = wx.getStorageSync('token') + return !!(userId && token) +} + +/** + * 获取用户信息 + * @returns {Object|null} 用户信息对象或null + */ +function getUserInfo() { + if (!isLoggedIn()) { + return null + } + + const userInfo = wx.getStorageSync('userInfo') + return userInfo || null +} + +/** + * 获取用户ID + * @returns {String|null} 用户ID或null + */ +function getUserId() { + return wx.getStorageSync('userId') || null +} + +/** + * 获取Token + * @returns {String|null} Token或null + */ +function getToken() { + return wx.getStorageSync('token') || null +} + +/** + * 检查登录状态,未登录则跳转登录页 + * @param {Object} options 配置项 + * @param {String} options.returnUrl 登录成功后返回的页面URL + * @param {String} options.returnType 返回类型: navigateTo/redirectTo/switchTab + * @param {Function} options.success 已登录时的回调 + * @param {Function} options.fail 未登录时的回调 + * @returns {Boolean} 是否已登录 + */ +function checkLogin(options = {}) { + const { + returnUrl = '', + returnType = 'navigateTo', + success, + fail + } = options + + if (isLoggedIn()) { + // 已登录 + success && success() + return true + } else { + // 未登录,跳转登录页 + fail && fail() + + // 保存返回信息 + if (returnUrl) { + wx.setStorageSync('returnTo', { + url: returnUrl, + type: returnType + }) + } + + wx.navigateTo({ + url: '/pages/login/login' + }) + + return false + } +} + +/** + * 要求登录后执行操作 + * @param {Function} callback 登录后执行的回调 + * @param {Object} options 配置项 + */ +function requireLogin(callback, options = {}) { + return checkLogin({ + ...options, + success: callback, + fail: () => { + wx.showToast({ + title: '请先登录', + icon: 'none', + duration: 2000 + }) + } + }) +} + +/** + * 保存用户信息到本地 + * @param {Object} data 用户数据 + * @param {String} data.userId 用户ID + * @param {String} data.token Token + * @param {Object} data.userInfo 用户信息 + */ +function saveUserData(data) { + const { userId, token, userInfo } = data + + if (userId) { + wx.setStorageSync('userId', userId) + } + + if (token) { + wx.setStorageSync('token', token) + } + + if (userInfo) { + wx.setStorageSync('userInfo', userInfo) + } +} + +/** + * 清除用户登录信息 + */ +function clearUserData() { + wx.removeStorageSync('userId') + wx.removeStorageSync('token') + wx.removeStorageSync('userInfo') +} + +/** + * 跳转到登录页面(带返回功能) + * 参考 checkin.js 的实现,显示弹窗确认后跳转登录页 + * @param {Object} options 配置项 + * @param {String} options.message 提示信息 + * @param {String} options.returnUrl 登录成功后返回的页面URL(不传则自动获取当前页面) + * @param {String} options.returnType 返回类型: navigateTo/redirectTo/switchTab + * @param {Boolean} options.showCancel 是否显示取消按钮 + * @param {Function} options.onCancel 取消回调 + */ +function navigateToLogin(options = {}) { + const { + message = '请先登录', + returnUrl = '', + returnType = 'navigateTo', + showCancel = true, + onCancel = null + } = options + + // 自动获取当前页面路径 + let finalReturnUrl = returnUrl + if (!finalReturnUrl) { + const pages = getCurrentPages() + if (pages.length > 0) { + const currentPage = pages[pages.length - 1] + finalReturnUrl = '/' + currentPage.route + // 添加页面参数 + if (currentPage.options && Object.keys(currentPage.options).length > 0) { + const params = Object.keys(currentPage.options) + .map(key => `${key}=${currentPage.options[key]}`) + .join('&') + finalReturnUrl += '?' + params + } + } + } + + wx.showModal({ + title: '提示', + content: message, + confirmText: '去登录', + cancelText: '返回', + showCancel: showCancel, + success: (res) => { + if (res.confirm) { + // 保存返回信息,登录成功后返回当前页面 + if (finalReturnUrl) { + wx.setStorageSync('returnTo', { + type: returnType, + url: finalReturnUrl + }) + } + wx.redirectTo({ + url: '/pages/login/login' + }) + } else { + // 点击取消 + if (onCancel) { + onCancel() + } + } + } + }) +} + +/** + * 退出登录 + * @param {Object} options 配置项 + * @param {String} options.redirectUrl 退出后跳转的页面 + * @param {Boolean} options.confirm 是否需要确认 + * @param {Function} options.success 成功回调 + */ +function logout(options = {}) { + const { + redirectUrl = '/pages/home/home', + confirm = true, + success + } = options + + const doLogout = () => { + clearUserData() + + wx.showToast({ + title: '已退出登录', + icon: 'success', + duration: 1500 + }) + + setTimeout(() => { + success && success() + + // 跳转到指定页面 + if (redirectUrl.startsWith('/pages/home/') || + redirectUrl.startsWith('/pages/index/') || + redirectUrl.startsWith('/pages/record/') || + redirectUrl.startsWith('/pages/settings/')) { + wx.reLaunch({ + url: redirectUrl + }) + } else { + wx.redirectTo({ + url: redirectUrl + }) + } + }, 1500) + } + + if (confirm) { + wx.showModal({ + title: '退出登录', + content: '确定要退出登录吗?', + success: (res) => { + if (res.confirm) { + doLogout() + } + } + }) + } else { + doLogout() + } +} + +module.exports = { + isLoggedIn, + getUserInfo, + getUserId, + getToken, + checkLogin, + requireLogin, + navigateToLogin, + saveUserData, + clearUserData, + logout +} diff --git a/utils/config.js b/utils/config.js new file mode 100644 index 0000000..bb33170 --- /dev/null +++ b/utils/config.js @@ -0,0 +1,40 @@ +/** + * API 配置文件 + * 统一管理开发环境和生产环境的配置 + */ + +// 开发模式开关 +const IS_DEV = false + +// 开发环境配置 +const DEV_CONFIG = { + apiBase: 'http://localhost:3001', + timeout: 30000, + enableLog: true +} + +// 生产环境配置 +const PROD_CONFIG = { + apiBase: '/mp/jd-youerqimeng', // 幼儿启蒙模块 + timeout: 30000, + enableLog: false +} + +// 当前环境配置 +const CONFIG = IS_DEV ? DEV_CONFIG : PROD_CONFIG + +// 数据资源地址前缀 +const DATA_BASE_URL = 'https://pic.miniappapi.com/youerqimeng' + +module.exports = { + IS_DEV, + API_BASE: CONFIG.apiBase, + DATA_BASE_URL, + TIMEOUT: CONFIG.timeout, + ENABLE_LOG: CONFIG.enableLog, + + // 切换环境方法(用于调试) + switchEnv: (isDev) => { + return isDev ? DEV_CONFIG : PROD_CONFIG + } +} diff --git a/utils/httpClient.js b/utils/httpClient.js new file mode 100644 index 0000000..36c1ddc --- /dev/null +++ b/utils/httpClient.js @@ -0,0 +1,88 @@ +/** + * 统一的 HTTP 客户端 + * 自动根据环境切换使用 gatewayHttpClient 或 HttpClient + */ + +import { gatewayHttpClient, HttpClient } from '@jdmini/api' +const { IS_DEV, API_BASE } = require('./config.js') + +// 开发环境使用 HttpClient +const devHttpClient = IS_DEV ? new HttpClient({ + baseURL: API_BASE, + timeout: 30000, +}) : null + +/** + * 统一的 HTTP 请求方法 + * @param {String} url - 请求路径(相对路径,不包含 baseURL) + * @param {String} method - 请求方法 GET/POST/PUT/DELETE + * @param {Object} data - 请求数据 + * @param {Object} options - 额外选项 + * @returns {Promise} + */ +function request(url, method = 'GET', data = {}, options = {}) { + // 确保 URL 以 / 开头 + const apiUrl = url.startsWith('/') ? url : `/${url}` + + if (IS_DEV) { + // 开发环境:使用 HttpClient + console.log(`[DEV] ${method} ${API_BASE}${apiUrl}`, data) + return devHttpClient.request(apiUrl, method, data, options) + } else { + // 生产环境:使用 gatewayHttpClient + // gatewayHttpClient 需要完整的路径:mp/jd-haiba/user/login + const fullUrl = `${API_BASE}${apiUrl}` + console.log(`[PROD] ${method} ${fullUrl}`, data) + + // gatewayHttpClient.request 的参数顺序:(url, method, data, options) + return gatewayHttpClient.request(fullUrl, method, data, options) + } +} + +/** + * GET 请求 + */ +function get(url, params = {}, options = {}) { + return request(url, 'GET', params, options) +} + +/** + * POST 请求 + */ +function post(url, data = {}, options = {}) { + return request(url, 'POST', data, options) +} + +/** + * PUT 请求 + */ +function put(url, data = {}, options = {}) { + return request(url, 'PUT', data, options) +} + +/** + * DELETE 请求 + */ +function del(url, data = {}, options = {}) { + return request(url, 'DELETE', data, options) +} + +/** + * 上传文件(暂时保留原有逻辑) + */ +function upload(filePath, formData = {}) { + return gatewayHttpClient.uploadFile(filePath,formData) +} + +module.exports = { + request, + get, + post, + put, + del, + upload, + + // 导出原始客户端供特殊场景使用 + gatewayHttpClient, + devHttpClient +} diff --git a/utils/index.js b/utils/index.js new file mode 100644 index 0000000..4922715 --- /dev/null +++ b/utils/index.js @@ -0,0 +1,27 @@ +/** + * Utils 统一导出 + * 方便各个页面统一引入 + */ + +const config = require('./config.js') +const request = require('./request.js') +const api = require('./api.js') +const auth = require('./auth.js') +const httpClient = require('./httpClient.js') + +module.exports = { + // 配置 + ...config, + + // 请求方法 + ...request, + + // API接口 + ...api, + + // 认证 + ...auth, + + // 统一 HTTP 客户端 + httpClient +} diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..7d8a16b --- /dev/null +++ b/utils/request.js @@ -0,0 +1,278 @@ +/** + * 统一请求模块 + * 封装所有 API 请求,支持拦截器、错误处理等 + */ + +const { API_BASE, TIMEOUT, ENABLE_LOG, IS_DEV } = require('./config.js') +const httpClient = require('./httpClient.js') + +/** + * 统一请求方法 + * @param {Object} options 请求配置 + * @param {String} options.url 请求路径(相对路径,会自动拼接 API_BASE) + * @param {String} options.method 请求方法 GET/POST/PUT/DELETE + * @param {Object} options.data 请求数据 + * @param {Object} options.header 自定义请求头 + * @param {Boolean} options.showLoading 是否显示加载提示 + * @param {String} options.loadingText 加载提示文字 + * @param {Boolean} options.showError 是否显示错误提示 + * @returns {Promise} + */ +function request(options = {}) { + const { + url, + method = 'GET', + data = {}, + header = {}, + showLoading = false, + loadingText = '加载中...', + showError = true + } = options + + // 显示加载提示 + if (showLoading) { + wx.showLoading({ + title: loadingText, + mask: true + }) + } + + // 拼接完整URL + const fullUrl = url.startsWith('http') ? url : `${API_BASE}${url}` + + // 构建请求头 + const requestHeader = { + 'Content-Type': 'application/json', + ...header + } + + // 添加 token(如果存在) + const token = wx.getStorageSync('token') + if (token) { + requestHeader['Authorization'] = `Bearer ${token}` + } + + // 日志输出 + if (ENABLE_LOG) { + console.log('[API Request]', { + url: fullUrl, + method, + data, + header: requestHeader + }) + } + + return new Promise((resolve, reject) => { + wx.request({ + url: fullUrl, + method, + data, + header: requestHeader, + timeout: TIMEOUT, + success: (res) => { + // 隐藏加载提示 + if (showLoading) { + wx.hideLoading() + } + + // 日志输出 + if (ENABLE_LOG) { + console.log('[API Response]', res.data) + } + + // 统一处理响应 + const { statusCode, data: resData } = res + + // HTTP 状态码检查 + if (statusCode >= 200 && statusCode < 300) { + // 业务状态码检查 + if (resData.code === 200 || resData.success === true) { + resolve(resData) + } else { + // 业务错误 + const errorMsg = resData.msg || resData.message || '请求失败' + if (showError) { + wx.showToast({ + title: errorMsg, + icon: 'none', + duration: 2000 + }) + } + reject({ + code: resData.code, + msg: errorMsg, + data: resData + }) + } + } else { + // HTTP 错误 + const errorMsg = `服务器错误 (${statusCode})` + if (showError) { + wx.showToast({ + title: errorMsg, + icon: 'none', + duration: 2000 + }) + } + reject({ + code: statusCode, + msg: errorMsg, + data: res + }) + } + }, + fail: (err) => { + // 隐藏加载提示 + if (showLoading) { + wx.hideLoading() + } + + // 日志输出 + if (ENABLE_LOG) { + console.error('[API Error]', err) + } + + // 网络错误 + const errorMsg = err.errMsg || '网络请求失败' + if (showError) { + wx.showToast({ + title: errorMsg, + icon: 'none', + duration: 2000 + }) + } + + reject({ + code: -1, + msg: errorMsg, + data: err + }) + } + }) + }) +} + +/** + * GET 请求 + */ +function get(url, data = {}, options = {}) { + // 直接使用 httpClient,它会自动处理环境切换 + return httpClient.get(url, data, options) +} + +/** + * POST 请求 + */ +function post(url, data = {}, options = {}) { + // 直接使用 httpClient,它会自动处理环境切换 + return httpClient.post(url, data, options) +} + +/** + * PUT 请求 + */ +function put(url, data = {}, options = {}) { + // 直接使用 httpClient,它会自动处理环境切换 + return httpClient.put(url, data, options) +} + +/** + * DELETE 请求 + */ +function del(url, data = {}, options = {}) { + // 直接使用 httpClient,它会自动处理环境切换 + return httpClient.del(url, data, options) +} + +/** + * 上传文件 + * @param {String} url 上传地址 + * @param {String} filePath 文件路径 + * @param {Object} formData 额外的表单数据 + * @param {Object} options 其他配置 + */ +function uploadFile(filePath, formData = {}) { + const { + name = 'file', + showLoading = true, + loadingText = '上传中...', + showError = true + } = options + + if (showLoading) { + wx.showLoading({ + title: loadingText, + mask: true + }) + } + + const fullUrl = url.startsWith('http') ? url : `${API_BASE}${url}` + + // 构建请求头 + const header = {} + const token = wx.getStorageSync('token') + if (token) { + header['Authorization'] = `Bearer ${token}` + } + + return new Promise((resolve, reject) => { + wx.uploadFile({ + url: fullUrl, + filePath, + name, + formData, + header, + timeout: TIMEOUT, + success: (res) => { + if (showLoading) { + wx.hideLoading() + } + + const data = JSON.parse(res.data) + if (data.code === 200 || data.success === true) { + resolve(data) + } else { + const errorMsg = data.msg || data.message || '上传失败' + if (showError) { + wx.showToast({ + title: errorMsg, + icon: 'none' + }) + } + reject({ + code: data.code, + msg: errorMsg, + data + }) + } + }, + fail: (err) => { + if (showLoading) { + wx.hideLoading() + } + + const errorMsg = err.errMsg || '上传失败' + if (showError) { + wx.showToast({ + title: errorMsg, + icon: 'none' + }) + } + reject({ + code: -1, + msg: errorMsg, + data: err + }) + } + }) + }) +} + +module.exports = { + request, + get, + post, + put, + del, + uploadFile +}