diff --git a/src/pageA/user_order/conponents/order-item.vue b/src/pageA/user_order/conponents/order-item.vue index bfe64cc..8b3c58b 100644 --- a/src/pageA/user_order/conponents/order-item.vue +++ b/src/pageA/user_order/conponents/order-item.vue @@ -44,7 +44,7 @@ + content="是否确认收货?"> + + diff --git a/src/pageB/components/almost-lottery/package.json b/src/pageB/components/almost-lottery/package.json new file mode 100644 index 0000000..ae08d49 --- /dev/null +++ b/src/pageB/components/almost-lottery/package.json @@ -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" + } + } + } + } +} \ No newline at end of file diff --git a/src/pageB/components/almost-lottery/readme.md b/src/pageB/components/almost-lottery/readme.md new file mode 100644 index 0000000..dc5dfce --- /dev/null +++ b/src/pageB/components/almost-lottery/readme.md @@ -0,0 +1,160 @@ +# almost-lottery +*使用 Canvas 绘制的抽奖转盘,提供奇数、缓存等众多配置项,更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中* + + +>
+> +> 如果用着还行,请支持一下 +> - 前往 [GitHub](https://github.com/ialmost/almost-components_uniapp) 给个 Star +> - 前往 [UniApp](https://ext.dcloud.net.cn/plugin?id=1030) 给个五星 +> - 使用中遇到问题时,可以添加 **QQ群 20441313** +> +>
+ + +## 基于 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 中,不可删除 + + +// 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`* \ No newline at end of file diff --git a/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action.png b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action.png new file mode 100644 index 0000000..2e221a4 Binary files /dev/null and b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action.png differ diff --git a/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action2x.png b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action2x.png new file mode 100644 index 0000000..10d497f Binary files /dev/null and b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action2x.png differ diff --git a/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action3x.png b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action3x.png new file mode 100644 index 0000000..5e3b8d3 Binary files /dev/null and b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__action3x.png differ diff --git a/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg.png b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg.png new file mode 100644 index 0000000..be39367 Binary files /dev/null and b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg.png differ diff --git a/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg2x.png b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg2x.png new file mode 100644 index 0000000..3fe4efd Binary files /dev/null and b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg2x.png differ diff --git a/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg3x.png b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg3x.png new file mode 100644 index 0000000..bcd70e1 Binary files /dev/null and b/src/pageB/components/almost-lottery/static/almost-lottery/almost-lottery__bg3x.png differ diff --git a/src/pageB/components/almost-lottery/utils/almost-utils.js b/src/pageB/components/almost-lottery/utils/almost-utils.js new file mode 100644 index 0000000..5db2cff --- /dev/null +++ b/src/pageB/components/almost-lottery/utils/almost-utils.js @@ -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')) + }) +} diff --git a/src/pageB/lucky/index.vue b/src/pageB/lucky/index.vue new file mode 100644 index 0000000..5e4912d --- /dev/null +++ b/src/pageB/lucky/index.vue @@ -0,0 +1,422 @@ + + + + + + diff --git a/src/pages.json b/src/pages.json index 1eafedf..c693451 100644 --- a/src/pages.json +++ b/src/pages.json @@ -593,6 +593,13 @@ "enablePullDownRefresh": false, "navigationStyle": "default" } + }, + { + "path": "lucky/index", + "style": { + "navigationBarTitleText": "活动", + "enablePullDownRefresh": false + } } ] } diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 2076a4c..42a8816 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -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) { diff --git a/src/pages/order_details/index.vue b/src/pages/order_details/index.vue index 9db4c2d..c02c22a 100644 --- a/src/pages/order_details/index.vue +++ b/src/pages/order_details/index.vue @@ -206,7 +206,7 @@ @confirm="receiptConfirm" confirm-color="#378264" show-cancel-button - content="确认收货后不能享受7天无理由退还货,是否确认收货?" + content="是否确认收货?" > diff --git a/src/pages/product_details/index.vue b/src/pages/product_details/index.vue index 5bb2e71..5513611 100644 --- a/src/pages/product_details/index.vue +++ b/src/pages/product_details/index.vue @@ -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 () => {