first commit

This commit is contained in:
honghefly 2025-11-20 16:42:59 +08:00
commit c6d7af9ae4
165 changed files with 18223 additions and 0 deletions

63
ICONS_README.md Normal file
View File

@ -0,0 +1,63 @@
# 小程序图标说明
## ✅ 图标已创建
所有必需的图标文件已经创建在 `miniapp/images/` 目录中:
### TabBar图标
| 文件名 | 用途 | 颜色 |
|--------|------|------|
| home.png | 首页图标(未选中) | #999999 |
| home-active.png | 首页图标(选中) | #07c160 |
| stats.png | 统计图标(未选中) | #999999 |
| stats-active.png | 统计图标(选中) | #07c160 |
| profile.png | 个人中心(未选中) | #999999 |
| profile-active.png | 个人中心(选中) | #07c160 |
### 其他图标
| 文件名 | 用途 |
|--------|------|
| default-avatar.png | 默认头像 |
| empty.png | 空状态图片 |
## 🎨 生成真实PNG图标
当前的PNG文件是占位符1x1像素可以让小程序正常运行。如需生成真实的图标
### 方法1使用HTML生成器
1. 在浏览器中打开 `miniapp/generate-icons.html`
2. 点击"下载所有图标"按钮
3. 将下载的PNG文件覆盖到 `images/` 目录
### 方法2使用SVG文件
- `images/` 目录中包含了对应的SVG文件
- 可以使用在线工具(如 https://cloudconvert.com/svg-to-png转换为PNG
### 方法3使用图标库
推荐使用以下图标:
- 🏠 首页: home, house
- 📊 统计: chart-bar, statistics
- 👤 个人: user, person
## 📏 图标规范
- **尺寸**: 81x81 像素(微信小程序推荐)
- **格式**: PNG支持透明背景
- **颜色**:
- 未选中: #999999
- 选中状态: #07c160(微信绿)
## 🔧 自定义图标
如需替换为自定义图标:
1. 准备81x81像素的PNG图片
2. 命名规则:`{name}.png` 和 `{name}-active.png`
3. 将文件放入 `images/` 目录
4. 重新编译小程序
## 💡 提示
- 图标文件很小,不会影响小程序性能
- 建议使用简洁的线性图标
- 保持图标风格统一
- 可以使用 iconfont 或其他图标网站下载

172
MINIAPP_README.md Normal file
View File

@ -0,0 +1,172 @@
# 打牌记账小程序 - 使用说明
## 🚀 快速开始
### 1. 环境准备
- 微信开发者工具
- 后端服务已启动http://localhost:3000
### 2. 导入项目
1. 打开微信开发者工具
2. 导入项目,选择 `miniapp` 目录
3. AppID 使用测试号或实际AppID
### 3. 安装依赖
```bash
cd miniapp
npm install
```
在微信开发者工具中:
- 工具 > 构建 npm
### 4. 修改配置
如果后端不在localhost:3000修改
- `app.js` 中的 `apiBase`
- `utils/request.js` 中的 `API_BASE`
## 📱 功能说明
### 已实现功能
1. **用户登录** - 首页点击登录按钮
2. **首页展示**
- 用户信息卡片
- 快速操作入口
- 进行中的牌局列表
### 核心页面
| 页面 | 路径 | 功能 |
|------|------|------|
| 首页 | pages/index/index | 登录、快速操作、牌局列表 |
| 创建牌局 | pages/game/create/create | 创建新牌局 |
| 加入牌局 | pages/game/join/join | 输入邀请码加入 |
| 牌局详情 | pages/game/detail/detail | 查看牌局信息 |
| 游戏进行 | pages/game/play/play | 记账功能 |
| 个人统计 | pages/stats/personal/personal | 战绩统计 |
| 个人中心 | pages/profile/index/index | 个人信息 |
## 🎮 使用流程
### 1. 登录流程
```
打开小程序 → 首页 → 点击登录 → 授权用户信息 → 登录成功
```
### 2. 创建牌局
```
首页 → 创建牌局 → 填写信息 → 生成邀请码 → 分享给朋友
```
### 3. 加入牌局
```
首页 → 加入牌局 → 输入邀请码 → 加入成功 → 进入牌局
```
### 4. 记账流程
```
牌局详情 → 开始游戏 → 记一笔 → 输入输赢金额 → 保存
```
## 🔧 API调用示例
### 登录
```javascript
const app = getApp()
await app.login()
```
### 创建牌局
```javascript
import request from '../../utils/request'
const data = await request.post('/sessions/create', {
session_name: '周五麻将',
game_type: 'mahjong',
base_score: 100,
player_count: 4
})
```
### 记录游戏
```javascript
const data = await request.post('/records/add', {
session_id: 1,
dealer_id: 1,
player_records: [
{ player_id: 1, score_change: 300 },
{ player_id: 2, score_change: -100 },
{ player_id: 3, score_change: -100 },
{ player_id: 4, score_change: -100 }
],
notes: '张三自摸'
})
```
## 📝 注意事项
1. **登录状态** - Token保存在Storage24小时有效
2. **数据刷新** - onShow时自动刷新数据
3. **错误处理** - 网络错误自动提示
4. **金额单位** - 后端使用分,前端显示元
## 🐛 常见问题
### 1. 无法登录
- 检查后端服务是否启动
- 检查网络连接
- 清除缓存重试
### 2. 请求失败
- 检查API地址配置
- 查看控制台错误信息
- 验证Token是否过期
### 3. 页面空白
- 重新构建npm
- 检查页面路径配置
- 查看编译错误
## 📂 项目结构
```
miniapp/
├── pages/ # 页面文件
│ ├── index/ # 首页
│ ├── game/ # 游戏相关
│ │ ├── create/ # 创建牌局
│ │ ├── join/ # 加入牌局
│ │ ├── detail/ # 牌局详情
│ │ └── play/ # 游戏进行
│ ├── stats/ # 统计相关
│ └── profile/ # 个人中心
├── utils/ # 工具函数
│ ├── request.js # 请求封装
│ └── format.js # 格式化工具
├── images/ # 图片资源
├── app.js # 应用入口
├── app.json # 应用配置
└── app.wxss # 全局样式
```
## 🎯 下一步开发
### 待实现功能
1. 实时同步WebSocket
2. 结算功能
3. 数据图表展示
4. 语音记账
5. 拍照记录
6. 导出报表
### 优化建议
1. 添加骨架屏
2. 优化加载动画
3. 添加下拉刷新
4. 缓存优化
5. 错误重试机制
## 📞 技术支持
如需帮助,请参考:
- [后端README](../BACKEND_README.md)
- [系统架构文档](../SYSTEM_ARCHITECTURE.md)
- [API设计文档](../API_DESIGN.md)

253
README.md Normal file
View File

@ -0,0 +1,253 @@
## 安装/更新
1、终端使用命令安装 npm 包
```bash
npm install --save @jdwx/api@latest
```
> 一般会和[jdwx-components](https://code.miniappapi.com/wx/jdwx-components)一同搭配使用。安装:
> ```bash
> npm install --save @jdwx/components@latest
> ```
2、在小程序开发者工具中菜单选择工具 -> 构建 npm
## 使用
```js
import {
onLoginReady,
waitLogin,
injectApp,
injectPage,
injectComponent,
hijackApp,
hijackAllPage,
gatewayHttpClient,
baseHttpClient,
apiHttpClient,
HttpClient,
adManager,
} from '@jdwx/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, dataoptions)
// 头像上传。参数:文件路径
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) => {
})
}
})
```

363
THEME_IMPLEMENTATION.md Normal file
View File

@ -0,0 +1,363 @@
# Miniapp3 黑金奢华主题 - 实施完成报告
## 🎨 主题概览
**主题名称**: 黑金奢华 (Black & Gold Luxury)
**设计风格**: 高端、奢华、精致
**核心理念**: 通过黑色基调和金色点缀营造尊贵感
---
## 🌟 核心配色方案
### 主色系
- **金色主调**: `#D4AF37``#F4E6C3` (渐变)
- **金色深色**: `#B8941F``#9A7B1A`
- **银色辅助**: `#C0C0C0``#E5E5E5`
### 背景色系
- **纯黑背景**: `#000000``#0A0A0A`
- **卡片背景**: `#1A1A1A``#252525`
- **深色层级**: `#121212`, `#2A2A2A`, `#3A3A3A`
### 文字色系
- **主文本**: `#FFFFFF` (纯白)
- **次级文本**: `#E0E0E0`
- **辅助文本**: `#B0B0B0`
- **禁用文本**: `#606060`
- **金色文本**: `#F4E6C3` (用于标题)
### 状态色
- **成功**: `#50C878` (翡翠绿)
- **警告**: `#FFD700` (金黄)
- **错误**: `#DC143C` (深红)
- **信息**: `#4682B4` (钢蓝)
---
## 📁 已完成的文件更新
### 1. 核心配置文件
#### [styles/theme.wxss](styles/theme.wxss)
完整的设计系统配置,包含:
- CSS 变量定义(颜色、阴影、圆角、间距)
- 渐变样式类(金色、银色、黑色)
- 文字颜色工具类
- 背景颜色工具类
- 阴影系统(带金色光晕)
- 边框样式(包括金色渐变边框)
- 按钮和卡片组件样式
- 徽章系统
- 特殊效果(金属质感、发光、玻璃态)
- 动画效果(淡入、滑入、发光脉冲、金属闪光)
- 响应式字体系统
- 实用工具类
#### [app.wxss](app.wxss)
全局样式文件,包含:
- **按钮系统**: primary (金色), secondary (银色), success, warning, danger, ghost
- **卡片系统**: 标准卡片、金色装饰卡片、高亮卡片
- **游戏相关**: 胜利、失败、平局样式
- **列表样式**: 带悬停效果的列表项
- **头像系统**: 4 种尺寸,金色边框,带发光效果
- **徽章标签**: 金色、银色、成功、警告、危险
- **模态框**: 带金色边框的弹窗系统
- **空状态**: 统一的空状态展示
- **加载状态**: 金色加载指示器
- **底部操作栏**: 固定底栏,金色顶边
- **排名徽章**: 金银铜三色渐变徽章
- **分割线**: 普通分割线和金色渐变分割线
- **实用工具类**: margin, padding, text-align
### 2. 组件样式
#### [components/navbar/navbar.wxss](components/navbar/navbar.wxss)
- 黑色渐变背景 (#1A1A1A → #252525)
- 金色底边装饰
- 深黑阴影
- 返回按钮:金色半透明背景,金色边框
- 标题:浅金色 (#F4E6C3)
#### [components/custom-tabbar/custom-tabbar.wxss](components/custom-tabbar/custom-tabbar.wxss)
- 黑色背景 (#1A1A1A)
- 金色顶边装饰
- 深黑阴影
- 未选中标签:灰色 (#B0B0B0)
- 选中标签:金色 (#D4AF37),带放大和上移效果
#### [components/guide/guide.wxss](components/guide/guide.wxss)
- 遮罩85% 黑色半透明
- 提示卡片:黑色渐变背景,金色边框
- 标题:浅金色 (#F4E6C3)
- 描述:浅灰色 (#E0E0E0)
- 进度点:
- 未激活:深灰 (#3A3A3A)
- 激活:金色渐变,带发光效果
- 按钮:金色渐变,黑色文字
### 3. 页面样式
#### [pages/index/index.wxss](pages/index/index.wxss)
**增强功能**
1. **用户卡片**
- 黑色渐变背景
- 金色边框装饰
- 顶部金色渐变条(带闪光动画)
- 深黑阴影
2. **用户头像**
- 金色边框 (3rpx)
- 金色发光效果
- 呼吸式发光动画 (avatarGlow)
3. **统计数据**
- 金色顶边分割线
- 白色数值文字
- 灰色标签文字
4. **操作按钮**
- 主按钮:绿色渐变,带发光阴影
- 次要按钮:银色渐变,带柔和阴影
- 悬停:阴影加深效果
5. **房间卡片**
- 黑色背景 (#1A1A1A)
- 深黑阴影
- 房主徽章:橙色半透明
- 状态徽章:
- 进行中:翡翠绿
- 等待中:橙色
- 时间文本:灰色
6. **空状态**
- 黑色背景
- 深黑阴影
- 灰色文字
---
## ✨ 关键视觉特征
### 1. 金属质感效果
```css
.gradient-metallic-gold {
background: linear-gradient(135deg,
#B8941F 0%,
#D4AF37 25%,
#F4E6C3 50%,
#D4AF37 75%,
#B8941F 100%
);
background-size: 200% 200%;
animation: shimmer 3s linear infinite;
}
```
### 2. 发光效果
```css
--glow-gold: 0 0 20rpx rgba(212, 175, 55, 0.6),
0 0 40rpx rgba(212, 175, 55, 0.4),
0 0 60rpx rgba(212, 175, 55, 0.2);
```
### 3. 动画系统
- **shimmer** - 金属闪光动画 (4s)
- **avatarGlow** - 头像呼吸发光 (3s)
- **fadeInDark** - 淡入效果
- **slideInGold** - 金色滑入
- **glowPulse** - 发光脉冲
### 4. 阴影系统
```css
--shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.6);
--shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.7);
--shadow-lg: 0 8rpx 32rpx rgba(0, 0, 0, 0.8);
--shadow-gold: 0 4rpx 20rpx rgba(212, 175, 55, 0.4);
--shadow-gold-lg: 0 8rpx 32rpx rgba(212, 175, 55, 0.5);
```
---
## 🎯 设计原则
### 1. 奢华感营造
- **金色点缀**: 边框、图标、高亮文字使用金色
- **黑色基调**: 深黑背景营造高端氛围
- **发光效果**: 金色元素带有柔和发光
- **金属质感**: 渐变和动画模拟金属光泽
### 2. 视觉层次
- **主要内容**: 白色文字 (#FFFFFF)
- **次要内容**: 浅灰文字 (#E0E0E0)
- **辅助内容**: 灰色文字 (#B0B0B0)
- **金色强调**: 标题和重要元素 (#F4E6C3)
### 3. 交互反馈
- **悬停**: transform scale + 阴影变化
- **激活**: 阴影加深 + 轻微缩放
- **动画**: 流畅的 cubic-bezier 过渡
- **发光**: 金色元素呼吸式发光
### 4. 品牌一致性
- **主题色**: 金色代表尊贵
- **辅助色**: 银色代表精致
- **背景色**: 纯黑代表高端
- **强调色**: 翡翠绿代表成功
---
## 📊 更新统计
### 文件修改数量
- **核心配置**: 2 个文件 (theme.wxss, app.wxss)
- **组件样式**: 3 个文件 (navbar, tabbar, guide)
- **页面样式**: 1 个文件 (index.wxss)
- **批量更新**: 21+ 个 wxss 文件
### 样式更新内容
- **颜色替换**: 100+ 处
- 紫色 → 金色/黑色
- 白色背景 → 黑色背景
- 深色文字 → 浅色文字
- **阴影更新**: 50+ 处
- 紫色阴影 → 黑色/金色阴影
- **新增效果**: 20+ 个
- 发光动画
- 金属闪光
- 渐变边框
---
## 🔧 技术实现细节
### 渐变方向统一
所有渐变使用 **135deg** 方向,确保视觉一致性
### 圆角系统
```css
--radius-sm: 8rpx;
--radius-md: 12rpx;
--radius-lg: 16rpx;
--radius-xl: 20rpx;
--radius-full: 9999rpx;
```
### 间距系统
```css
--space-xs: 8rpx;
--space-sm: 12rpx;
--space-md: 16rpx;
--space-lg: 24rpx;
--space-xl: 32rpx;
```
### 动画性能优化
- 使用 `will-change` 提示浏览器优化
- 避免在 `width`/`height` 上使用动画
- 优先使用 `transform``opacity`
- 合理控制动画时长 (2-4s)
---
## 🚀 使用指南
### 1. 应用主题样式
在页面 wxss 中引入主题:
```css
@import "../../styles/theme.wxss";
```
### 2. 使用预定义类
```html
<!-- 按钮 -->
<button class="btn-primary">主按钮</button>
<button class="btn-ghost">幽灵按钮</button>
<!-- 卡片 -->
<view class="card card-gold">
金色装饰卡片
</view>
<!-- 徽章 -->
<view class="badge badge-gold">VIP</view>
<!-- 头像 -->
<image class="avatar avatar-lg" src="..." />
<!-- 文字 -->
<text class="text-gold">金色文字</text>
<text class="text-tertiary">辅助文字</text>
```
### 3. 自定义组件样式
参考 `theme.wxss` 中的 CSS 变量:
```css
.my-component {
background: var(--bg-card);
color: var(--text-primary);
border: 1rpx solid var(--border-gold);
box-shadow: var(--shadow-gold);
border-radius: var(--radius-lg);
}
```
---
## 💡 设计理念对比
| 特征 | Miniapp (紫色) | Miniapp2 (橙色) | Miniapp3 (黑金) |
|------|---------------|----------------|----------------|
| **定位** | 神秘科技 | 清新友好 | 奢华高端 |
| **主色** | #667eea 紫色 | #FF6B35 橙色 | #D4AF37 金色 |
| **背景** | 渐变紫色 | 浅橙白色 | 纯黑色 |
| **风格** | 现代炫酷 | 温暖活泼 | 精致尊贵 |
| **用户群** | 年轻科技爱好者 | 休闲游戏玩家 | 高端用户 |
| **特效** | 霓虹发光 | 柔和阴影 | 金属光泽 |
---
## ✅ 实施检查清单
- [x] 主题配置文件创建完成
- [x] 全局样式文件创建完成
- [x] Navbar 组件金色样式完成
- [x] TabBar 组件金色样式完成
- [x] Guide 组件金色样式完成
- [x] 首页样式增强完成
- [x] 按钮系统金色/银色渐变
- [x] 卡片系统金色装饰
- [x] 文字颜色浅色适配
- [x] 背景颜色深色统一
- [x] 阴影效果深黑/金色
- [x] 发光效果添加
- [x] 动画效果优化
- [x] 响应式适配完成
---
## 📞 后续优化建议
1. **性能优化**
- 减少动画使用频率
- 优化大图片加载
- 使用 CSS containment
2. **无障碍改进**
- 确保颜色对比度 ≥ 4.5:1
- 添加 aria 标签
- 支持高对比度模式
3. **深色模式增强**
- 添加"纯黑"和"深灰"两种模式切换
- 保存用户偏好设置
4. **主题扩展**
- 支持自定义金色色相
- 添加更多预设主题配色
---
**更新日期**: 2025-01-XX
**版本**: v3.0.0
**主题**: 黑金奢华 (Black & Gold Luxury)

432
USER_GUIDE_README.md Normal file
View File

@ -0,0 +1,432 @@
# 新用户引导功能说明
## 功能概述
为首页添加了新用户引导功能,首次访问小程序时会自动显示,帮助用户快速了解核心功能。
---
## 技术实现
### 1. 引导组件 (`components/guide/guide`)
**核心特性**
- ✅ 高亮遮罩层(打洞效果)
- ✅ 自适应位置计算
- ✅ 多步骤引导流程
- ✅ 进度指示器
- ✅ 平滑过渡动画
- ✅ 防止穿透交互
**文件结构**
```
components/guide/
├── guide.wxml # 模板(遮罩 + 高亮 + 提示卡片)
├── guide.wxss # 样式(渐变背景 + 动画)
├── guide.js # 逻辑(位置计算 + 步骤控制)
└── guide.json # 配置
```
### 2. 引导步骤配置
在 [pages/index/index.js](pages/index/index.js) 中定义引导步骤:
```javascript
guideSteps: [
{
selector: '.guide-create-btn', // 目标元素选择器
title: '创建房间', // 标题
description: '点击这里可以...', // 说明文字
position: 'bottom', // 提示框位置top/bottom/left/right
padding: 12, // 高亮区域扩展边距
borderRadius: 16 // 高亮区域圆角
},
// ... 更多步骤
]
```
### 3. 首次访问检测
使用 `wx.getStorageSync('hasShownGuide')` 检测是否已显示过引导:
```javascript
checkAndShowGuide() {
const hasShownGuide = wx.getStorageSync('hasShownGuide')
if (!hasShownGuide) {
// 延迟500ms显示等待页面完全渲染
setTimeout(() => {
this.setData({ showGuide: true })
}, 500)
}
}
```
### 4. 目标元素标记
为需要引导的元素添加唯一的class
```html
<!-- 创建房间按钮 -->
<view class="action-btn primary-btn guide-create-btn" bindtap="createRoom">
<!-- ... -->
</view>
<!-- 加入房间按钮 -->
<view class="action-btn secondary-btn guide-join-btn" bindtap="joinRoom">
<!-- ... -->
</view>
<!-- 扫码按钮 -->
<view class="scan-btn guide-scan-btn" bindtap="scanCode">
<!-- ... -->
</view>
```
---
## 使用方式
### 在其他页面中使用
#### 1. 注册组件
在页面的 `xxx.json` 中:
```json
{
"usingComponents": {
"user-guide": "../../components/guide/guide"
}
}
```
#### 2. 添加模板
在页面的 `xxx.wxml` 中:
```html
<user-guide
show="{{showGuide}}"
steps="{{guideSteps}}"
bind:complete="onGuideComplete"
/>
```
#### 3. 配置步骤
在页面的 `xxx.js` 中:
```javascript
Page({
data: {
showGuide: false,
guideSteps: [
{
selector: '.target-element',
title: '功能标题',
description: '功能说明文字',
position: 'bottom', // 可选top/bottom/left/right
padding: 12, // 可选高亮区域扩展边距默认8
borderRadius: 16, // 可选圆角大小默认16
showArrow: true // 可选是否显示箭头默认true
}
// ... 更多步骤
]
},
onReady() {
// 检查是否需要显示引导
const hasShown = wx.getStorageSync('hasShownGuide_pageName')
if (!hasShown) {
setTimeout(() => {
this.setData({ showGuide: true })
}, 500)
}
},
onGuideComplete() {
this.setData({ showGuide: false })
wx.setStorageSync('hasShownGuide_pageName', true)
}
})
```
---
## 测试方法
### 方法1清除存储测试
在微信开发者工具的控制台中执行:
```javascript
// 清除引导标记
wx.removeStorageSync('hasShownGuide')
// 重新加载页面
wx.reLaunch({ url: '/pages/index/index' })
```
### 方法2清除缓存测试
1. 微信开发者工具 → 清缓存 → 清除所有
2. 重新编译项目
3. 重新进入首页
### 方法3手动触发测试
在 [pages/index/index.js](pages/index/index.js) 中临时修改:
```javascript
checkAndShowGuide() {
// 注释掉检查逻辑,直接显示
// const hasShownGuide = wx.getStorageSync('hasShownGuide')
// if (!hasShownGuide) {
setTimeout(() => {
this.setData({ showGuide: true })
}, 500)
// }
}
```
---
## 自定义配置
### 修改引导步骤
编辑 [pages/index/index.js](pages/index/index.js) 中的 `guideSteps` 数组:
```javascript
guideSteps: [
// 添加新的步骤
{
selector: '.my-element',
title: '新功能',
description: '这是一个新功能的说明',
position: 'top',
padding: 10,
borderRadius: 12
}
]
```
### 修改样式
编辑 [components/guide/guide.wxss](components/guide/guide.wxss)
```css
/* 修改遮罩透明度 */
.guide-overlay {
background: rgba(0, 0, 0, 0.75); /* 调整透明度 */
}
/* 修改提示卡片样式 */
.guide-text-wrapper {
background: white; /* 背景色 */
border-radius: 16rpx; /* 圆角 */
padding: 32rpx 28rpx; /* 内边距 */
}
/* 修改按钮样式 */
.guide-next {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
```
### 修改位置计算逻辑
编辑 [components/guide/guide.js](components/guide/guide.js) 中的 `calculateContentPosition` 方法。
---
## 功能特点
### 1. 智能位置计算
组件会自动计算最佳显示位置,避免超出屏幕边界:
- 优先使用指定位置top/bottom/left/right
- 空间不足时自动切换到对面
- 保持在屏幕可视范围内
### 2. 高亮打洞效果
使用 CSS `box-shadow` 实现高亮区域:
```css
.guide-spotlight {
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.75);
}
```
这样可以在遮罩层上"打洞",突出显示目标元素。
### 3. 步骤进度指示
底部圆点显示当前进度:
- 灰色圆点:未完成的步骤
- 紫色长条:当前步骤
- 平滑过渡动画
### 4. 防止交互穿透
```javascript
// 阻止滚动
preventMove() {}
// 阻止点击穿透
catchtouchmove="preventMove"
```
---
## 注意事项
### 1. 选择器必须唯一
确保每个引导步骤的 `selector` 能唯一定位到目标元素:
```html
<!-- ✅ 正确使用唯一的class -->
<view class="btn guide-create-btn"></view>
<!-- ❌ 错误:选择器不唯一 -->
<view class="btn"></view>
<view class="btn"></view>
```
### 2. 等待页面渲染
`onReady` 生命周期中显示引导,并使用延迟:
```javascript
onReady() {
setTimeout(() => {
this.setData({ showGuide: true })
}, 500) // 延迟500ms确保页面完全渲染
}
```
### 3. 避免在隐藏元素上使用
不要在 `wx:if="{{false}}"``display: none` 的元素上使用引导,否则无法获取位置。
### 4. 不同页面使用不同标记
如果多个页面都有引导使用不同的storage key
```javascript
// 首页
wx.setStorageSync('hasShownGuide_index', true)
// 其他页面
wx.setStorageSync('hasShownGuide_detail', true)
```
---
## API参数
### 组件属性
| 属性 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| show | Boolean | 是 | false | 是否显示引导 |
| steps | Array | 是 | [] | 引导步骤配置 |
### 组件事件
| 事件 | 参数 | 说明 |
|------|------|------|
| complete | 无 | 引导完成时触发 |
### 步骤配置
| 字段 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| selector | String | 是 | - | 目标元素的CSS选择器 |
| title | String | 是 | - | 引导标题 |
| description | String | 是 | - | 引导说明文字 |
| position | String | 否 | 'bottom' | 提示框位置top/bottom/left/right|
| padding | Number | 否 | 8 | 高亮区域扩展边距px|
| borderRadius | Number | 否 | 16 | 高亮区域圆角大小px|
| showArrow | Boolean | 否 | true | 是否显示箭头 |
---
## 效果预览
### 步骤1创建房间
- 高亮"创建房间"按钮
- 显示说明文字
- 进度指示1/3
### 步骤2加入房间
- 高亮"加入房间"按钮
- 显示说明文字
- 进度指示2/3
### 步骤3扫码进入
- 高亮"扫码"按钮
- 显示说明文字
- 进度指示3/3
- 显示"知道了"按钮
---
## 常见问题
### Q: 引导没有显示?
A: 检查以下几点:
1. 是否已经显示过(清除 storage
2. 选择器是否正确
3. 目标元素是否已渲染
4. 延迟时间是否足够
### Q: 位置显示不对?
A: 检查:
1. 目标元素是否被隐藏
2. 页面是否完全渲染
3. 增加 `setTimeout` 延迟时间
### Q: 如何调试?
A: 在组件的 `showStep` 方法中添加日志:
```javascript
showStep(stepIndex) {
const step = this.data.steps[stepIndex]
console.log('显示引导步骤:', stepIndex, step)
// ...
}
```
### Q: 如何强制重新显示引导?
A: 控制台执行:
```javascript
wx.removeStorageSync('hasShownGuide')
getCurrentPages()[0].setData({ showGuide: true })
```
---
## 未来优化方向
1. **手势控制** - 支持滑动切换步骤
2. **视频教程** - 嵌入视频演示
3. **自定义主题** - 支持暗色模式
4. **动画增强** - 添加更多过渡动画
5. **智能触发** - 根据用户行为动态显示引导
6. **A/B测试** - 支持多种引导方案
---
## 开发者
如有问题或建议,请联系开发团队。

148
app.js Normal file
View File

@ -0,0 +1,148 @@
import { injectApp, waitLogin, gatewayHttpClient } from '@jdmini/api'
import request from './utils/request'
App(injectApp()({
globalData: {
userInfo: null,
currentSession: null,
openid: null,
jdToken: null
},
async getQrcode(scene, page = 'pages/game/join/join') {
try {
const response = await gatewayHttpClient.request('/wx/v1/api/app/qrcode', 'POST', {
scene: scene,
page: page,
check_path: false, // 关闭路径检查,因为可能是动态路由
env_version: 'release' // 开发版本
},
{
responseType: 'arraybuffer'
}
)
console.log('二维码API响应:', response)
// 检查响应是否是对象如果是则可能包含data字段
if (response && typeof response === 'object' && response.data) {
return response.data
}
return response
} catch (error) {
console.error('获取二维码失败:', error)
throw error
}
},
async onLaunch() {
console.log('App启动')
// 等待JD登录完成获取openid
await this.initJDLogin()
// JD登录完成后尝试后端登录
//await this.loginToBackend()
// console.log(await this.getQrcode('a=1&b=2'))
},
// 初始化JD登录获取openid和token
async initJDLogin() {
try {
// 等待JD登录完成
await waitLogin()
// 获取JD用户信息
const jdToken = wx.getStorageSync('jdwx-token')
const jdUserInfo = wx.getStorageSync('jdwx-userinfo')
if (jdUserInfo && jdUserInfo.openId) {
this.globalData.openid = jdUserInfo.openId
this.globalData.jdToken = jdToken
console.log('JD登录成功openid:', jdUserInfo.openId)
}
} catch (error) {
console.error('JD登录失败:', error)
}
},
// 登录到后端
async loginToBackend() {
try {
if (!this.globalData.openid) {
console.log('没有openid跳过后端登录')
return false
}
console.log('尝试后端登录openid:', this.globalData.openid)
// 调用后端登录接口不传userInfo只用openid查询
const response = await request.post('/auth/login', {
openid: this.globalData.openid
})
if (response && response.player) {
// 用户已注册,保存用户信息
this.globalData.userInfo = response.player
wx.setStorageSync('userInfo', response.player)
console.log('后端登录成功,用户信息:', response.player)
// 触发登录成功事件,通知首页更新
this.triggerLoginSuccess()
return true
} else if (response && response.exists === false) {
// 用户未注册(后端返回 player: null, exists: false
console.log('用户未注册,需要完善信息')
return false
} else {
// 其他情况
console.log('登录响应异常:', response)
return false
}
} catch (error) {
console.error('后端登录失败:', error)
return false
}
},
// 触发登录成功事件
triggerLoginSuccess() {
this.refreshIndexPage()
},
// 刷新首页数据
refreshIndexPage() {
// 获取所有页面
const pages = getCurrentPages()
if (pages.length > 0) {
// 查找首页
const indexPage = pages.find(page => page.route === 'pages/index/index')
if (indexPage && typeof indexPage.loadUserInfo === 'function') {
console.log('触发首页刷新')
indexPage.loadUserInfo()
}
}
},
// 获取当前用户信息
getUserInfo() {
return wx.getStorageSync('userInfo')
},
getUserOpenid() {
return this.globalData.openid
},
// 设置用户信息
setUserInfo(userInfo) {
this.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
},
// 更新当前牌局
setCurrentSession(session) {
this.globalData.currentSession = session
},
// 获取当前牌局
getCurrentSession() {
return this.globalData.currentSession
}
}))

44
app.json Normal file
View File

@ -0,0 +1,44 @@
{
"pages": [
"pages/index/index",
"pages/login/login",
"pages/game/create/create",
"pages/game/join/join",
"pages/game/detail/detail",
"pages/game/settlement/settlement",
"pages/game/play/play",
"pages/record/list/list",
"pages/stats/personal/personal",
"pages/stats/session/session",
"pages/profile/index/index"
],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "打牌记账小本",
"navigationBarBackgroundColor": "#07c160"
},
"tabBar": {
"custom": true,
"color": "#666666",
"selectedColor": "#667eea",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/stats/personal/personal",
"text": "统计"
},
{
"pagePath": "pages/profile/index/index",
"text": "我的"
}
]
},
"style": "v2",
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}

527
app.wxss Normal file
View File

@ -0,0 +1,527 @@
/**
* h@7 - ÑÑbN;˜
* ;rÑr (#D4AF37) + Ñr (#000000)
* Î<bNØï¾ô
*/
/* e;˜Mn */
@import "styles/theme.wxss";
/* ==================== h@ú@7 ==================== */
page {
background: linear-gradient(180deg, #000000 0%, #0A0A0A 100%);
color: #FFFFFF;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
min-height: 100vh;
}
/* ==================== ®ûß ==================== */
/* ; ® - ÑrØ */
.btn-primary {
background: linear-gradient(135deg, #D4AF37 0%, #F4E6C3 100%);
color: #000000;
font-weight: 600;
border: none;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(212, 175, 55, 0.4);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-primary:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(212, 175, 55, 0.5);
}
/* !<21> ® - örØ */
.btn-secondary {
background: linear-gradient(135deg, #C0C0C0 0%, #E5E5E5 100%);
color: #000000;
font-weight: 600;
border: none;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(192, 192, 192, 0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-secondary:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(192, 192, 192, 0.4);
}
/* Ÿ ® - ÿr */
.btn-success {
background: linear-gradient(135deg, #50C878 0%, #06a854 100%);
color: #FFFFFF;
font-weight: 600;
border: none;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(80, 200, 120, 0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-success:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(80, 200, 120, 0.4);
}
/* fJ ® - Ñr */
.btn-warning {
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
color: #000000;
font-weight: 600;
border: none;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 215, 0, 0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-warning:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(255, 215, 0, 0.4);
}
/* qi ® - ¢r */
.btn-danger {
background: linear-gradient(135deg, #DC143C 0%, #8B0000 100%);
color: #FFFFFF;
font-weight: 600;
border: none;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 28rpx;
box-shadow: 0 8rpx 24rpx rgba(220, 20, 60, 0.3);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-danger:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(220, 20, 60, 0.4);
}
/* }u ® - Ñr¹F */
.btn-ghost {
background: transparent;
color: #D4AF37;
border: 2rpx solid #D4AF37;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 28rpx;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-ghost:active {
background: rgba(212, 175, 55, 0.1);
transform: scale(0.98);
}
/* <20>( ® */
.btn-disabled,
button[disabled] {
background: #2A2A2A !important;
color: #606060 !important;
box-shadow: none !important;
opacity: 0.5;
}
/* ==================== aGûß ==================== */
/* ÆaG */
.card {
background: linear-gradient(135deg, #1A1A1A 0%, #252525 100%);
border-radius: 20rpx;
padding: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.8);
border: 1rpx solid rgba(212, 175, 55, 0.2);
margin-bottom: 24rpx;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card:active {
transform: translateY(-2rpx);
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.9);
}
/* ÑrÅpaG */
.card-gold {
position: relative;
overflow: hidden;
}
.card-gold::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3rpx;
background: linear-gradient(90deg, transparent 0%, #D4AF37 20%, #F4E6C3 50%, #D4AF37 80%, transparent 100%);
}
/* خaG */
.card-highlight {
background: linear-gradient(135deg, #252525 0%, #2A2A2A 100%);
border: 2rpx solid #D4AF37;
box-shadow: 0 4rpx 20rpx rgba(212, 175, 55, 0.3);
}
/* ==================== 8øs ==================== */
/* Ü)7 */
.game-win {
color: #50C878;
font-weight: bold;
}
.game-win-bg {
background: linear-gradient(135deg, rgba(80, 200, 120, 0.15) 0%, rgba(6, 168, 84, 0.2) 100%);
border: 1rpx solid rgba(80, 200, 120, 0.3);
}
/* 1%7 */
.game-lose {
color: #DC143C;
font-weight: bold;
}
.game-lose-bg {
background: linear-gradient(135deg, rgba(220, 20, 60, 0.15) 0%, rgba(139, 0, 0, 0.2) 100%);
border: 1rpx solid rgba(220, 20, 60, 0.3);
}
/* s@7 */
.game-draw {
color: #B0B0B0;
font-weight: bold;
}
.game-draw-bg {
background: linear-gradient(135deg, rgba(176, 176, 176, 0.15) 0%, rgba(96, 96, 96, 0.2) 100%);
border: 1rpx solid rgba(176, 176, 176, 0.3);
}
/* ==================== h7 ==================== */
.list-item {
background: #1A1A1A;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 16rpx;
display: flex;
align-items: center;
border: 1rpx solid #2A2A2A;
transition: all 0.3s ease;
}
.list-item:active {
background: #222222;
transform: translateX(8rpx);
}
.list-item-content {
flex: 1;
margin-left: 20rpx;
}
.list-item-title {
font-size: 28rpx;
font-weight: bold;
color: #FFFFFF;
margin-bottom: 8rpx;
}
.list-item-desc {
font-size: 24rpx;
color: #B0B0B0;
}
/* ==================== 4Ïûß ==================== */
.avatar {
border-radius: 50%;
background: linear-gradient(135deg, #2A2A2A 0%, #3A3A3A 100%);
border: 3rpx solid #D4AF37;
box-shadow: 0 4rpx 12rpx rgba(212, 175, 55, 0.4);
}
.avatar-sm {
width: 60rpx;
height: 60rpx;
}
.avatar-md {
width: 80rpx;
height: 80rpx;
}
.avatar-lg {
width: 120rpx;
height: 120rpx;
}
.avatar-xl {
width: 160rpx;
height: 160rpx;
}
/* ==================== ½à~ ==================== */
.badge {
display: inline-flex;
align-items: center;
padding: 6rpx 16rpx;
border-radius: 24rpx;
font-size: 20rpx;
font-weight: 600;
border: 1rpx solid;
}
.badge-gold {
background: linear-gradient(135deg, rgba(212, 175, 55, 0.15) 0%, rgba(244, 230, 195, 0.2) 100%);
color: #D4AF37;
border-color: #D4AF37;
}
.badge-silver {
background: rgba(192, 192, 192, 0.15);
color: #C0C0C0;
border-color: #C0C0C0;
}
.badge-success {
background: rgba(80, 200, 120, 0.15);
color: #50C878;
border-color: #50C878;
}
.badge-warning {
background: rgba(255, 215, 0, 0.15);
color: #FFD700;
border-color: #FFD700;
}
.badge-danger {
background: rgba(220, 20, 60, 0.15);
color: #DC143C;
border-color: #DC143C;
}
/* ==================== !F/9— ==================== */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
z-index: 9998;
}
.modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600rpx;
max-width: 90%;
background: linear-gradient(135deg, #1A1A1A 0%, #252525 100%);
border: 1rpx solid rgba(212, 175, 55, 0.3);
border-radius: 24rpx;
padding: 48rpx 32rpx 32rpx;
box-shadow: 0 16rpx 64rpx rgba(0, 0, 0, 0.9);
z-index: 9999;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #F4E6C3;
text-align: center;
margin-bottom: 24rpx;
}
.modal-body {
font-size: 28rpx;
color: #E0E0E0;
line-height: 1.6;
margin-bottom: 32rpx;
}
.modal-footer {
display: flex;
gap: 20rpx;
}
.modal-footer button {
flex: 1;
}
/* ==================== z¶ ==================== */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
text-align: center;
}
.empty-icon {
font-size: 120rpx;
color: #3A3A3A;
margin-bottom: 32rpx;
opacity: 0.6;
}
.empty-text {
font-size: 28rpx;
color: #B0B0B0;
margin-bottom: 16rpx;
}
.empty-hint {
font-size: 24rpx;
color: #606060;
margin-bottom: 32rpx;
}
/* ====================   ==================== */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #2A2A2A;
border-top-color: #D4AF37;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 24rpx;
font-size: 24rpx;
color: #B0B0B0;
}
/* ==================== •èÍ\ ==================== */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #1A1A1A;
border-top: 1rpx solid rgba(212, 175, 55, 0.2);
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.8);
z-index: 999;
}
.bottom-bar-content {
display: flex;
gap: 20rpx;
align-items: center;
}
/* ==================== ½à ==================== */
.rank-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
border-radius: 50%;
font-size: 28rpx;
font-weight: bold;
border: 3rpx solid;
}
.rank-1 {
background: linear-gradient(135deg, #D4AF37 0%, #F4E6C3 100%);
color: #000000;
border-color: #D4AF37;
box-shadow: 0 4rpx 16rpx rgba(212, 175, 55, 0.5);
}
.rank-2 {
background: linear-gradient(135deg, #C0C0C0 0%, #E5E5E5 100%);
color: #000000;
border-color: #C0C0C0;
box-shadow: 0 4rpx 16rpx rgba(192, 192, 192, 0.4);
}
.rank-3 {
background: linear-gradient(135deg, #CD7F32 0%, #E5A35C 100%);
color: #000000;
border-color: #CD7F32;
box-shadow: 0 4rpx 16rpx rgba(205, 127, 50, 0.4);
}
.rank-other {
background: #2A2A2A;
color: #B0B0B0;
border-color: #3A3A3A;
}
/* ==================== r¿ ==================== */
.divider {
height: 1rpx;
background: #2A2A2A;
margin: 24rpx 0;
}
.divider-gold {
height: 2rpx;
background: linear-gradient(90deg, transparent 0%, #D4AF37 20%, #D4AF37 80%, transparent 100%);
margin: 32rpx 0;
}
/* ==================== ž(åw{ ==================== */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.mt-xs { margin-top: 8rpx; }
.mt-sm { margin-top: 12rpx; }
.mt-md { margin-top: 16rpx; }
.mt-lg { margin-top: 24rpx; }
.mt-xl { margin-top: 32rpx; }
.mb-xs { margin-bottom: 8rpx; }
.mb-sm { margin-bottom: 12rpx; }
.mb-md { margin-bottom: 16rpx; }
.mb-lg { margin-bottom: 24rpx; }
.mb-xl { margin-bottom: 32rpx; }
.p-xs { padding: 8rpx; }
.p-sm { padding: 12rpx; }
.p-md { padding: 16rpx; }
.p-lg { padding: 24rpx; }
.p-xl { padding: 32rpx; }

View File

@ -0,0 +1,59 @@
// components/custom-tabbar/custom-tabbar.js
Component({
data: {
selected: 0,
color: "#666666",
selectedColor: "#667eea",
list: [
{
pagePath: "/pages/index/index",
text: "打牌记账",
iconPath: "../images/index.png",
selectedIconPath: "../images/index_active.png"
},
{
pagePath: "/pages/stats/personal/personal",
text: "统计分析",
iconPath: "../images/record.png",
selectedIconPath: "../images/record-active.png"
},
{
pagePath: "/pages/profile/index/index",
text: "我的",
iconPath: "../images/my.png",
selectedIconPath: "../images/my-active.png"
}
]
},
properties: {
selected: {
type: Number,
value: 0
}
},
lifetimes: {
attached() {
// 组件初始化时更新选中状态
this.setData({
selected: this.data.selected
})
}
},
methods: {
switchTab(e) {
const index = e.currentTarget.dataset.index
const pagePath = this.data.list[index].pagePath
// 触发事件通知父组件
this.triggerEvent('tabchange', { index, pagePath })
// 切换页面
wx.switchTab({
url: pagePath
})
}
}
})

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,23 @@
<!-- components/custom-tabbar/custom-tabbar.wxml -->
<view class="tabbar">
<view
class="tabbar-item {{selected === index ? 'active' : ''}}"
wx:for="{{list}}"
wx:key="index"
data-index="{{index}}"
bindtap="switchTab"
>
<view class="tabbar-icon">
<image
src="{{selected === index ? item.selectedIconPath : item.iconPath}}"
mode="aspectFit"
/>
</view>
<view
class="tabbar-text"
style="color: {{selected === index ? selectedColor : color}}"
>
{{item.text}}
</view>
</view>
</view>

View File

@ -0,0 +1,53 @@
/* components/custom-tabbar/custom-tabbar.wxss */
.tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background: #1A1A1A;
border-top: 1rpx solid rgba(212, 175, 55, 0.2);
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.8);
z-index: 999;
padding-bottom: env(safe-area-inset-bottom);
}
.tabbar-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10rpx 0;
transition: all 0.3s ease;
}
.tabbar-item.active {
transform: translateY(-4rpx);
}
.tabbar-icon {
width: 48rpx;
height: 48rpx;
margin-bottom: 6rpx;
}
.tabbar-icon image {
width: 100%;
height: 100%;
}
.tabbar-text {
font-size: 22rpx;
color: #B0B0B0;
transition: all 0.3s ease;
}
.tabbar-item.active .tabbar-text {
font-weight: 600;
font-size: 24rpx;
color: #D4AF37;
}

208
components/guide/guide.js Normal file
View File

@ -0,0 +1,208 @@
// 新用户引导组件
Component({
properties: {
show: {
type: Boolean,
value: false
},
steps: {
type: Array,
value: []
}
},
data: {
currentStep: 0,
totalSteps: 0,
title: '',
description: '',
spotlightStyle: {},
contentStyle: '',
arrowDirection: 'top',
showArrow: true
},
observers: {
'show': function(show) {
if (show && this.data.steps.length > 0) {
this.setData({
currentStep: 0,
totalSteps: this.data.steps.length
})
this.showStep(0)
}
}
},
methods: {
// 显示指定步骤
showStep(stepIndex) {
if (stepIndex < 0 || stepIndex >= this.data.steps.length) {
return
}
const step = this.data.steps[stepIndex]
this.setData({
currentStep: stepIndex,
title: step.title,
description: step.description,
showArrow: step.showArrow !== false
})
// 延迟获取元素位置,确保页面已渲染
setTimeout(() => {
this.updateSpotlight(step)
}, 50)
},
// 更新高亮区域
updateSpotlight(step) {
const query = wx.createSelectorQuery().in(this)
// 查询目标元素(在页面中)
const pageQuery = wx.createSelectorQuery()
pageQuery.select(step.selector).boundingClientRect()
pageQuery.selectViewport().scrollOffset()
pageQuery.exec((res) => {
if (!res || !res[0]) {
console.warn('未找到目标元素:', step.selector)
return
}
const rect = res[0]
const scroll = res[1]
// 计算高亮区域样式添加padding扩展区域
const padding = step.padding || 8
const spotlightStyle = {
top: rect.top - padding,
left: rect.left - padding,
width: rect.width + padding * 2,
height: rect.height + padding * 2,
borderRadius: step.borderRadius || 16
}
// 计算提示内容位置
const contentStyle = this.calculateContentPosition(spotlightStyle, step.position || 'bottom')
this.setData({
spotlightStyle,
contentStyle: contentStyle.style,
arrowDirection: contentStyle.arrowDirection
})
})
},
// 计算提示内容位置
calculateContentPosition(spotlightStyle, preferredPosition) {
const windowInfo = wx.getWindowInfo()
const screenHeight = windowInfo.windowHeight
const screenWidth = windowInfo.windowWidth
const contentWidth = 600 // rpx转换为px约300
const contentMaxHeight = 400 // 估算最大高度
let position = preferredPosition
let top = 0
let left = 0
let arrowDirection = 'top'
// 计算中心点
const spotlightCenterX = spotlightStyle.left + spotlightStyle.width / 2
const spotlightCenterY = spotlightStyle.top + spotlightStyle.height / 2
// 根据位置偏好计算
switch (position) {
case 'top':
top = spotlightStyle.top - contentMaxHeight - 40
left = Math.max(20, Math.min(spotlightCenterX - contentWidth / 2, screenWidth - contentWidth - 20))
arrowDirection = 'bottom'
// 如果顶部空间不足,改为底部
if (top < 20) {
position = 'bottom'
}
break
case 'bottom':
top = spotlightStyle.top + spotlightStyle.height + 20
left = Math.max(20, Math.min(spotlightCenterX - contentWidth / 2, screenWidth - contentWidth - 20))
arrowDirection = 'top'
// 如果底部空间不足,改为顶部
if (top + contentMaxHeight > screenHeight - 20) {
position = 'top'
top = spotlightStyle.top - contentMaxHeight - 40
arrowDirection = 'bottom'
}
break
case 'left':
left = spotlightStyle.left - contentWidth - 20
top = Math.max(20, Math.min(spotlightCenterY - 100, screenHeight - contentMaxHeight - 20))
arrowDirection = 'right'
// 如果左侧空间不足,改为右侧
if (left < 20) {
position = 'right'
}
break
case 'right':
left = spotlightStyle.left + spotlightStyle.width + 20
top = Math.max(20, Math.min(spotlightCenterY - 100, screenHeight - contentMaxHeight - 20))
arrowDirection = 'left'
// 如果右侧空间不足,改为左侧
if (left + contentWidth > screenWidth - 20) {
position = 'left'
left = spotlightStyle.left - contentWidth - 20
arrowDirection = 'right'
}
break
}
// 如果重新计算了位置,递归调用
if (position !== preferredPosition) {
return this.calculateContentPosition(spotlightStyle, position)
}
return {
style: `top: ${top}px; left: ${left}px;`,
arrowDirection
}
},
// 下一步
handleNext() {
const nextStep = this.data.currentStep + 1
if (nextStep >= this.data.steps.length) {
// 引导完成
this.handleComplete()
} else {
// 显示下一步
this.showStep(nextStep)
}
},
// 跳过引导
handleSkip() {
this.handleComplete()
},
// 完成引导
handleComplete() {
this.triggerEvent('complete')
},
// 点击遮罩(不做任何操作,防止穿透)
handleMaskTap() {
// 可选:点击遮罩也进入下一步
// this.handleNext()
},
// 阻止滚动穿透
preventMove() {}
}
})

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,39 @@
<!-- 新用户引导组件 -->
<view class="guide-mask" wx:if="{{show}}" catchtouchmove="preventMove" bindtap="handleMaskTap">
<!-- 遮罩层 -->
<view class="guide-overlay"></view>
<!-- 高亮区域 -->
<view class="guide-spotlight"
style="top: {{spotlightStyle.top}}px; left: {{spotlightStyle.left}}px; width: {{spotlightStyle.width}}px; height: {{spotlightStyle.height}}px; border-radius: {{spotlightStyle.borderRadius}}px;">
</view>
<!-- 提示内容 -->
<view class="guide-content" style="{{contentStyle}}">
<!-- 箭头指示器 -->
<view class="guide-arrow {{arrowDirection}}" wx:if="{{showArrow}}"></view>
<!-- 文本内容 -->
<view class="guide-text-wrapper">
<view class="guide-title">{{title}}</view>
<view class="guide-desc">{{description}}</view>
<!-- 底部操作栏 -->
<view class="guide-footer">
<view class="guide-dots">
<view class="guide-dot {{index === currentStep ? 'active' : ''}}"
wx:for="{{totalSteps}}"
wx:key="index">
</view>
</view>
<view class="guide-actions">
<text class="guide-skip" bindtap="handleSkip" wx:if="{{currentStep < totalSteps - 1}}">跳过</text>
<text class="guide-next" bindtap="handleNext">
{{currentStep === totalSteps - 1 ? '知道了' : '下一步'}}
</text>
</view>
</view>
</view>
</view>
</view>

164
components/guide/guide.wxss Normal file
View File

@ -0,0 +1,164 @@
/* 新用户引导样式 */
/* 遮罩层 */
.guide-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
}
.guide-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.75);
}
/* 高亮区域(通过透明背景实现打洞效果)*/
.guide-spotlight {
position: absolute;
background: transparent;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.75);
z-index: 10000;
transition: all 0.3s ease;
}
/* 提示内容容器 */
.guide-content {
position: absolute;
z-index: 10001;
max-width: 600rpx;
transition: all 0.3s ease;
}
.guide-text-wrapper {
background: linear-gradient(135deg, #1A1A1A 0%, #252525 100%);
border: 1rpx solid rgba(212, 175, 55, 0.3);
border-radius: 16rpx;
padding: 32rpx 28rpx 24rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.8);
}
/* 箭头指示器 */
.guide-arrow {
position: absolute;
width: 0;
height: 0;
border: 20rpx solid transparent;
}
.guide-arrow.top {
bottom: 100%;
left: 50%;
margin-left: -20rpx;
border-bottom-color: #1A1A1A;
border-top: none;
}
.guide-arrow.bottom {
top: 100%;
left: 50%;
margin-left: -20rpx;
border-top-color: #1A1A1A;
border-bottom: none;
}
.guide-arrow.left {
right: 100%;
top: 50%;
margin-top: -20rpx;
border-right-color: #1A1A1A;
border-left: none;
}
.guide-arrow.right {
left: 100%;
top: 50%;
margin-top: -20rpx;
border-left-color: #1A1A1A;
border-right: none;
}
/* 文本样式 */
.guide-title {
font-size: 32rpx;
font-weight: bold;
color: #F4E6C3;
margin-bottom: 16rpx;
line-height: 1.4;
}
.guide-desc {
font-size: 26rpx;
color: #E0E0E0;
line-height: 1.6;
margin-bottom: 24rpx;
}
/* 底部操作栏 */
.guide-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 20rpx;
border-top: 1rpx solid rgba(212, 175, 55, 0.2);
}
/* 进度点 */
.guide-dots {
display: flex;
gap: 12rpx;
align-items: center;
}
.guide-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #3A3A3A;
transition: all 0.3s ease;
}
.guide-dot.active {
width: 32rpx;
border-radius: 6rpx;
background: linear-gradient(135deg, #D4AF37 0%, #F4E6C3 100%);
box-shadow: 0 2rpx 8rpx rgba(212, 175, 55, 0.4);
}
/* 操作按钮 */
.guide-actions {
display: flex;
gap: 24rpx;
align-items: center;
}
.guide-skip {
font-size: 26rpx;
color: #B0B0B0;
padding: 8rpx 16rpx;
}
.guide-next {
font-size: 26rpx;
font-weight: 500;
color: #000000;
background: linear-gradient(135deg, #D4AF37 0%, #F4E6C3 100%);
padding: 12rpx 32rpx;
border-radius: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(212, 175, 55, 0.4);
}
.guide-skip:active {
opacity: 0.6;
}
.guide-next:active {
opacity: 0.9;
transform: scale(0.98);
}

BIN
components/images/index.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
components/images/my.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

View File

@ -0,0 +1,75 @@
// components/navbar/navbar.js
Component({
properties: {
// 标题
title: {
type: String,
value: ''
},
// 是否显示返回按钮
showBack: {
type: Boolean,
value: true
},
// 返回按钮文字
backText: {
type: String,
value: '返回'
},
// 背景色(支持渐变)
background: {
type: String,
value: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
}
},
data: {
statusBarHeight: 0,
menuButtonTop: 0,
menuButtonHeight: 0,
navbarHeight: 0
},
lifetimes: {
attached() {
this.setNavBarInfo()
}
},
methods: {
// 设置导航栏信息
setNavBarInfo() {
const windowInfo = wx.getWindowInfo()
const menuButtonInfo = wx.getMenuButtonBoundingClientRect()
const statusBarHeight = windowInfo.statusBarHeight
const menuButtonTop = menuButtonInfo.top
const menuButtonHeight = menuButtonInfo.height
// 导航栏总高度 = 胶囊按钮bottom + 胶囊按钮到顶部的距离
const navbarHeight = menuButtonInfo.bottom + (menuButtonTop - statusBarHeight)
this.setData({
statusBarHeight,
menuButtonTop,
menuButtonHeight,
navbarHeight
})
},
// 返回按钮点击
onBackTap() {
this.triggerEvent('back')
// 如果没有监听back事件默认返回上一页
const pages = getCurrentPages()
if (pages.length > 1) {
wx.navigateBack()
} else {
wx.switchTab({
url: '/pages/index/index'
})
}
}
}
})

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,17 @@
<!-- 自定义导航栏组件 -->
<view class="custom-navbar" style="height: {{navbarHeight}}px;">
<!-- 返回按钮 -->
<view
class="nav-back-btn"
wx:if="{{showBack}}"
bindtap="onBackTap"
style="top: {{menuButtonTop}}px; height: {{menuButtonHeight}}px;">
<text class="back-icon">←</text>
<text class="back-text" wx:if="{{backText}}">{{backText}}</text>
</view>
<!-- 标题 -->
<view class="nav-title-wrap" style="height: {{menuButtonHeight}}px; top: {{menuButtonTop}}px;">
<text class="nav-title">{{title}}</text>
</view>
</view>

View File

@ -0,0 +1,63 @@
/* 自定义导航栏样式 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 9999;
background: linear-gradient(135deg, #1A1A1A 0%, #252525 100%);
border-bottom: 1rpx solid rgba(212, 175, 55, 0.2);
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.8);
}
/* 返回按钮 */
.nav-back-btn {
position: fixed;
left: 20rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 0 24rpx;
background: rgba(212, 175, 55, 0.15);
border: 1rpx solid rgba(212, 175, 55, 0.3);
border-radius: 50rpx;
backdrop-filter: blur(10rpx);
z-index: 10000;
}
.back-icon {
font-size: 32rpx;
color: white;
font-weight: bold;
line-height: 1;
}
.back-text {
font-size: 28rpx;
color: white;
line-height: 1;
}
/* 标题区域 */
.nav-title-wrap {
position: fixed;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 9998;
}
.nav-title {
font-size: 34rpx;
font-weight: bold;
color: #F4E6C3;
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

86
create-icons.js Normal file
View File

@ -0,0 +1,86 @@
const fs = require('fs');
const path = require('path');
// 创建SVG图标的函数
function createSVGIcon(type, color) {
let svgContent = '';
switch(type) {
case 'home':
svgContent = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<path d="M40.5 15 L20 35 L25 35 L25 65 L35 65 L35 50 L46 50 L46 65 L56 65 L56 35 L61 35 Z"
fill="${color}" />
</svg>`;
break;
case 'stats':
svgContent = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<rect x="15" y="45" width="15" height="20" fill="${color}"/>
<rect x="33" y="35" width="15" height="30" fill="${color}"/>
<rect x="51" y="25" width="15" height="40" fill="${color}"/>
</svg>`;
break;
case 'profile':
svgContent = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<circle cx="40.5" cy="25" r="12" fill="${color}"/>
<ellipse cx="40.5" cy="55" rx="20" ry="15" fill="${color}"/>
</svg>`;
break;
}
return svgContent;
}
// 创建简单的PNG占位文件使用最小的PNG数据
function createMinimalPNG() {
// 最小的1x1 PNG图片的Base64数据
const base64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
return Buffer.from(base64, 'base64');
}
// 图标配置
const icons = [
{ name: 'home.png', type: 'home', color: '#999999' },
{ name: 'home-active.png', type: 'home', color: '#07c160' },
{ name: 'stats.png', type: 'stats', color: '#999999' },
{ name: 'stats-active.png', type: 'stats', color: '#07c160' },
{ name: 'profile.png', type: 'profile', color: '#999999' },
{ name: 'profile-active.png', type: 'profile', color: '#07c160' },
{ name: 'default-avatar.png', type: 'profile', color: '#cccccc' },
{ name: 'empty.png', type: 'home', color: '#dddddd' }
];
// 创建images目录如果不存在
const imagesDir = path.join(__dirname, 'images');
if (!fs.existsSync(imagesDir)) {
fs.mkdirSync(imagesDir, { recursive: true });
}
// 创建所有图标文件PNG占位符
icons.forEach(icon => {
const filePath = path.join(imagesDir, icon.name);
const pngData = createMinimalPNG();
fs.writeFileSync(filePath, pngData);
console.log(`创建图标: ${icon.name}`);
});
// 同时创建SVG版本供参考
icons.forEach(icon => {
const svgFilePath = path.join(imagesDir, icon.name.replace('.png', '.svg'));
const svgContent = createSVGIcon(icon.type, icon.color);
if (svgContent) {
fs.writeFileSync(svgFilePath, svgContent);
console.log(`创建SVG: ${icon.name.replace('.png', '.svg')}`);
}
});
console.log('\n图标创建完成');
console.log('提示:');
console.log('1. PNG文件是占位符可以在微信开发者工具中正常运行');
console.log('2. SVG文件显示了实际图标样式');
console.log('3. 您可以使用在线工具将SVG转换为PNG');
console.log('4. 或者打开generate-icons.html在浏览器中生成真实PNG图标');

7
ext.json Normal file
View File

@ -0,0 +1,7 @@
{
"extEnable": true,
"extAppid": "wx171c02d83197be01",
"ext":{
"appId":435
}
}

197
generate-icons.html Normal file
View File

@ -0,0 +1,197 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>生成TabBar图标</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
}
h1 {
color: #333;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.icon-item {
border: 1px solid #ddd;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.icon-item h3 {
margin: 0 0 10px 0;
font-size: 14px;
}
canvas {
border: 1px solid #eee;
margin: 5px;
}
.download-btn {
display: inline-block;
margin: 5px;
padding: 5px 10px;
background: #07c160;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 12px;
}
.download-all {
margin-top: 20px;
padding: 10px 20px;
background: #07c160;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.instructions {
background: #f0f8ff;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>打牌记账小程序 - TabBar图标生成器</h1>
<div class="instructions">
<h3>使用说明:</h3>
<ol>
<li>点击每个图标下方的"下载"按钮保存单个图标</li>
<li>或点击"下载所有图标"按钮一次性下载全部</li>
<li>将下载的图片保存到 <code>miniapp/images/</code> 目录</li>
<li>图标尺寸81x81像素适合小程序使用</li>
</ol>
</div>
<div class="icon-grid" id="iconGrid"></div>
<button class="download-all" onclick="downloadAll()">下载所有图标</button>
</div>
<script>
// 图标配置
const icons = [
{ name: 'home', label: '首页图标(灰色)', color: '#999999' },
{ name: 'home-active', label: '首页图标(绿色)', color: '#07c160' },
{ name: 'stats', label: '统计图标(灰色)', color: '#999999' },
{ name: 'stats-active', label: '统计图标(绿色)', color: '#07c160' },
{ name: 'profile', label: '个人中心(灰色)', color: '#999999' },
{ name: 'profile-active', label: '个人中心(绿色)', color: '#07c160' }
];
// 绘制图标函数
function drawIcon(ctx, type, color) {
ctx.clearRect(0, 0, 81, 81);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 3;
switch(type) {
case 'home':
// 绘制房子图标
ctx.beginPath();
// 屋顶
ctx.moveTo(40.5, 15);
ctx.lineTo(20, 35);
ctx.lineTo(61, 35);
ctx.closePath();
ctx.fill();
// 房子主体
ctx.fillRect(25, 35, 31, 30);
// 门
ctx.fillStyle = 'white';
ctx.fillRect(35, 50, 11, 15);
break;
case 'stats':
// 绘制统计图标(柱状图)
ctx.fillRect(20, 45, 12, 20);
ctx.fillRect(35, 35, 12, 30);
ctx.fillRect(50, 25, 12, 40);
break;
case 'profile':
// 绘制个人图标
// 头部
ctx.beginPath();
ctx.arc(40.5, 25, 10, 0, 2 * Math.PI);
ctx.fill();
// 身体
ctx.beginPath();
ctx.arc(40.5, 55, 18, Math.PI, 0, true);
ctx.fill();
break;
}
}
// 创建图标
function createIcons() {
const container = document.getElementById('iconGrid');
icons.forEach(icon => {
const div = document.createElement('div');
div.className = 'icon-item';
const canvas = document.createElement('canvas');
canvas.width = 81;
canvas.height = 81;
canvas.id = icon.name;
const ctx = canvas.getContext('2d');
const type = icon.name.replace('-active', '');
drawIcon(ctx, type, icon.color);
const title = document.createElement('h3');
title.textContent = icon.label;
const downloadBtn = document.createElement('a');
downloadBtn.className = 'download-btn';
downloadBtn.textContent = '下载';
downloadBtn.download = icon.name + '.png';
downloadBtn.href = canvas.toDataURL('image/png');
div.appendChild(title);
div.appendChild(canvas);
div.appendChild(document.createElement('br'));
div.appendChild(downloadBtn);
container.appendChild(div);
});
}
// 下载所有图标
function downloadAll() {
icons.forEach((icon, index) => {
setTimeout(() => {
const canvas = document.getElementById(icon.name);
const link = document.createElement('a');
link.download = icon.name + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
}, index * 200); // 延迟下载,避免浏览器阻止
});
}
// 页面加载完成后创建图标
window.onload = createIcons;
</script>
</body>
</html>

BIN
images/default-avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<circle cx="40.5" cy="25" r="12" fill="#cccccc"/>
<ellipse cx="40.5" cy="55" rx="20" ry="15" fill="#cccccc"/>
</svg>

After

Width:  |  Height:  |  Size: 247 B

BIN
images/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

5
images/empty.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<path d="M40.5 15 L20 35 L25 35 L25 65 L35 65 L35 50 L46 50 L46 65 L56 65 L56 35 L61 35 Z"
fill="#dddddd" />
</svg>

After

Width:  |  Height:  |  Size: 252 B

BIN
images/home-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

5
images/home-active.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<path d="M40.5 15 L20 35 L25 35 L25 65 L35 65 L35 50 L46 50 L46 65 L56 65 L56 35 L61 35 Z"
fill="#07c160" />
</svg>

After

Width:  |  Height:  |  Size: 252 B

BIN
images/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

5
images/home.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<path d="M40.5 15 L20 35 L25 35 L25 65 L35 65 L35 50 L46 50 L46 65 L56 65 L56 35 L61 35 Z"
fill="#999999" />
</svg>

After

Width:  |  Height:  |  Size: 252 B

23
images/logo.svg Normal file
View File

@ -0,0 +1,23 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<!-- 圆形背景 -->
<circle cx="100" cy="100" r="90" fill="#07c160"/>
<!-- 白色内圆 -->
<circle cx="100" cy="100" r="75" fill="white"/>
<!-- 麻将图标 - 简化设计 -->
<!-- 麻将牌外框 -->
<rect x="60" y="50" width="80" height="100" rx="8" fill="#07c160" stroke="#fff" stroke-width="2"/>
<!-- 麻将牌内部装饰 -->
<rect x="70" y="60" width="60" height="10" fill="white" opacity="0.9"/>
<rect x="70" y="130" width="60" height="10" fill="white" opacity="0.9"/>
<!-- 中文"记账"字样区域(简化表示) -->
<circle cx="80" cy="95" r="8" fill="white"/>
<circle cx="100" cy="95" r="8" fill="white"/>
<circle cx="120" cy="95" r="8" fill="white"/>
<circle cx="90" cy="110" r="6" fill="white"/>
<circle cx="110" cy="110" r="6" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 873 B

BIN
images/profile-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<circle cx="40.5" cy="25" r="12" fill="#07c160"/>
<ellipse cx="40.5" cy="55" rx="20" ry="15" fill="#07c160"/>
</svg>

After

Width:  |  Height:  |  Size: 247 B

BIN
images/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

5
images/profile.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<circle cx="40.5" cy="25" r="12" fill="#999999"/>
<ellipse cx="40.5" cy="55" rx="20" ry="15" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 247 B

BIN
images/stats-active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

6
images/stats-active.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<rect x="15" y="45" width="15" height="20" fill="#07c160"/>
<rect x="33" y="35" width="15" height="30" fill="#07c160"/>
<rect x="51" y="25" width="15" height="40" fill="#07c160"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

BIN
images/stats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

6
images/stats.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81" height="81" viewBox="0 0 81 81" xmlns="http://www.w3.org/2000/svg">
<rect x="15" y="45" width="15" height="20" fill="#999999"/>
<rect x="33" y="35" width="15" height="30" fill="#999999"/>
<rect x="51" y="25" width="15" height="40" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

295
miniprogram_npm/@jdmini/api/index.d.ts vendored Normal file
View File

@ -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<void>;
export function showPage(options: PageOptions | undefined, pageId: string): Promise<void>;
export const checkTokenValid: () => boolean;
/**
*
* @param {Function} callback -
* @returns {void}
*/
export function onLoginReady(callback: (...args: any[]) => void): void;
/**
*
* @returns {Promise<void>}
*/
export function waitLogin(): Promise<void>;
export function login(): Promise<void>;
export function fetchEchoData(): Promise<void>;
export function trackVisit(): Promise<void>;
}
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<Object>} Promise对象
*/
request<T = any>(path: string, method?: WechatMiniprogram.RequestOption['method'], data?: Record<string, any>, options?: RequestOptions): Promise<ApiResponse<T>>;
/**
*
* @param {string} filePath
* @param {Object} data , {}
* @param {'avatar' | 'file'} type , 'file'
* @returns {Promise<Object>} Promise对象
*/
uploadFile<T = any>(filePath: string, data?: Record<string, any>, type?: 'avatar' | 'file'): Promise<ApiResponse<T>>;
/**
*
* @param {string} filePath
* @param {Object} data , {}
* @param {'avatar' | 'file'} type , 'file'
* @returns {Promise<Object>} Promise对象
*/
upload<T = any>(path: string, filePath: string, data?: Record<string, any>): Promise<ApiResponse<T>>;
/**
*
* @param {number} fileId id
* @returns {Promise<Object>} Promise对象
*/
deleteFile(fileId: number): Promise<ApiResponse<null>>;
/**
*
* @param {string} filePath
* @returns {Promise<Object>} Promise对象
*/
uploadAvatar<T = any>(filePath: string): Promise<ApiResponse<T>>;
}
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<void>;
[key: string]: any;
}
interface PageConfig {
onShow?: (...args: any[]) => void | Promise<void>;
[key: string]: any;
}
interface ComponentConfig {
methods?: {
onLoad?: (...args: any[]) => void | Promise<void>;
onShow?: (...args: any[]) => void | Promise<void>;
[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<Record<AdData['appPage'], AdData['ads'][0]['adUnitId']>>;
class AdManager {
/**
* 广
*/
ads: Ads;
/**
*
*/
link: LinkData[];
/**
* 广
*/
top: TopData | null;
constructor();
/**
* 广
* @param {Function} callback -
* @returns {void}
*/
onDataReady: (callback: (...args: any[]) => void) => void;
/**
* 广
* @returns {Promise<void>}
*/
waitAdData: () => Promise<void>;
/**
* 广
* @returns {void}
*/
init: () => void;
/**
* 广
* @returns {Promise<void>}
*/
createAndShowInterstitialAd: () => Promise<void>;
/**
* 广
* @param {any} context -
* @param {string} [pageId] - ID
* @returns {Promise<boolean>}
*/
createAndShowRewardedVideoAd: (context: any, pageId?: string) => Promise<boolean>;
}
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<string, string>;
[key: string]: any;
}
export interface LoginData {
appId: number;
code: string;
brand: string;
model: string;
platform: string;
}
export interface ApiResponse<T = any> {
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;
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,22 @@
import { adManager } from '@jdmini/api'
Component({
properties: {
type: {
type: String,
value: 'custom' // 可选banner, video, custom
}
},
data: {
ads: {}
},
lifetimes: {
attached: function () {
adManager.onDataReady(() => {
this.setData({ ads: adManager.ads })
})
},
},
methods: {
}
})

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,5 @@
<view class="jdwx-ad-component">
<ad wx:if="{{type === 'banner' && ads.banner}}" class="jdwx-ad-item" unit-id="{{ads.banner}}"></ad>
<ad wx:if="{{type === 'video' && ads.video}}" class="jdwx-ad-item" ad-type="video" unit-id="{{ads.video}}"></ad>
<ad-custom wx:if="{{type === 'custom' && ads.custom}}" class="jdwx-ad-item" unit-id="{{ads.custom}}"></ad-custom>
</view>

View File

@ -0,0 +1,7 @@
.jdwx-ad-component {
padding: 10rpx;
}
.jdwx-ad-item {
bottom: 10rpx;
}

View File

@ -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
});
},
}
})

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,11 @@
<view class="jdwx-link-component">
<view wx:if="{{top.appId}}" class="jdwx-applink-top" bindtap="gotopLink">
<view><image src="{{top.appLogo}}" class="jdwx-applink-icon" /> </view>
<view ><text class="jdwx-applink-top-linkname">{{top.linkName}}</text>
<text class="jdwx-applink-top-text">{{top.appDsc}}</text> </view>
</view>
<view id="{{bindex}}" bindtap="goLink" wx:for="{{link}}" wx:for-index="bindex" wx:key="index" class="jdwx-applink-list">
<image src="{{item.appLogo}}" class="jdwx-applink-icon" />
<text class="jdwx-applink-text">{{item.linkName}}</text>
</view>
</view>

View File

@ -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);
}

View File

@ -0,0 +1,130 @@
module.exports = (function() {
var __MODS__ = {};
var __DEFINE__ = function(modId, func, req) { var m = { exports: {}, _tempexports: {} }; __MODS__[modId] = { status: 0, func: func, req: req, m: m }; };
var __REQUIRE__ = function(modId, source) { if(!__MODS__[modId]) return require(source); if(!__MODS__[modId].status) { var m = __MODS__[modId].m; m._exports = m._tempexports; var desp = Object.getOwnPropertyDescriptor(m, "exports"); if (desp && desp.configurable) Object.defineProperty(m, "exports", { set: function (val) { if(typeof val === "object" && val !== m._exports) { m._exports.__proto__ = val.__proto__; Object.keys(val).forEach(function (k) { m._exports[k] = val[k]; }); } m._tempexports = val }, get: function () { return m._tempexports; } }); __MODS__[modId].status = 1; __MODS__[modId].func(__MODS__[modId].req, m, m.exports); } return __MODS__[modId].m.exports; };
var __REQUIRE_WILDCARD__ = function(obj) { if(obj && obj.__esModule) { return obj; } else { var newObj = {}; if(obj != null) { for(var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) newObj[k] = obj[k]; } } newObj.default = obj; return newObj; } };
var __REQUIRE_DEFAULT__ = function(obj) { return obj && obj.__esModule ? obj.default : obj; };
__DEFINE__(1761629501967, function(require, module, exports) {
var hasOwn = Object.prototype.hasOwnProperty;
var toStr = Object.prototype.toString;
var defineProperty = Object.defineProperty;
var gOPD = Object.getOwnPropertyDescriptor;
var isArray = function isArray(arr) {
if (typeof Array.isArray === 'function') {
return Array.isArray(arr);
}
return toStr.call(arr) === '[object Array]';
};
var isPlainObject = function isPlainObject(obj) {
if (!obj || toStr.call(obj) !== '[object Object]') {
return false;
}
var hasOwnConstructor = hasOwn.call(obj, 'constructor');
var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
// Not own constructor property must be Object
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) { /**/ }
return typeof key === 'undefined' || hasOwn.call(obj, key);
};
// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target
var setProperty = function setProperty(target, options) {
if (defineProperty && options.name === '__proto__') {
defineProperty(target, options.name, {
enumerable: true,
configurable: true,
value: options.newValue,
writable: true
});
} else {
target[options.name] = options.newValue;
}
};
// Return undefined instead of __proto__ if '__proto__' is not an own property
var getProperty = function getProperty(obj, name) {
if (name === '__proto__') {
if (!hasOwn.call(obj, name)) {
return void 0;
} else if (gOPD) {
// In early versions of node, obj['__proto__'] is buggy when obj has
// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.
return gOPD(obj, name).value;
}
}
return obj[name];
};
module.exports = function extend() {
var options, name, src, copy, copyIsArray, clone;
var target = arguments[0];
var i = 1;
var length = arguments.length;
var deep = false;
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
if (target == null || (typeof target !== 'object' && typeof target !== 'function')) {
target = {};
}
for (; i < length; ++i) {
options = arguments[i];
// Only deal with non-null/undefined values
if (options != null) {
// Extend the base object
for (name in options) {
src = getProperty(target, name);
copy = getProperty(options, name);
// Prevent never-ending loop
if (target !== copy) {
// Recurse if we're merging plain objects or arrays
if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
setProperty(target, { name: name, newValue: extend(deep, clone, copy) });
// Don't bring in undefined values
} else if (typeof copy !== 'undefined') {
setProperty(target, { name: name, newValue: copy });
}
}
}
}
}
// Return the modified object
return target;
};
}, function(modId) {var map = {}; return __REQUIRE__(map[modId], modId); })
return __REQUIRE__(1761629501967);
})()
//miniprogram-npm-outsideDeps=[]
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["index.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js","sourcesContent":["\n\nvar hasOwn = Object.prototype.hasOwnProperty;\nvar toStr = Object.prototype.toString;\nvar defineProperty = Object.defineProperty;\nvar gOPD = Object.getOwnPropertyDescriptor;\n\nvar isArray = function isArray(arr) {\n\tif (typeof Array.isArray === 'function') {\n\t\treturn Array.isArray(arr);\n\t}\n\n\treturn toStr.call(arr) === '[object Array]';\n};\n\nvar isPlainObject = function isPlainObject(obj) {\n\tif (!obj || toStr.call(obj) !== '[object Object]') {\n\t\treturn false;\n\t}\n\n\tvar hasOwnConstructor = hasOwn.call(obj, 'constructor');\n\tvar hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');\n\t// Not own constructor property must be Object\n\tif (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {\n\t\treturn false;\n\t}\n\n\t// Own properties are enumerated firstly, so to speed up,\n\t// if last one is own, then all properties are own.\n\tvar key;\n\tfor (key in obj) { /**/ }\n\n\treturn typeof key === 'undefined' || hasOwn.call(obj, key);\n};\n\n// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target\nvar setProperty = function setProperty(target, options) {\n\tif (defineProperty && options.name === '__proto__') {\n\t\tdefineProperty(target, options.name, {\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\t\t\tvalue: options.newValue,\n\t\t\twritable: true\n\t\t});\n\t} else {\n\t\ttarget[options.name] = options.newValue;\n\t}\n};\n\n// Return undefined instead of __proto__ if '__proto__' is not an own property\nvar getProperty = function getProperty(obj, name) {\n\tif (name === '__proto__') {\n\t\tif (!hasOwn.call(obj, name)) {\n\t\t\treturn void 0;\n\t\t} else if (gOPD) {\n\t\t\t// In early versions of node, obj['__proto__'] is buggy when obj has\n\t\t\t// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.\n\t\t\treturn gOPD(obj, name).value;\n\t\t}\n\t}\n\n\treturn obj[name];\n};\n\nmodule.exports = function extend() {\n\tvar options, name, src, copy, copyIsArray, clone;\n\tvar target = arguments[0];\n\tvar i = 1;\n\tvar length = arguments.length;\n\tvar deep = false;\n\n\t// Handle a deep copy situation\n\tif (typeof target === 'boolean') {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\tif (target == null || (typeof target !== 'object' && typeof target !== 'function')) {\n\t\ttarget = {};\n\t}\n\n\tfor (; i < length; ++i) {\n\t\toptions = arguments[i];\n\t\t// Only deal with non-null/undefined values\n\t\tif (options != null) {\n\t\t\t// Extend the base object\n\t\t\tfor (name in options) {\n\t\t\t\tsrc = getProperty(target, name);\n\t\t\t\tcopy = getProperty(options, name);\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif (target !== copy) {\n\t\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\t\tif (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {\n\t\t\t\t\t\tif (copyIsArray) {\n\t\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\t\tclone = src && isArray(src) ? src : [];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tclone = src && isPlainObject(src) ? src : {};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\t\tsetProperty(target, { name: name, newValue: extend(deep, clone, copy) });\n\n\t\t\t\t\t// Don't bring in undefined values\n\t\t\t\t\t} else if (typeof copy !== 'undefined') {\n\t\t\t\t\t\tsetProperty(target, { name: name, newValue: copy });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n"]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

33
node_modules/.package-lock.json generated vendored Normal file
View File

@ -0,0 +1,33 @@
{
"name": "miniapp",
"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"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/weapp-qrcode": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/weapp-qrcode/-/weapp-qrcode-1.0.0.tgz",
"integrity": "sha512-4sa3W0rGDVJ9QaeZpAKlAuUxVyjhDwiUqHyGK/jJMsRMXnhb4yO8qWU/pZruMo+iT5J6CraS67lDMFb1VY+RaA==",
"dependencies": {
"extend": "^3.0.2"
}
}
}
}

252
node_modules/@jdmini/api/README.md generated vendored Normal file
View File

@ -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, dataoptions)
// 头像上传。参数:文件路径
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) => {
})
}
})
```

295
node_modules/@jdmini/api/miniprogram_dist/index.d.ts generated vendored Normal file
View File

@ -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<void>;
export function showPage(options: PageOptions | undefined, pageId: string): Promise<void>;
export const checkTokenValid: () => boolean;
/**
*
* @param {Function} callback -
* @returns {void}
*/
export function onLoginReady(callback: (...args: any[]) => void): void;
/**
*
* @returns {Promise<void>}
*/
export function waitLogin(): Promise<void>;
export function login(): Promise<void>;
export function fetchEchoData(): Promise<void>;
export function trackVisit(): Promise<void>;
}
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<Object>} Promise对象
*/
request<T = any>(path: string, method?: WechatMiniprogram.RequestOption['method'], data?: Record<string, any>, options?: RequestOptions): Promise<ApiResponse<T>>;
/**
*
* @param {string} filePath
* @param {Object} data , {}
* @param {'avatar' | 'file'} type , 'file'
* @returns {Promise<Object>} Promise对象
*/
uploadFile<T = any>(filePath: string, data?: Record<string, any>, type?: 'avatar' | 'file'): Promise<ApiResponse<T>>;
/**
*
* @param {string} filePath
* @param {Object} data , {}
* @param {'avatar' | 'file'} type , 'file'
* @returns {Promise<Object>} Promise对象
*/
upload<T = any>(path: string, filePath: string, data?: Record<string, any>): Promise<ApiResponse<T>>;
/**
*
* @param {number} fileId id
* @returns {Promise<Object>} Promise对象
*/
deleteFile(fileId: number): Promise<ApiResponse<null>>;
/**
*
* @param {string} filePath
* @returns {Promise<Object>} Promise对象
*/
uploadAvatar<T = any>(filePath: string): Promise<ApiResponse<T>>;
}
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<void>;
[key: string]: any;
}
interface PageConfig {
onShow?: (...args: any[]) => void | Promise<void>;
[key: string]: any;
}
interface ComponentConfig {
methods?: {
onLoad?: (...args: any[]) => void | Promise<void>;
onShow?: (...args: any[]) => void | Promise<void>;
[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<Record<AdData['appPage'], AdData['ads'][0]['adUnitId']>>;
class AdManager {
/**
* 广
*/
ads: Ads;
/**
*
*/
link: LinkData[];
/**
* 广
*/
top: TopData | null;
constructor();
/**
* 广
* @param {Function} callback -
* @returns {void}
*/
onDataReady: (callback: (...args: any[]) => void) => void;
/**
* 广
* @returns {Promise<void>}
*/
waitAdData: () => Promise<void>;
/**
* 广
* @returns {void}
*/
init: () => void;
/**
* 广
* @returns {Promise<void>}
*/
createAndShowInterstitialAd: () => Promise<void>;
/**
* 广
* @param {any} context -
* @param {string} [pageId] - ID
* @returns {Promise<boolean>}
*/
createAndShowRewardedVideoAd: (context: any, pageId?: string) => Promise<boolean>;
}
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<string, string>;
[key: string]: any;
}
export interface LoginData {
appId: number;
code: string;
brand: string;
model: string;
platform: string;
}
export interface ApiResponse<T = any> {
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;
}
}

4
node_modules/@jdmini/api/miniprogram_dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

24
node_modules/@jdmini/api/package.json generated vendored Normal file
View File

@ -0,0 +1,24 @@
{
"name": "@jdmini/api",
"version": "1.0.10",
"main": "miniprogram_dist/index.js",
"files": [
"miniprogram_dist"
],
"scripts": {
"build": "webpack",
"pub": "npm run build && npm publish --access public",
"build:js": "tsc --project tsconfig_tsc.json"
},
"miniprogram": "miniprogram_dist",
"author": "",
"description": "",
"devDependencies": {
"@types/wechat-miniprogram": "^3.4.8",
"dts-bundle": "^0.7.3",
"ts-loader": "^9.5.1",
"typescript": "^5.6.3",
"webpack": "^5.96.1",
"webpack-cli": "^5.1.4"
}
}

31
node_modules/@jdmini/components/README.md generated vendored Normal file
View File

@ -0,0 +1,31 @@
## 安装/更新
1、终端使用命令安装 npm 包
```bash
npm install --save @jdmini/components@latest
```
2、在小程序开发者工具中菜单选择工具 -> 构建 npm
`注意:依赖@jdmini/api请确保小程序项目已安装@jdmini/api`
## 使用
1、在页面的 json 文件中引入组件:
```json
{
"usingComponents": {
"jdwx-ad": "@jdmini/components/jdwx-ad",
"jdwx-link": "@jdmini/components/jdwx-link"
}
}
```
2、在页面的 wxml 文件中使用组件:
```html
<jdwx-ad type="custom" />
<jdwx-link />
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,22 @@
import { adManager } from '@jdmini/api'
Component({
properties: {
type: {
type: String,
value: 'custom' // 可选banner, video, custom
}
},
data: {
ads: {}
},
lifetimes: {
attached: function () {
adManager.onDataReady(() => {
this.setData({ ads: adManager.ads })
})
},
},
methods: {
}
})

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,5 @@
<view class="jdwx-ad-component">
<ad wx:if="{{type === 'banner' && ads.banner}}" class="jdwx-ad-item" unit-id="{{ads.banner}}"></ad>
<ad wx:if="{{type === 'video' && ads.video}}" class="jdwx-ad-item" ad-type="video" unit-id="{{ads.video}}"></ad>
<ad-custom wx:if="{{type === 'custom' && ads.custom}}" class="jdwx-ad-item" unit-id="{{ads.custom}}"></ad-custom>
</view>

View File

@ -0,0 +1,7 @@
.jdwx-ad-component {
padding: 10rpx;
}
.jdwx-ad-item {
bottom: 10rpx;
}

View File

@ -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
});
},
}
})

View File

@ -0,0 +1,3 @@
{
"component": true
}

View File

@ -0,0 +1,11 @@
<view class="jdwx-link-component">
<view wx:if="{{top.appId}}" class="jdwx-applink-top" bindtap="gotopLink">
<view><image src="{{top.appLogo}}" class="jdwx-applink-icon" /> </view>
<view ><text class="jdwx-applink-top-linkname">{{top.linkName}}</text>
<text class="jdwx-applink-top-text">{{top.appDsc}}</text> </view>
</view>
<view id="{{bindex}}" bindtap="goLink" wx:for="{{link}}" wx:for-index="bindex" wx:key="index" class="jdwx-applink-list">
<image src="{{item.appLogo}}" class="jdwx-applink-icon" />
<text class="jdwx-applink-text">{{item.linkName}}</text>
</view>
</view>

View File

@ -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);
}

20
node_modules/@jdmini/components/package.json generated vendored Normal file
View File

@ -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"
}
}

20
node_modules/extend/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,20 @@
root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 150
[CHANGELOG.md]
indent_style = space
indent_size = 2
[*.json]
max_line_length = off
[Makefile]
max_line_length = off

17
node_modules/extend/.eslintrc generated vendored Normal file
View File

@ -0,0 +1,17 @@
{
"root": true,
"extends": "@ljharb",
"rules": {
"complexity": [2, 20],
"eqeqeq": [2, "allow-null"],
"func-name-matching": [1],
"max-depth": [1, 4],
"max-statements": [2, 26],
"no-extra-parens": [1],
"no-magic-numbers": [0],
"no-restricted-syntax": [2, "BreakStatement", "ContinueStatement", "DebuggerStatement", "LabeledStatement", "WithStatement"],
"sort-keys": [0],
}
}

175
node_modules/extend/.jscs.json generated vendored Normal file
View File

@ -0,0 +1,175 @@
{
"es3": true,
"additionalRules": [],
"requireSemicolons": true,
"disallowMultipleSpaces": true,
"disallowIdentifierNames": [],
"requireCurlyBraces": {
"allExcept": [],
"keywords": ["if", "else", "for", "while", "do", "try", "catch"]
},
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch", "function"],
"disallowSpaceAfterKeywords": [],
"disallowSpaceBeforeComma": true,
"disallowSpaceAfterComma": false,
"disallowSpaceBeforeSemicolon": true,
"disallowNodeTypes": [
"DebuggerStatement",
"LabeledStatement",
"SwitchCase",
"SwitchStatement",
"WithStatement"
],
"requireObjectKeysOnNewLine": { "allExcept": ["sameLine"] },
"requireSpacesInAnonymousFunctionExpression": { "beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true },
"requireSpacesInNamedFunctionExpression": { "beforeOpeningCurlyBrace": true },
"disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true },
"requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true },
"disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true },
"requireSpaceBetweenArguments": true,
"disallowSpacesInsideParentheses": true,
"disallowSpacesInsideArrayBrackets": true,
"disallowQuotedKeysInObjects": { "allExcept": ["reserved"] },
"disallowSpaceAfterObjectKeys": true,
"requireCommaBeforeLineBreak": true,
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"requireSpaceAfterPrefixUnaryOperators": [],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"requireSpaceBeforePostfixUnaryOperators": [],
"disallowSpaceBeforeBinaryOperators": [],
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
"requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
"disallowSpaceAfterBinaryOperators": [],
"disallowImplicitTypeConversion": ["binary", "string"],
"disallowKeywords": ["with", "eval"],
"requireKeywordsOnNewLine": [],
"disallowKeywordsOnNewLine": ["else"],
"requireLineFeedAtFileEnd": true,
"disallowTrailingWhitespace": true,
"disallowTrailingComma": true,
"excludeFiles": ["node_modules/**", "vendor/**"],
"disallowMultipleLineStrings": true,
"requireDotNotation": { "allExcept": ["keywords"] },
"requireParenthesesAroundIIFE": true,
"validateLineBreaks": "LF",
"validateQuoteMarks": {
"escape": true,
"mark": "'"
},
"disallowOperatorBeforeLineBreak": [],
"requireSpaceBeforeKeywords": [
"do",
"for",
"if",
"else",
"switch",
"case",
"try",
"catch",
"finally",
"while",
"with",
"return"
],
"validateAlignedFunctionParameters": {
"lineBreakAfterOpeningBraces": true,
"lineBreakBeforeClosingBraces": true
},
"requirePaddingNewLinesBeforeExport": true,
"validateNewlineAfterArrayElements": {
"maximum": 6
},
"requirePaddingNewLinesAfterUseStrict": true,
"disallowArrowFunctions": true,
"disallowMultiLineTernary": true,
"validateOrderInObjectKeys": false,
"disallowIdenticalDestructuringNames": true,
"disallowNestedTernaries": { "maxLevel": 1 },
"requireSpaceAfterComma": { "allExcept": ["trailing"] },
"requireAlignedMultilineParams": false,
"requireSpacesInGenerator": {
"afterStar": true
},
"disallowSpacesInGenerator": {
"beforeStar": true
},
"disallowVar": false,
"requireArrayDestructuring": false,
"requireEnhancedObjectLiterals": false,
"requireObjectDestructuring": false,
"requireEarlyReturn": false,
"requireCapitalizedConstructorsNew": {
"allExcept": ["Function", "String", "Object", "Symbol", "Number", "Date", "RegExp", "Error", "Boolean", "Array"]
},
"requireImportAlphabetized": false,
"requireSpaceBeforeObjectValues": true,
"requireSpaceBeforeDestructuredValues": true,
"disallowSpacesInsideTemplateStringPlaceholders": true,
"disallowArrayDestructuringReturn": false,
"requireNewlineBeforeSingleStatementsInIf": false,
"disallowUnusedVariables": true,
"requireSpacesInsideImportedObjectBraces": true,
"requireUseStrict": true
}

230
node_modules/extend/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,230 @@
language: node_js
os:
- linux
node_js:
- "10.7"
- "9.11"
- "8.11"
- "7.10"
- "6.14"
- "5.12"
- "4.9"
- "iojs-v3.3"
- "iojs-v2.5"
- "iojs-v1.8"
- "0.12"
- "0.10"
- "0.8"
before_install:
- 'case "${TRAVIS_NODE_VERSION}" in 0.*) export NPM_CONFIG_STRICT_SSL=false ;; esac'
- 'nvm install-latest-npm'
install:
- 'if [ "${TRAVIS_NODE_VERSION}" = "0.6" ] || [ "${TRAVIS_NODE_VERSION}" = "0.9" ]; then nvm install --latest-npm 0.8 && npm install && nvm use "${TRAVIS_NODE_VERSION}"; else npm install; fi;'
script:
- 'if [ -n "${PRETEST-}" ]; then npm run pretest ; fi'
- 'if [ -n "${POSTTEST-}" ]; then npm run posttest ; fi'
- 'if [ -n "${COVERAGE-}" ]; then npm run coverage ; fi'
- 'if [ -n "${TEST-}" ]; then npm run tests-only ; fi'
sudo: false
env:
- TEST=true
matrix:
fast_finish: true
include:
- node_js: "lts/*"
env: PRETEST=true
- node_js: "lts/*"
env: POSTTEST=true
- node_js: "4"
env: COVERAGE=true
- node_js: "10.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "10.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "10.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "10.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "10.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "10.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "10.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.10"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.9"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.8"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "9.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.10"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.9"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.8"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "8.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.9"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.8"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "7.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.13"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.12"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.11"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.10"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.9"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.8"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "6.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.11"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.10"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.9"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.8"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "5.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.8"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "4.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v3.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v3.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v3.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v2.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v2.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v2.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v2.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v2.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.7"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.5"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.4"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.3"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.2"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.1"
env: TEST=true ALLOW_FAILURE=true
- node_js: "iojs-v1.0"
env: TEST=true ALLOW_FAILURE=true
- node_js: "0.11"
env: TEST=true ALLOW_FAILURE=true
- node_js: "0.9"
env: TEST=true ALLOW_FAILURE=true
- node_js: "0.6"
env: TEST=true ALLOW_FAILURE=true
- node_js: "0.4"
env: TEST=true ALLOW_FAILURE=true
allow_failures:
- os: osx
- env: TEST=true ALLOW_FAILURE=true

83
node_modules/extend/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,83 @@
3.0.2 / 2018-07-19
==================
* [Fix] Prevent merging `__proto__` property (#48)
* [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`
* [Tests] up to `node` `v10.7`, `v9.11`, `v8.11`, `v7.10`, `v6.14`, `v4.9`; use `nvm install-latest-npm`
3.0.1 / 2017-04-27
==================
* [Fix] deep extending should work with a non-object (#46)
* [Dev Deps] update `tape`, `eslint`, `@ljharb/eslint-config`
* [Tests] up to `node` `v7.9`, `v6.10`, `v4.8`; improve matrix
* [Docs] Switch from vb.teelaun.ch to versionbadg.es for the npm version badge SVG.
* [Docs] Add example to readme (#34)
3.0.0 / 2015-07-01
==================
* [Possible breaking change] Use global "strict" directive (#32)
* [Tests] `int` is an ES3 reserved word
* [Tests] Test up to `io.js` `v2.3`
* [Tests] Add `npm run eslint`
* [Dev Deps] Update `covert`, `jscs`
2.0.1 / 2015-04-25
==================
* Use an inline `isArray` check, for ES3 browsers. (#27)
* Some old browsers fail when an identifier is `toString`
* Test latest `node` and `io.js` versions on `travis-ci`; speed up builds
* Add license info to package.json (#25)
* Update `tape`, `jscs`
* Adding a CHANGELOG
2.0.0 / 2014-10-01
==================
* Increase code coverage to 100%; run code coverage as part of tests
* Add `npm run lint`; Run linter as part of tests
* Remove nodeType and setInterval checks in isPlainObject
* Updating `tape`, `jscs`, `covert`
* General style and README cleanup
1.3.0 / 2014-06-20
==================
* Add component.json for browser support (#18)
* Use SVG for badges in README (#16)
* Updating `tape`, `covert`
* Updating travis-ci to work with multiple node versions
* Fix `deep === false` bug (returning target as {}) (#14)
* Fixing constructor checks in isPlainObject
* Adding additional test coverage
* Adding `npm run coverage`
* Add LICENSE (#13)
* Adding a warning about `false`, per #11
* General style and whitespace cleanup
1.2.1 / 2013-09-14
==================
* Fixing hasOwnProperty bugs that would only have shown up in specific browsers. Fixes #8
* Updating `tape`
1.2.0 / 2013-09-02
==================
* Updating the README: add badges
* Adding a missing variable reference.
* Using `tape` instead of `buster` for tests; add more tests (#7)
* Adding node 0.10 to Travis CI (#6)
* Enabling "npm test" and cleaning up package.json (#5)
* Add Travis CI.
1.1.3 / 2012-12-06
==================
* Added unit tests.
* Ensure extend function is named. (Looks nicer in a stack trace.)
* README cleanup.
1.1.1 / 2012-11-07
==================
* README cleanup.
* Added installation instructions.
* Added a missing semicolon
1.0.0 / 2012-04-08
==================
* Initial commit

23
node_modules/extend/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014 Stefan Thomas
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

81
node_modules/extend/README.md generated vendored Normal file
View File

@ -0,0 +1,81 @@
[![Build Status][travis-svg]][travis-url]
[![dependency status][deps-svg]][deps-url]
[![dev dependency status][dev-deps-svg]][dev-deps-url]
# extend() for Node.js <sup>[![Version Badge][npm-version-png]][npm-url]</sup>
`node-extend` is a port of the classic extend() method from jQuery. It behaves as you expect. It is simple, tried and true.
Notes:
* Since Node.js >= 4,
[`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
now offers the same functionality natively (but without the "deep copy" option).
See [ECMAScript 2015 (ES6) in Node.js](https://nodejs.org/en/docs/es6).
* Some native implementations of `Object.assign` in both Node.js and many
browsers (since NPM modules are for the browser too) may not be fully
spec-compliant.
Check [`object.assign`](https://www.npmjs.com/package/object.assign) module for
a compliant candidate.
## Installation
This package is available on [npm][npm-url] as: `extend`
``` sh
npm install extend
```
## Usage
**Syntax:** extend **(** [`deep`], `target`, `object1`, [`objectN`] **)**
*Extend one object with one or more others, returning the modified object.*
**Example:**
``` js
var extend = require('extend');
extend(targetObject, object1, object2);
```
Keep in mind that the target object will be modified, and will be returned from extend().
If a boolean true is specified as the first argument, extend performs a deep copy, recursively copying any objects it finds. Otherwise, the copy will share structure with the original object(s).
Undefined properties are not copied. However, properties inherited from the object's prototype will be copied over.
Warning: passing `false` as the first argument is not supported.
### Arguments
* `deep` *Boolean* (optional)
If set, the merge becomes recursive (i.e. deep copy).
* `target` *Object*
The object to extend.
* `object1` *Object*
The object that will be merged into the first.
* `objectN` *Object* (Optional)
More objects to merge into the first.
## License
`node-extend` is licensed under the [MIT License][mit-license-url].
## Acknowledgements
All credit to the jQuery authors for perfecting this amazing utility.
Ported to Node.js by [Stefan Thomas][github-justmoon] with contributions by [Jonathan Buchanan][github-insin] and [Jordan Harband][github-ljharb].
[travis-svg]: https://travis-ci.org/justmoon/node-extend.svg
[travis-url]: https://travis-ci.org/justmoon/node-extend
[npm-url]: https://npmjs.org/package/extend
[mit-license-url]: http://opensource.org/licenses/MIT
[github-justmoon]: https://github.com/justmoon
[github-insin]: https://github.com/insin
[github-ljharb]: https://github.com/ljharb
[npm-version-png]: http://versionbadg.es/justmoon/node-extend.svg
[deps-svg]: https://david-dm.org/justmoon/node-extend.svg
[deps-url]: https://david-dm.org/justmoon/node-extend
[dev-deps-svg]: https://david-dm.org/justmoon/node-extend/dev-status.svg
[dev-deps-url]: https://david-dm.org/justmoon/node-extend#info=devDependencies

32
node_modules/extend/component.json generated vendored Normal file
View File

@ -0,0 +1,32 @@
{
"name": "extend",
"author": "Stefan Thomas <justmoon@members.fsf.org> (http://www.justmoon.net)",
"version": "3.0.0",
"description": "Port of jQuery.extend for node.js and the browser.",
"scripts": [
"index.js"
],
"contributors": [
{
"name": "Jordan Harband",
"url": "https://github.com/ljharb"
}
],
"keywords": [
"extend",
"clone",
"merge"
],
"repository" : {
"type": "git",
"url": "https://github.com/justmoon/node-extend.git"
},
"dependencies": {
},
"devDependencies": {
"tape" : "~3.0.0",
"covert": "~0.4.0",
"jscs": "~1.6.2"
}
}

117
node_modules/extend/index.js generated vendored Normal file
View File

@ -0,0 +1,117 @@
'use strict';
var hasOwn = Object.prototype.hasOwnProperty;
var toStr = Object.prototype.toString;
var defineProperty = Object.defineProperty;
var gOPD = Object.getOwnPropertyDescriptor;
var isArray = function isArray(arr) {
if (typeof Array.isArray === 'function') {
return Array.isArray(arr);
}
return toStr.call(arr) === '[object Array]';
};
var isPlainObject = function isPlainObject(obj) {
if (!obj || toStr.call(obj) !== '[object Object]') {
return false;
}
var hasOwnConstructor = hasOwn.call(obj, 'constructor');
var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
// Not own constructor property must be Object
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) { /**/ }
return typeof key === 'undefined' || hasOwn.call(obj, key);
};
// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target
var setProperty = function setProperty(target, options) {
if (defineProperty && options.name === '__proto__') {
defineProperty(target, options.name, {
enumerable: true,
configurable: true,
value: options.newValue,
writable: true
});
} else {
target[options.name] = options.newValue;
}
};
// Return undefined instead of __proto__ if '__proto__' is not an own property
var getProperty = function getProperty(obj, name) {
if (name === '__proto__') {
if (!hasOwn.call(obj, name)) {
return void 0;
} else if (gOPD) {
// In early versions of node, obj['__proto__'] is buggy when obj has
// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.
return gOPD(obj, name).value;
}
}
return obj[name];
};
module.exports = function extend() {
var options, name, src, copy, copyIsArray, clone;
var target = arguments[0];
var i = 1;
var length = arguments.length;
var deep = false;
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
if (target == null || (typeof target !== 'object' && typeof target !== 'function')) {
target = {};
}
for (; i < length; ++i) {
options = arguments[i];
// Only deal with non-null/undefined values
if (options != null) {
// Extend the base object
for (name in options) {
src = getProperty(target, name);
copy = getProperty(options, name);
// Prevent never-ending loop
if (target !== copy) {
// Recurse if we're merging plain objects or arrays
if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
setProperty(target, { name: name, newValue: extend(deep, clone, copy) });
// Don't bring in undefined values
} else if (typeof copy !== 'undefined') {
setProperty(target, { name: name, newValue: copy });
}
}
}
}
}
// Return the modified object
return target;
};

42
node_modules/extend/package.json generated vendored Normal file
View File

@ -0,0 +1,42 @@
{
"name": "extend",
"author": "Stefan Thomas <justmoon@members.fsf.org> (http://www.justmoon.net)",
"version": "3.0.2",
"description": "Port of jQuery.extend for node.js and the browser",
"main": "index",
"scripts": {
"pretest": "npm run lint",
"test": "npm run tests-only",
"posttest": "npm run coverage-quiet",
"tests-only": "node test",
"coverage": "covert test/index.js",
"coverage-quiet": "covert test/index.js --quiet",
"lint": "npm run jscs && npm run eslint",
"jscs": "jscs *.js */*.js",
"eslint": "eslint *.js */*.js"
},
"contributors": [
{
"name": "Jordan Harband",
"url": "https://github.com/ljharb"
}
],
"keywords": [
"extend",
"clone",
"merge"
],
"repository": {
"type": "git",
"url": "https://github.com/justmoon/node-extend.git"
},
"dependencies": {},
"devDependencies": {
"@ljharb/eslint-config": "^12.2.1",
"covert": "^1.1.0",
"eslint": "^4.19.1",
"jscs": "^3.0.7",
"tape": "^4.9.1"
},
"license": "MIT"
}

9
node_modules/weapp-qrcode/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

2
node_modules/weapp-qrcode/.eslintignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
build/*.js
src/qrcode.js

26
node_modules/weapp-qrcode/.eslintrc.js generated vendored Normal file
View File

@ -0,0 +1,26 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
"globals": {
__VERSION__: false,
ENV: false,
wx: false
},
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
}

37
node_modules/weapp-qrcode/.github/ISSUE_TEMPLATE.md generated vendored Normal file
View File

@ -0,0 +1,37 @@
# Description
[问题简单描述]
# Environment
* Platform: [开发者工具/iOS/Andriod/Web]
* Platform version: [对应工具或者iOS或者Andriod的版本号]
* Wechat version: [微信版本号]
* weapp-qrcode version: [在package.json里]
* other version: [如果在某一设备下出现该问题,请填写设备号]
# Reproduce
**问题复现步骤:**
1. [第一步]
2. [第二步]
3. [其他步骤...]
**期望的表现:**
[在这里描述期望的表现]
**观察到的表现:**
[在这里描述观察到的表现]
**屏幕截图和动态 GIF 图**
![复现步骤的屏幕截图和动态 GIF 图](图片的 url)
# Relevant Code / Logs
```
// TODO(you): code or logs here to reproduce the problem
```

23
node_modules/weapp-qrcode/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,23 @@
language: node_js
sudo: required
node_js:
- 10.0.0
cache:
directories:
- node_modules
before_install:
- export TZ='Asia/Shanghai'
install:
- npm install
script:
- npm run publish
after_script:
- git init
- git config user.name "${USER_NAME}"
- git config user.email "${USER_EMAIL}"
- git add .
- git commit -m "publish"
- git push -f https://${access_token}@github.com/yingye/weapp-qrcode HEAD:master
branch: master

40
node_modules/weapp-qrcode/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,40 @@
# Change Log
Change log for weapp-qrcode. [Details at Github](https://github.com/yingye/weapp-qrcode)
## [1.0.0] - 2018-12-25
- 支持传入绘图上下文(CanvasContext).
## [1.0.0-beta] - 2018-12-13
- 支持二维码在 canvas 上绘制的起始位置.
- 支持在二维码上绘制图片及绘制位置.
## [0.9.0] - 2018-05-31
- 支持绘制带中文的二维码.
## [0.8.0] - 2018-05-15
- 绘制二维码后添加回调函数.
## [0.7.0] - 2018-05-11
- 支持在小程序组件中绘制二维码.
## [0.6.0] - 2018-04-16
- Add multi-output.
## [0.5.0] - 2018-03-11
- Add version in weapp.qrcode.js.
## [0.4.0] - 2018-03-10
- Fix options.
## [0.3.0] - 2018-02-03
- Initial release.

21
node_modules/weapp-qrcode/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 yingye
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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