6
0
Fork 0

添加活动功能

hui.zhou
h30830569 2022-06-02 14:24:10 +08:00
parent 15d0198b94
commit fcf7563f69
17 changed files with 2230 additions and 4 deletions

View File

@ -44,7 +44,7 @@
</cu-modal>
<!-- 确认收货弹窗 -->
<cu-modal v-model="receiptShow" title="确认收货" @confirm="receiptConfirm" confirm-color="#378264" show-cancel-button
content="确认收货后不能享受7天无理由退还货是否确认收货?"></cu-modal>
content="是否确认收货?"></cu-modal>
</view>
</template>
<script>

View File

@ -0,0 +1,200 @@
## 1.8.272022-06-01
本次更新:
- 优化示例项目
## 1.8.262022-06-01
本次更新:
- 修复奖品图片裁切为圆形时在安卓机器不显示的问题
## 1.8.252022-05-31
本次更新:
- 修复部分安卓下载图片得到.unknown格式文件的问题
## 1.8.242022-05-09
本地缓存:
- 优化示例项目
## 1.8.232022-05-09
本地缓存:
- 优化清除文件缓存的方法
## 1.8.222022-05-09
本次更新:
- 调整计算转盘绘制的方式
## 1.8.212022-05-08
本次更新:
- 调整示例项目中本地图片的引入方式
## 1.8.202022-04-29
本次更新:
- 修复转盘在某个临界点可以出现多次触发的问题
## 1.8.192022-04-27
本次更新:
- 奖品文字的绘制由先前的两行变成多行,根据设定的每行文字的长度分段绘制
## 1.8.182022-04-25
本次更新:
- 减少小程序平台的 delay
## 1.8.172022-03-23
本次更新:
- 新增配置项 `imgCircled` 奖品图片是否裁切为圆形,默认不裁切
## 1.8.162022-03-04
本次更新:
- 示例项目新增绘制时长的计算,方便开发时定位绘制慢的问题
## 1.8.152022-03-02
本次更新:
- 优化一处错误提示信息的展现方式
## 1.8.142021-11-29
本次更新:
- 示例项目中新增开放自定义权重最大值,没有自定义则取权重数组中的最大值
- 更新文档
## 1.8.132021-11-03
本次更新:
- 注释 `1.8.12` 版本中调试时的代码
## 1.8.122021-11-03
本次更新:
- 修复一些老机型不支持 `flex` 导致布局错乱的问题
## 1.8.112021-10-29
本次更新:
- 优化示例项目中模拟接口访问的速度
## 1.8.102021-10-19
本次更新:
- 优化组件代码
- 更新示例项目
## 1.8.92021-09-28
本次更新:
- 移除内置的 `奖品准备中...` 提示
## 1.8.82021-09-27
本次更新:
- 修复 `1.8.6` 引起的非微信小程序平台绘制异常的问题
## 1.8.72021-09-23
本次更新:
- 优化项目中使用到的图片大小
## 1.8.62021-09-23
本次更新:
- 修复小程序平台在绘制 `base64` 格式的图片时无法在真机模式下正常显示的问题
## 1.8.52021-09-13
本次更新:
- 修复一个已知问题
## 1.8.42021-09-12
本次更新:
- 调整 `strFontColor``strFontColors`,现在可以设置每个区块的文字颜色,详见文档说明
## 1.8.32021-09-12
本次更新:
- 修复因 `1.8.0` 改动引起的文字方向、无奖品图时绘制异常的问题
- 新增 `imgDrawed` 是否绘制奖品图片的配置项 ,默认为 `true`
## 1.8.22021-09-10
本次更新:
**不兼容旧版本的更新**
- 移除配置项 `strKey` 字段
- 调整 `prizeList` 结构
## 1.8.12021-09-06
本次更新:
- 修复 `hbx3.1.22` 在小程序平台处理 `id-name` 存在解析错误的问题
## 1.8.02021-09-06
本次更新:
**该版本更新涉及破坏性的变更,请重新查看 `API - Props` 的部分**
- `px` 全面调整为 `rpx` 单位,多个`Props` 的参数相应调整,请查看文档
- 新增 `pixelRatio` 参数,该参数为设计稿的设备像素比基准值,默认为 `2` 倍素
## 1.7.182021-09-05
本次更新:
- 修复一个已知问题
## 1.7.172021-08-23
本次更新:
- 更新示例项目
## 1.7.162021-08-14
本次更新:
- 更新示例项目
## 1.7.152021-08-03
本次更新:
- 新增文字竖向展示的功能,详见文档说明
## 1.7.132021-08-02
本次更新:
- 更新文档
## 1.7.122021-07-30
本次更新:
- 修复示例项目的已知问题
- 现已提供Almost-Lottery抽奖转盘的uniCloud云端一体页面模板
- 现已提供Almost-Lottery抽奖转盘云端一体页面配套的Admin配置中心
## 1.7.112021-07-22
本次更新:
- 修复部分安卓手机文字大小异常的问题
- 字段 `strHeightMultiple` 更换为 `strLineHeight`
## 1.7.102021-07-12
本次更新:
- 修复奖品名称 `name` 为空字符串时无法成功绘制转盘的问题
- 新增 `prizeNameDrawed` 是否绘制奖品名称的配置项,现在可以仅展示奖品图片了
## 1.7.92021-07-09
本次更新:
- 优化组件内部代码
- 修复奖品图片已然是 `base64` 格式时导致转盘绘制失败的问题
- 文档新增QQ群号让沟通更便捷
## 1.7.82021-07-08
本次更新:
- 调整 `Canvas` 默认宽高为 `280`
## 1.7.72021-07-08
本次更新:
- 新增多个配置项,满足更多自定义需求
- 优化多行文本情况下非中文字符的字节处理
- 修复偶发的第一条数据文本不居中显示的问题
## 1.7.62021-07-02
本次更新:
- 调整 `imageWidth``imageHeight` 字段为 `imgWidth``imgHeight`
- 更新示例项目
## 1.7.52021-07-01
本次更新:
- 新增配置项 `imgMarginStr` 奖品图片距离奖品文字的距离
## 1.7.42021-06-28
本次更新:
- 新增轮盘旋转或指针旋转配置项
- 转盘内置的外环图片以及按钮图片统一调整为 `image` 展示
- 更新相关文档说明
## 1.7.32021-06-16
本次更新:
- 优化错误提示
- 优化示例项目
- 优化文档说明
## 1.7.22021-06-11
本次更新:
- 新增 `canvasId` 参数配置项,多画板情况下需要配置不同的 `canvasId`
- 优化多画板情况下的缓存功能
- 优化示例项目
- 修改文档说明
## 1.7.12021-06-10
本次更新:
- 优化示例项目中的注释
## 1.7.02021-06-04
本次更新:
- 修复 `1.6.1` 引起的多行奖品文字行高异常的问题
- 新增配置转盘外环和抽奖按钮图片的功能,详见文档说明
- 更新示例项目,新增抽奖次数等业务有关的逻辑供参考
## 1.6.12021-05-28
本次更新:
- 修复小程序平台画板模糊的问题
## 1.6.02021-05-28
本次更新:
- 新增奖品区块是否开启描边的配置项,默认不开启
- 调整画板缓存为默认不开启
- 优化代码
- 优化文档说明
- 更新示例项目并修改部分注释
## 1.5.132021-05-22
本次更新:
- 优化文档说明
- 更新示例项目
## 1.5.122021-05-22
本次更新:
- 新增配置项 `strokeColor` 奖品区块边框颜色
- 更新文档说明
## 1.5.112021-05-19
本次更新:
- 新增`strMarginOutside`参数,用于设置奖品文字距离边缘的距离
- 修复奖品文字在某些情况下不是居中显示的问题
## 1.5.102021-05-19
本次更新:
- 修复示例项目中权重值相同时的取值逻辑
## 1.5.92021-05-14
本次更新:
- 调整代码,优化小程序端的展示
## 1.5.82021-05-12
本次更新:
- 文档增加预警提示:不再维护非 `uni_modules` 模式下的版本
## 1.5.72021-05-12
本次更新:
- 修复小程序平台奖品名称不清晰的问题
## 1.5.62021-03-18
本次更新:
- 适配 uni_modules 插件模式

View File

@ -0,0 +1,82 @@
{
"id": "almost-lottery",
"displayName": "Almost-Lottery抽奖转盘",
"version": "1.8.27",
"description": "【荣获2021插件大赛三等奖】提供奇数、缓存等众多配置项更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中完美支持APP、各平台小程序、H5、PC同时提供 uniCloud 云端版本",
"keywords": [
"转盘",
"抽奖",
"大转盘抽奖"
],
"repository": "https://github.com/ialmost/almost-components_uniapp",
"engines": {
"HBuilderX": "^3.1.22"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "n"
}
}
}
}
}

View File

@ -0,0 +1,160 @@
# almost-lottery
*使用 Canvas 绘制的抽奖转盘,提供奇数、缓存等众多配置项,更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中*
> <br />
>
> 如果用着还行,请支持一下
> - 前往 [GitHub](https://github.com/ialmost/almost-components_uniapp) 给个 Star
> - 前往 [UniApp](https://ext.dcloud.net.cn/plugin?id=1030) 给个五星
> - 使用中遇到问题时,可以添加 **QQ群 20441313**
>
> <br />
## 基于 uniCloud 开发的云端 Almost-Lottery 抽奖转盘,欢迎尝试体验
- [Almost-Lottery抽奖转盘的云端一体页面](https://ext.dcloud.net.cn/plugin?id=5763)
- [Almost-Lottery抽奖转盘的配置中心](https://ext.dcloud.net.cn/plugin?id=5762)
## 高能预警
- 本插件仅支持 `uni_modules` 模式,强烈推荐使用该模式,**非 `uni_modules` 模式不再维护**
- 在使用本插件之前,强烈建议使用 `HBuilderX` 导入示例项目验证可用性并参照修改
## 功能概要
- [x] 可配置奖品文字 **支持横向/竖向展示**
- [x] 可配置每个奖品区块的背景颜色
- [x] 可配置每个奖品区块的奖品文字颜色
- [x] 可配置奖品区块是否开启描边以及边框的颜色,默认不开启
- [x] 可配置转盘外环和抽奖按钮图
- [x] 可配置每个奖品区块的奖品图片,**当图片是网络地址时小程序端需要配置白名单H5端需要允许跨域奖品文字为竖向时不支持展示奖品图片**
- [x] 奖品列表支持奇数,**奇数时需尽量能被 `360` 除尽**
- [x] 可配置内圈与外圈的间距
- [x] 可配置轮盘旋转或指针旋转
- [x] 可配置画板是否缓存,默认不开启
- [x] 更多配置请查看API说明
## 示例项目附加功能
- [x] 中奖概率,**强烈推荐中奖概率应由后端控制**
- [x] 抽奖次数
- [x] 付费抽奖
## 注意事项
- 编译到小程序端时请务必勾选ES6转ES5
- `@reset-index="prizeIndex = -1"` 必须默认写入到 `template` 中,不可删除
- 每个奖品区块的奖品图片尺寸不宜过大,图片越大,绘制的过程越慢,尽量将图片尺寸控制在 `100*100` 以内,且图片大小控制在 `40KB` 以内
- 关于中奖概率的配置,请下载示例项目,参照 `pages/index/index.vue` 中的代码进行配置
- 组件本身不涉及任何业务逻辑,与业务相关的代码建议都放在 `pages/index/index.vue`
## 代码演示
#### 基础用法
```
// template
// @reset-index="prizeIndex = -1" 必须默认写入到 template 中,不可删除
<almost-lottery
:prizeList="prizeList"
:prizeIndex="prizeIndex"
@reset-index="prizeIndex = -1"
@draw-start="handleDrawStart"
@draw-end="handleDrawEnd"
@finish="handleDrawFinish"
v-if="prizeList.length"
/>
// script
import AlmostLottery from '@/uni_modules/almost-lottery/components/almost-lottery/almost-lottery.vue'
export default {
components: {
AlmostLottery
},
data () {
return {
// 以下是奖品配置数据
// 奖品数据
prizeList: [],
// 中奖下标
prizeIndex: -1
}
},
methods: {
// 本次抽奖开始
handleDrawStart () {
// 这里需要处理你的中奖逻辑,并得出 prizeIndex
// 请查看示例项目中的代码
},
// 本次抽奖结束
handleDrawEnd () {
// 完成抽奖后,这里处理你拿到结果后的逻辑
// 请查看示例项目中的代码
},
// 抽奖转盘绘制完成
handleDrawFinish (res) {
// 抽奖转盘准备就绪后,这里处理你的逻辑
// 请查看示例项目中的代码
// console.log('抽奖转盘绘制完成', res)
}
}
}
```
## API
#### Props
参数 | 说明 | 类型 | 默认值
:---|:---|:---|:---
pixelRatio | 移动端设计稿的像素比基准值,**涉及到 `rpx` 的适配问题** | *`Number`* | `2`
canvasId | Canvas的标识**多画板情况下需要配置不同的标识** | *`String`* | `'almostLottery'`
lotterySize | 抽奖转盘的整体尺寸,单位 `rpx` | *`Number`* | `600`
actionSize | 抽奖按钮的尺寸,单位 `rpx` | *`Number`* | `200`
canvasMarginOutside | Canvas边缘距离转盘边缘的距离单位`rpx` | *`Number`* | `90`
prizeIndex | 获奖奖品在奖品列表中的序号,**每次抽奖结束后会自动重置为 `-1`** | *`Number`* | `-1`
prizeList | 奖品列表,支持奇数(尽量能被 `360` 除尽),**为奇数时需要重设 `colors` 参数** | *`Array`* | -
lotteryBg | 转盘外环图片 | `String` | `默认内置的本地图片`
actionBg | 抽奖按钮图片 | `String` | `默认内置的本地图片`
colors | 奖品区块对应的背景颜色,默认 2 个颜色相互交替,**也可以对每个区块设置不同颜色** | *`Array`* | `['#FFFFFF', '#FFBF05']`
prizeNameDrawed | 是否绘制奖品名称 | *`Boolean`* | `true`
stroked | 是否开启奖品区块描边 | *`Boolean`* | `false`
strDirection | 奖品名称展示方向,可选值 `'horizontal'` => 横向 `'vertical'` => 竖向 | *`String`* | `'horizontal'`
strokeColor | 奖品区块边框颜色 | *`String`* | `'#FFBF05'`
rotateType | 旋转的类型,可选值 `'roulette'` => 轮盘旋转 `'pointer'` => 指针旋转 | *`String`* | `'roulette'`
duration | 转盘旋转的动画时长,单位:秒 | *`Number`* | `8`
ringCount | 旋转的圈数 | *`Number`* | `8`
pointerPosition | 点击抽奖按钮指针的位置,可选值 `'edge'` => 指向边界 `'middle'` => 指向中间 | *`String`* | `'edge'`
strFontColors | 奖品文字颜色,默认 2 个颜色相互交替,**也可以对每个区块的文字设置不同颜色,或仅设置一个颜色** | *`Array`* | `['#FFBF05', '#FFFFFF']`
strFontSize | 奖品名称的字号,单位 `rpx` | *`Number`* | `24`
strLineHeight | 奖品名称多行情况下的行高 | *`Number`* | `1.2`
strMaxLen | 奖品名称长度限制,**文字竖向时不生效** | *`Number`* | `12`
strLineLen | 奖品名称在多行情况下第一行文字的长度,**文字竖向时不生效** | *`Number`* | `6`
strMarginOutside | 奖品文字相对轮盘边缘的距离,单位 `rpx` | *`Number`* | `strFontSize 的一半`
imgMarginStr | 奖品图片相对奖品文字的距离,单位 `rpx` | *`Number`* | `60`
imgWidth | 奖品图片的宽度,单位 `rpx` | *`Number`* | `50`
imgHeight | 奖品图片的高度,单位 `rpx` | *`Number`* | `50`
imgDrawed | 是否绘制奖品图片,默认绘制 | *`Boolean`* | `true`
imgCircled | 奖品图片是否裁切为圆形,默认不裁切 | *`Boolean`* | `false`
successMsg | 转盘绘制成功的提示 | *`String`* | `'奖品准备就绪,快来参与抽奖吧'`
failMsg | 转盘绘制失败的提示 | *`String`* | `'奖品仍在准备中,请稍后再来...'`
canvasCached | 是否开启缓存,避免在数据不变的情况下重复绘制,建议在生产环境中开启 | *`Boolean`* | `false`
#### Events
事件名 | 说明 | 回调参数
:---|:---|:---
@reset-index | 每次抽奖结束后重置获奖的序号为 `-1`**该事件必须默认写入到 `template` 中,不可删除** | -
@draw-start | 转盘旋转开始时触发 | -
@draw-end | 转盘旋转结束时触发 | -
@finish | Canvas转盘绘制完成时触发 | `{ ok: 绘制是否成功, data: 转盘的图片, msg: 绘制结果的提示 }`
#### prizeList 数据结构
*请按如下数据字段对你的奖品列表数据结构进行调整*
键名 | 说明 | 类型
:---|:---|:---
prizeId | 奖品对应 `ID` | *`Number`*
prizeName | 奖品名称 | *`String`*
prizeStock | 奖品库存 | *`Number`*
prizeWeight | 奖品权重 | *`Number`*
prizeImage | 奖品图片地址,网络图片仅支持`http`和`https`协议 | *`String`*

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,316 @@
/**
* 存储 localStorage 数据
* @param {String} name - 缓存数据的标识
* @param {any} content - 缓存的数据内容
*/
export const setStore = (name, content) => {
if (!name) return
if (typeof content !== 'string') {
content = JSON.stringify(content)
}
uni.setStorageSync(name, content)
}
/**
* 获取 localStorage 数据
* @param {String} name - 缓存数据的标识
*/
export const getStore = (name) => {
if (!name) return
return uni.getStorageSync(name)
}
/**
* 清除 localStorage 数据
* @param {String} name - 缓存数据的标识
*/
export const clearStore = (name) => {
if (name) {
uni.removeStorageSync(name)
} else {
console.log('清理本地全部缓存')
uni.clearStorageSync()
}
}
/**
* 绘制圆形
* @param {String} ctx - 图片网络地址
* @param {String} img - 图片地址
* @param {String} x - x 轴偏移量
* @param {String} y - y 轴偏移量
* @param {String} w -
* @param {String} h -
*/
export const circleImg = (ctx, img, x, y, w, h) => {
let r = Math.floor(w/2)
let cx = x + r
let cy = y + r
ctx.save()
ctx.beginPath()
ctx.arc(cx, cy, r, 0, Math.PI * 2)
ctx.fill()
ctx.clip()
ctx.drawImage(img, x, y, w, h)
ctx.restore()
}
/**
* 计算文本的长度
* @param {String} text - 文本内容
*/
export const clacTextLen = (text) => {
if (!text) return { byteLen: 0, realLen: 0 }
text += ''
let clacLen = 0
for (let i = 0; i < text.length; i++) {
if ((text.charCodeAt(i) < 0) || (text.charCodeAt(i) > 255)) {
clacLen += 2
} else {
clacLen += 1
}
}
// console.log(`当前文本 ${text} 的长度为 ${clacLen / 2}`)
return {
byteLen: clacLen,
realLen: clacLen / 2
}
}
/**
* 下载文件并返回临时路径
* @return {String} 临时路径
* @param {String} fileUrl - 网络地址
*/
export const downloadFile = (fileUrl) => {
return new Promise((resolve) => {
// #ifdef MP-WEIXIN
let extName = fileUrl.split('.').pop()
let fileName = Date.now() + '.' + extName
// #endif
uni.downloadFile({
url: fileUrl,
// #ifdef MP-WEIXIN
filePath: wx.env.USER_DATA_PATH + '/' + fileName,
// #endif
success: (res) => {
// #ifdef MP-WEIXIN
resolve({
ok: true,
data: res.errMsg,
tempFilePath: res.filePath
})
// #endif
// #ifndef MP-WEIXIN
resolve({
ok: true,
data: res.errMsg,
tempFilePath: res.tempFilePath
})
// #endif
},
fail: (err) => {
resolve({
ok: false,
data: err.errMsg,
msg: '图片下载失败'
})
}
})
})
}
/**
* 清理应用已缓存的文件
*/
export const clearCacheFile = () => {
// #ifndef H5
uni.getSavedFileList({
success: (res) => {
let fileList = res.fileList
if (fileList.length) {
for (let i = 0; i < fileList.length; i++) {
uni.removeSavedFile({
filePath: fileList[i].filePath,
complete: () => {
console.log('清除缓存已完成')
}
})
}
}
},
fail: (err) => {
console.log('getSavedFileList Fail')
}
})
// #endif
}
// 图像转换工具可用于图像和base64的转换
// https://ext.dcloud.net.cn/plugin?id=123
const getLocalFilePath = (path) => {
if (
path.indexOf('_www') === 0 ||
path.indexOf('_doc') === 0 ||
path.indexOf('_documents') === 0 ||
path.indexOf('_downloads') === 0
) return path
if (path.indexOf('/storage/emulated/0/') === 0) return path
if (path.indexOf('/storage/sdcard0/') === 0) return path
if (path.indexOf('/var/mobile/') === 0) return path
if (path.indexOf('file://') === 0) return path
if (path.indexOf('/') === 0) {
// ios 无法获取本地路径
let localFilePath = plus.os.name === 'iOS' ? path : plus.io.convertLocalFileSystemURL(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.substring(1)
}
}
return '_www/' + path
}
export const pathToBase64 = (path) => {
return new Promise((resolve, reject) => {
if (typeof window === 'object' && 'document' in window) {
if (typeof FileReader === 'function') {
let xhr = new XMLHttpRequest()
xhr.open('GET', path, true)
xhr.responseType = 'blob'
xhr.onload = function() {
if (this.status === 200) {
let fileReader = new FileReader()
fileReader.onload = function(e) {
resolve(e.target.result)
}
fileReader.onerror = reject
fileReader.readAsDataURL(this.response)
}
}
xhr.onerror = reject
xhr.send()
return
}
let canvas = document.createElement('canvas')
let c2x = canvas.getContext('2d')
let img = new Image
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
c2x.drawImage(img, 0, 0)
resolve(canvas.toDataURL())
canvas.height = canvas.width = 0
}
img.onerror = reject
img.src = path
return
}
if (typeof plus === 'object') {
let tempPath = getLocalFilePath(path)
plus.io.resolveLocalFileSystemURL(tempPath, (entry) => {
entry.file((file) => {
let fileReader = new plus.io.FileReader()
fileReader.onload = function(data) {
resolve(data.target.result)
}
fileReader.onerror = function(error) {
console.log(error)
reject(error)
}
fileReader.readAsDataURL(file)
}, (error) => {
reject(error)
})
}, (error) => {
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
wx.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: (res) => {
resolve('data:image/png;base64,' + res.data)
},
fail: (error) => {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
export const base64ToPath = (base64) => {
return new Promise((resolve, reject) => {
if (typeof window === 'object' && 'document' in window) {
base64 = base64.split(',')
let type = base64[0].match(/:(.*?);/)[1]
let str = atob(base64[1])
let n = str.length
let array = new Uint8Array(n)
while (n--) {
array[n] = str.charCodeAt(n)
}
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], {
type: type
})))
}
let extName = base64.match(/data\:\S+\/(\S+);/)
if (extName) {
extName = extName[1]
} else {
reject(new Error('base64 error'))
}
let fileName = Date.now() + '.' + extName
if (typeof plus === 'object') {
let bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
let filePath = '_doc/uniapp_temp/' + fileName
bitmap.save(filePath, {}, () => {
bitmap.clear()
resolve(filePath)
}, (error) => {
bitmap.clear()
reject(error)
})
}, (error) => {
bitmap.clear()
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
let filePath = wx.env.USER_DATA_PATH + '/' + fileName
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
encoding: 'base64',
success: () => {
resolve(filePath)
},
fail: (error) => {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}

View File

@ -0,0 +1,422 @@
<template>
<view class="h-full">
<loading-view v-if="isFirstLoading"></loading-view>
<view
class="bg min-h-full overflow-hidden"
v-else
:style="{ backgroundImage: `url(${draw_activity.bg_image})`, backgroundColor: draw_activity.bg_color }"
>
<view class="">
<view class="fixed top-0 left-0 w-full bg z-10" :style="{ backgroundImage: `url(${draw_activity.bg_image})` }">
<view :style="{ height: statusBarHeight + 'px' }"></view>
<view class="px-30rpx py-20rpx flex h-50px flex items-center relative">
<image
@click="onBack"
class="h-56rpx w-56rpx"
src="https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/back.png"
alt=""
srcset=""
></image>
<view class="absolute left-100rpx right-100rpx text-center text-52rpx text-white">{{draw_activity.name}}</view>
</view>
</view>
<view class="w-full" :style="{ height: 50 + statusBarHeight + 'px' }"> </view>
</view>
<view class="h-300rpx w-full"></view>
<view class="w-full relative">
<view class="h-100rpx"></view>
<view class="absolute top-0 z-9 left-0 px-30rpx message w-full">
<view class="h-180rpx">
<swiper
vertical="vertical"
autoplay="play"
class="h-full"
previous-margin="60rpx"
next-margin="60rpx"
disable-touch
@change="change"
circular
:current="current"
>
<swiper-item v-for="(item, index) in logsAll" :key="index" class="h-full flex items-start flex-col justify-center">
<view
class="bg-hex-000 bg-opacity-30 text-white rounded-full px-20px h-44rpx leading-44rpx text-28rpx inline-block"
:class="index == current ? 'opacity1' : index + 1 == current ? 'opacity0' : index - 1 == current ? 'opacity2' :(current==logsAll.length-1 && index==0)?'opacity2' : 'opacity3'"
>
<text
>恭喜{{ item.user.nickname }}抽中<text class="text-hex-ffde58">{{ item.prize.name }}</text></text
>
</view>
</swiper-item>
</swiper>
</view>
<!-- <view v-for="item in logs" :key="item.id" class="opacity">
<view class="bg-hex-000 bg-opacity-30 text-white rounded-full px-20px h-44rpx leading-44rpx text-28rpx mb-10rpx inline-block">
<text
>恭喜{{ item.user.nickname }}抽中<text class="text-hex-ffde58">{{ item.prize.name }}</text></text
>
</view>
</view> -->
</view>
<almost-lottery
pointer-position="edge"
:lottery-size="lotteryConfig.lotterySize"
:action-size="lotteryConfig.actionSize"
:ring-count="8"
:duration="8"
:prize-list="prizeList"
:prize-index="prizeIndex"
@reset-index="prizeIndex = -1"
@draw-start="handleDrawStart"
@draw-end="handleDrawEnd"
@finish="handleDrawFinish"
v-if="prizeList.length"
stroke-color="#ffffff"
:str-font-size="18"
:strFontColors="['#fb6056']"
:colors="['#ffeaa7', '#ffeaa7']"
:stroked="true"
:img-width="120"
:img-height="120"
:imgMarginStr="30"
:strMarginOutside="16"
:canvasCached="true"
:lotteryBg="lotteryBg"
:actionBg="actionBg"
/>
<view class="text-center mt-40rpx"
>今天还可以抽 <text class="text-error">{{ freeNum }}</text> </view
>
</view>
<block>
<view class="p-30rpx">
<u-parse :html="draw_activity.desc"></u-parse>
</view>
</block>
<u-mask :show="show">
<view class="flex items-center justify-center h-full">
<view class="">
<view class="pr-30rpx text-right">
<image src="/static/images/app/app_update_close.png" alt="" srcset="" class="w-48rpx h-48rpx" @click="show = false"></image>
</view>
<view class="w-600rpx h-600rpx relative" v-if="prizeData.prize">
<image :src="prizeData.prize.type==0?results.goods:tips_image" class="w-full h-full"></image>
<block v-if="prizeData.prize.type!=0">
<view class="absolute top-178rpx left-0 right-0 text-center mx-auto mx-auto w-300rpx h-290rpx">
<image :src="results.goods" alt="" class="w-full h-full"></image>
</view>
<view
class="absolute top-136rpx left-0 right-0 text-center h-42rpx leading-40rpx text-24rpx text-hex-f74a2c bg-gradient-to-t to-hex-fff4e5 from-hex-ffd4b2 w-240rpx rounded-full mx-auto"
>
{{ results.name }}
</view>
<view
v-if="!!results.btn"
@click="onResults"
class="absolute top-466rpx left-0 right-0 text-center h-54rpx leading-54rpx text-28rpx text-white bg-img w-280rpx mx-auto"
>
{{ results.btn }}
</view>
</block>
</view>
</view>
</view>
</u-mask>
<u-popup v-model="showAddress" mode="center" :border-radius="20" :mask-close-able="false">
<view class="p-30rpx w-600rpx">
<u-form :model="form" ref="uForm" label-width="130rpx" :error-type="['toast']">
<u-form-item label="收件人" prop="consignee_name">
<u-input v-model="form.consignee_name" cursor-spacing="50rpx" />
</u-form-item>
<u-form-item label="联系方式" prop="consignee_phone">
<u-input v-model="form.consignee_phone" cursor-spacing="50rpx" />
</u-form-item>
<u-form-item label="收货地址" prop="consignee_address">
<u-input v-model="form.consignee_address" cursor-spacing="50rpx" />
</u-form-item>
</u-form>
<view class="bg-primary rounded-full h-64rpx leading-64rpx text-center text-white" @click="submit"> </view>
</view>
</u-popup>
</view>
</view>
</template>
<script>
let systemInfo = uni.getSystemInfoSync();
import AlmostLottery from '../components/almost-lottery/components/almost-lottery/almost-lottery.vue';
export default {
name: 'Home',
components: {
AlmostLottery,
},
data() {
return {
isFirstLoading: true,
id: '',
config: {},
current: 1,
statusBarHeight: systemInfo.statusBarHeight,
form: {
consignee_name: '',
consignee_phone: '',
consignee_address: '',
},
show: false,
prizeData: {},
results: {
name: '50元消费体验价',
btn: '充值账户余额内',
goods: 'https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/hb.png',
},
//
isDev: process.env.NODE_ENV === 'development',
//
lotteryConfig: {
// rpx
lotterySize: 680,
// rpx
actionSize: 200,
},
// UI
// 稿
lotteryBg: 'https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/lottery-bg.png',
//
actionBg: 'https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/action-bg.png',
//
prizeList: [],
//
onStock: true,
//
prizeIndex: -1,
//
prizeing: false,
//
freeNum: 0,
logsAll: [],
logsIndex: 0,
logs: [],
timer: null,
showAddress: false,
draw_log_id: null,
rules: {
consignee_name: [
{
required: true,
message: '请输入收件人',
trigger: 'change',
},
],
consignee_phone: [
{
required: true,
message: '请输入连续方式',
trigger: 'change',
},
],
consignee_address: [
{
required: true,
message: '请输入收货地址',
trigger: 'change',
},
],
},
};
},
computed: {
isApple() {
return uni.getSystemInfoSync().platform === 'ios';
},
draw_activity() {
return this.config.draw_activity;
},
tips_image(){
return this.draw_activity?.tips_image ?? 'https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/lucky-jg.png'
}
},
async onLoad({ id }) {
this.id = id;
this.getConfig();
this.getLuckyLog();
},
methods: {
change(e) {
this.current = e.detail.current;
},
onBack() {
uni.navigateBack();
},
async getConfig() {
try {
const resData = await this.$api.get(`/v1/draw-activities/${this.id}`);
this.config = resData;
this.handleInitCanvas();
} catch (error) {
console.log(error);
} finally {
this.isFirstLoading = false;
}
},
onResults() {
const { prize } = this.prizeData;
if (prize.type == 1) {
this.show = false;
this.showAddress = true;
} else {
this.show = false;
}
},
submit() {
this.$refs.uForm.validate(async (valid) => {
if (valid) {
await this.$api.put(`/v1/draw-activities/${this.id}/logs/${this.draw_log_id}`, this.form);
this.$refs.uForm.resetFields();
this.showAddress = false;
}
});
},
//
async getLuckyLog() {
const resData = await this.$api.get(`/v1/draw-activities/${this.id}/logs`, {
params: {
page: 1,
per_page: 4,
},
});
this.logsAll = resData.data.reverse();
},
//
handleInitCanvas() {
this.prizeList = [];
this.getPrizeList();
},
//
async getPrizeList() {
this.freeNum = this.config.draw_tickets_number;
const data = this.draw_activity.prizes.map((e) => {
return { prizeId: e.id, prizeName: e.name, prizeStock: 10, prizeWeight: 200, prizeImage: e.icon, ...e };
});
this.draw_log_id = this.config.draw_log_id;
this.prizeList = data;
if (!!this.draw_log_id && !this.showAddress) {
this.showAddress = true;
this.$nextTick(() => {
this.$refs.uForm.setRules(this.rules);
});
}
},
//
handleDrawStart() {
if(this.draw_activity.status == 4){
return this.$u.toast('活动已结束');
}
if(this.draw_activity.status == 2){
return this.$u.toast('活动未开始');
}
if(this.draw_activity.status != 3){
return;
}
if (this.prizeing) return;
this.prizeing = true;
//
if (this.freeNum > 0) {
//
if (this.freeNum > 0) {
this.freeNum--;
}
this.tryLotteryDraw();
} else {
this.$u.toast('抽奖次数不足');
this.prizeing = false;
}
},
//
tryLotteryDraw() {
this.remoteGetPrizeIndex();
},
//
async remoteGetPrizeIndex() {
const resData = await this.$api.post(`/v1/draw-activities/${this.id}/draw`);
this.prizeData = resData;
const { prize } = resData;
let _prizeId = prize.id;
this.prizeIndex = this.prizeList.findIndex(({ prizeId }) => prizeId == _prizeId);
},
//
handleDrawEnd() {
//
let prize = this.prizeList[this.prizeIndex];
const { type, prizeImage, name, amount } = prize;
this.draw_log_id = this.prizeData.id;
this.results.name = name;
this.results.btn = '';
if (type == 1) {
// this.showAddress = true
this.results.btn = '填写地址';
}
this.results.goods = prizeImage;
this.show = true;
this.prizeing = false;
this.getLuckyLog();
},
//
handleDrawFinish(res) {
let stoTimer = setTimeout(() => {
clearTimeout(stoTimer);
stoTimer = null;
uni.showToast({
title: res.msg,
mask: true,
icon: 'none',
});
}, 50);
},
},
onUnload() {
uni.hideLoading();
clearTimeout(this.timer);
},
};
</script>
<style>
page {
height: 100%;
}
</style>
<style lang="scss" scoped>
.bg {
// background-image: url('https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/luckywheel.png');
background-size: 100% auto;
background-position: top;
background-repeat: no-repeat;
}
.bg-img {
background: url('https://ystmp.oss-cn-beijing.aliyuncs.com/wechart/lucky/btn.png') no-repeat;
background-size: 100% auto;
}
.opacity0 {
@apply opacity-60;
}
.opacity1 {
@apply opacity-80;
}
.opacity2 {
@apply opacity-100;
}
.opacity3 {
@apply opacity-80;
}
</style>

View File

@ -593,6 +593,13 @@
"enablePullDownRefresh": false,
"navigationStyle": "default"
}
},
{
"path": "lucky/index",
"style": {
"navigationBarTitleText": "活动",
"enablePullDownRefresh": false
}
}
]
}

View File

@ -553,7 +553,7 @@ export default {
return this.bannerList[key] ?? [];
},
jumpByOption(e) {
const whiteRouter = ['/pages/user_sign/index', '/pageA/user_sign/index','/pages/points/swap'];
const whiteRouter = ['/pages/user_sign/index', '/pageA/user_sign/index','/pages/points/swap','/pageB/lucky/index'];
const tabRouter = ['/pages/sort/index', '/pages/shop_cart/index', '/pages/healthy/index', '/pages/me/me'];
if (!!e.jump_link) {
if (e.jump_type == 1) {

View File

@ -206,7 +206,7 @@
@confirm="receiptConfirm"
confirm-color="#378264"
show-cancel-button
content="确认收货后不能享受7天无理由退还货是否确认收货?"
content="是否确认收货?"
></cu-modal>
</view>
</template>

View File

@ -641,8 +641,33 @@ export default {
//sku
onChangeSku(e) {
this.skuId = e.sku_id;
this.getProducts();
this.getProdutsku();
},
//
async getProdutsku() {
try {
this.loading = true;
const resData = await this.$api.get(`/v1/product/sku/${this.skuId}`);
this.products = resData;
if (resData?.sku?.is_bargaing) {
this.bargainOrderLog();
}
//
this.getTitle(resData.sku.name);
//
this.getLocal(resData.sku.cover);
} catch (error) {
if (error != 404) {
this.isNull = true;
}
// console.log(error);
} finally {
this.loading = false;
this.isFirstLoading = false;
}
},
//
//
onCollection() {
this.$u.throttle(async () => {