This commit is contained in:
YeMingfei666 2025-01-07 17:27:30 +08:00
commit 79813d84d1
48 changed files with 5904 additions and 145 deletions

View File

@ -32,4 +32,25 @@ export const selectCourseTitles = (data) => {
url: '/course/selectCourseTitles',
data
})
}
}
// 抽奖
export const selectDiscSpinning = (data) => {
return http.request({
url: '/discSpinning/selectDiscSpinning',
data
})
}
export const selectUserMoney = (data) => {
return http.request({
url: '/moneyDetails/selectUserMoney',
data
})
}
export const drawCount = (data) => {
return http.request({
url: '/discSpinning/drawCount',
data
})
}

32
api/task/index.js Normal file
View File

@ -0,0 +1,32 @@
import http from '@/http/http.js'
// 获取任务列表
export const selectTaskCenter = (data) => {
return http.request({
url: '/taskCenter/selectTaskCenter',
data
})
}
// 获取签到
export const getUserSignData = (data) => {
return http.request({
url: 'userSignRecord/getUserSignData',
data
})
}
// 任务-签到
export const taskReceive = (data) => {
return http.request({
url: 'taskCenter/taskReceive',
data,
isreturm:true
})
}
// 任务列表
export const selectDiscSpinning = (data) => {
return http.request({
url: 'discSpinning/selectDiscSpinning',
data,
})
}

View File

@ -0,0 +1,26 @@
<template>
<view class="container">
<up-empty :icon="props.icon" :text="props.text"></up-empty>
</view>
</template>
<script setup>
const props = defineProps({
icon: {
type: String,
default: '/static/default/none.png'
},
text: {
type: String,
default: '空空如也~'
}
});
</script>
<style scoped lang="scss">
.container {
width: 100%;
display: flex;
justify-content: center;
}
</style>

163
components/pop-ling-qu.vue Normal file
View File

@ -0,0 +1,163 @@
<template>
<view>
<u-popup v-model="show" mode="center" @close="close">
<view class="bg">
<view class="title">恭喜您获得</view>
<view class="goods">
<template v-if="result&&result.type==3">
<view class="u-flex u-col-center u-row-center">
<image style="height: 100px;" :src="result.img" mode="heightFix"></image>
</view>
<view class="u-flex u-row-center u-m-t-30">
<view class="type">{{result.name}}</view>
</view>
</template>
<template v-if="result&&result.type==2">
<view class="u-flex color-money u-col-center u-row-center">
<view class="money">{{result.number}}</view>
<view class="font-bold " style="margin-top: 20rpx;font-size: 36rpx;"></view>
</view>
<view class="u-flex u-m-t-24 u-row-center">
<view class="type">现金红包</view>
</view>
</template>
</view>
<view class="u-flex close u-row-center">
<u-icon name="close-circle" :size="54" @click="close" color="#fff"></u-icon>
</view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
show: false,
result: ''
}
},
methods: {
open(data) {
console.log(data);
this.result = data
this.show = true
},
close() {
console.log('抽奖弹窗关闭');
this.show = false
if(!this.result){
return
}
const {
orderId,
id
} = this.result
this.$Request.postJson('/app/discSpinning/receive', this.result).then(res => {
this.result = ''
console.log(res)
if (res.code == 0) {
console.log('抽奖领取成功');
const key=res.data==0?'isBindAliPay':undefined
this.$emit('close',key)
if(key&&key=='isBindAliPay'){
uni.navigateTo({
url:'/me/yaoqing/zhifubao-tixain'
})
}
} else {
}
})
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .u-mode-center-box {
background-color: transparent;
}
.color-money {
color: #E42F00;
}
.money {
font-weight: 700;
font-size: 96rpx;
letter-spacing: 2px;
}
.bg {
width: 628rpx;
height: 770rpx;
margin-right: 10rpx;
background-color: transparent;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
background-image: url("~static/images/zhuanpan/gift.png");
position: relative;
@media (-webkit-min-device-pixel-ratio: 2),
(min-device-pixel-ratio: 2) {
background-image: url("~static/images/zhuanpan/gift@2x.png");
}
.title {
position: absolute;
top: 218rpx;
text-align: center;
left: 0;
right: 0;
font-weight: 700;
font-size: 60rpx;
color: #AF6920;
letter-spacing: 4rpx;
}
.goods {
position: absolute;
top: 336rpx;
text-align: center;
left: 0;
right: 0;
text-align: center;
}
.type {
padding: 6rpx 28rpx;
border-radius: 100rpx;
background: #E25B41;
font-size: 28rpx;
color: #fff;
font-weight: bold;
}
.close{
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.btn-box {
position: absolute;
top: 574rpx;
left: 0;
right: 0;
.btn {
padding: 10rpx 60rpx 10rpx 64rpx;
text-align: center;
font-weight: bold;
font-size: 44rpx;
color: #AF6920;
letter-spacing: 2px;
}
}
}
</style>

View File

@ -14,7 +14,7 @@ function getHeader() {
}
// 通用处理逻辑
function commonsProcess(showLoading, httpReqCallback) {
function commonsProcess(showLoading, httpReqCallback,isreturm) {
// 判断是否请求完成(用作 是否loading
// 包括: 'ing', 'ingLoading', 'finish'
let reqState = "ing";
@ -41,11 +41,15 @@ function commonsProcess(showLoading, httpReqCallback) {
return httpReqCallback()
.then((httpData) => {
reqFinishFunc(); // 请求完毕的动作
reqFinishFunc(); // 请求完毕的动作
// 从http响应数据中解构响应数据 [ 响应码、 bodyData ]
let { statusCode, data } = httpData;
// 避免混淆重新命名
let bodyData = data;
if(isreturm){
return Promise.resolve(bodyData.data || bodyData.page|| bodyData);
}
if (statusCode == 500) {
isShowErrorToast = true;
return Promise.reject(bodyData); // 跳转到catch函数
@ -81,11 +85,12 @@ function commonsProcess(showLoading, httpReqCallback) {
code: bodyData.code,
});
}
// 构造请求成功的响应数据
return Promise.resolve(bodyData.data || bodyData.page|| bodyData);
})
.catch((res) => {
console.log(res);
if (res.status == 404) {
infoBox.showErrorToast("接口404").then(() => {});
reject();
@ -147,7 +152,8 @@ function request(args) {
params,
method = "GET",
showLoading = true,
extParams = {}
extParams = {},
isreturm=false
} = args
if (params) {
let result = ''
@ -170,7 +176,7 @@ function request(args) {
}, extParams)
)
})
},isreturm)
}
// 处理/
function slash(baseUrl, url) {

View File

@ -32,8 +32,7 @@
{
"path": "pages/task/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
"navigationBarTitleText": "任务大厅"
}
}, {
"path": "pages/chasingDrama/index",
@ -95,6 +94,29 @@
"style": {
"navigationBarTitleText": "搜索"
}
},
{
"path" : "pages/task/prizeList",
"style" :
{
"navigationBarTitleText" : "任务"
}
},
{
"path" : "pages/task/receiveMember",
"style" :
{
"navigationBarTitleText" : "",
"navigationStyle": "custom"
}
},
{
"path" : "pages/me/prizeDraw",
"style" :
{
"navigationBarTitleText" : "抽奖"
}
}
],
"globalStyle": {

View File

@ -7,57 +7,18 @@
<navigator class="more" url="/pages/watching_history/watching_history?type=3">更多</navigator>
</view>
<view class="list">
<view class="item">
<view class="item" v-for="item in data.list1" :key="item.id">
<view class="cover">
<image class="img" src="https://img0.baidu.com/it/u=966333451,3199467079&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=714" mode="aspectFill"></image>
<view class="num">第三集</view>
<image class="img" :src="item.titleImg" mode="aspectFill"></image>
<view class="num">{{ item.courseDetailsName }}</view>
</view>
<view class="intro-wrap">
<view class="name">我在八十年代当后妈</view>
<view class="t">言情</view>
</view>
</view>
<view class="item">
<view class="cover">
<image class="img" src="https://img0.baidu.com/it/u=966333451,3199467079&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=714" mode="aspectFill"></image>
<view class="num">第三集</view>
</view>
<view class="intro-wrap">
<view class="name">我在八十年代当后妈</view>
<view class="t">言情</view>
</view>
</view>
<view class="item">
<view class="cover">
<image class="img" src="https://img0.baidu.com/it/u=966333451,3199467079&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=714" mode="aspectFill"></image>
<view class="num">第三集</view>
</view>
<view class="intro-wrap">
<view class="name">我在八十年代当后妈</view>
<view class="t">言情</view>
</view>
</view>
<view class="item">
<view class="cover">
<image class="img" src="https://img0.baidu.com/it/u=966333451,3199467079&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=714" mode="aspectFill"></image>
<view class="num">第三集</view>
</view>
<view class="intro-wrap">
<view class="name">我在八十年代当后妈</view>
<view class="t">言情</view>
</view>
</view>
<view class="item">
<view class="cover">
<image class="img" src="https://img0.baidu.com/it/u=966333451,3199467079&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=714" mode="aspectFill"></image>
<view class="num">第三集</view>
</view>
<view class="intro-wrap">
<view class="name">我在八十年代当后妈</view>
<view class="t">言情</view>
<view class="name">{{ item.title }}</view>
<view class="t">{{ item.courseLabel }}</view>
</view>
</view>
</view>
<emprty-card v-if="!data.list1.length" />
</view>
<view class="list-wrap">
<view class="title-wrap">
@ -65,19 +26,20 @@
<navigator class="more" url="/pages/watching_history/watching_history?type=1">更多</navigator>
</view>
<view class="list">
<view class="item">
<view class="item" v-for="item in data.list2" :key="item.id">
<div class="item-content">
<view class="cover">
<image class="img" src="https://img0.baidu.com/it/u=966333451,3199467079&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=714" mode="aspectFill"></image>
<view class="num">第三集</view>
<image class="img" :src="item.titleImg" mode="aspectFill"></image>
<view class="num">{{ item.courseDetailsName }}</view>
</view>
<view class="intro-wrap">
<view class="name">我在八十年代当后妈</view>
<view class="t">言情</view>
<view class="name">{{ item.title }}</view>
<view class="t">{{ item.courseLabel }}</view>
</view>
</div>
</view>
</view>
<emprty-card v-if="!data.list2.length" />
</view>
</view>
</template>
@ -87,12 +49,13 @@ import { reactive } from 'vue';
import { selectByUserId } from '@/api/me/me.js';
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
//
async function selectByUserIdAjax() {
try {
const res1 = await selectByUserId({ page: 1, limit: 6, classify: 3 });
const res2 = await selectByUserId({ page: 1, limit: 6, classify: 6 });
console.log(res1);
console.log(res2);
data.list1 = res1.records;
data.list2 = res2.records;
} catch (error) {
console.log(error);
}

View File

@ -55,8 +55,8 @@
}
.liststyle:last-child {
margin-right: auto;
margin-left: 6rpx;
// margin-right: auto;
// margin-left: 6rpx;
}
}
</style>

View File

@ -17,7 +17,7 @@
</view>
<swiper :autoplay="true" :vertical="true" :interval="4000" :circular="true" :indicator-dots="false"
class="swiperstyle">
<swiper-item @tap='goMsg(item.url)' v-for="(item, index) in datas.noticeList" :key="index">
<swiper-item v-for="(item, index) in datas.noticeList" :key="index">
<view style="height: 80rpx;line-height: 80rpx;">{{ item.title }}</view>
</swiper-item>
</swiper>
@ -25,25 +25,26 @@
</template>
</view>
<view class="navTop">
<view :style="datas.active == 1 ? 'color:#ff6b7f' : ''" @tap="getrecomVideo('', 1)">
<view :class="datas.active == 1 ? 'navTop-active' : ''" @tap="getrecomVideo('', 1)">
最新
</view>
<view :style="datas.active == 2 ? 'color:#ff6b7f' : ''" @tap="getrecomVideo('1', 2)">
<view :class="datas.active == 2 ? 'navTop-active' : ''" @tap="getrecomVideo('1', 2)">
排行
</view>
<view :style="datas.active == 3 ? 'color:#ff6b7f' : ''" @tap="getrecomVideo('2', 3)">
<view :class="datas.active == 3 ? 'navTop-active' : ''" @tap="getrecomVideo('2', 3)">
最热
</view>
<view :style="datas.active == 4 ? 'color:#ff6b7f' : ''" @tap="getrecomVideo('1', 4)">
<view :class="datas.active == 4 ? 'navTop-active' : ''" @tap="getrecomVideo('1', 4)">
剧情
</view>
<view :style="datas.active == 5 ? 'color:#ff6b7f' : ''" @tap="getrecomVideo('2', 5)">
<view :class="datas.active == 5 ? 'navTop-active' : ''" @tap="getrecomVideo('2', 5)">
飙升
</view>
</view>
<contentlist :list='datas.list'></contentlist>
<!-- <u-image v-if="isShowMoneyPay" @click="goMsg('/me/VjgyqAzklr/VjgyqAzklr')" :src="`../../static/red-pack-new.gif`"
style="width: 200rpx;height: 200rpx;position: fixed;right: 10rpx;bottom: 180rpx;"></u-image> -->
<image v-if="datas.isExamine == 0" @click="goMsg('/me/VjgyqAzklr/VjgyqAzklr')" src="@/static/index/red-pack-new.gif"
style="width: 200rpx;height: 200rpx;position: fixed;right: 10rpx;bottom: 180rpx;" mode=""></image>
<u-modal :show="datas.ruleShow" v-if="datas.isExamine == 0" confirm-text="知道了" @confirm='datas.ruleShow = false'
:title="datas.rule_title" :title-style="{ fontWeight: '700' }" confirm-color="rgb(255, 117, 129)">
<view class="" style="padding-top: 30rpx;text-align: left;">
@ -58,13 +59,12 @@
import {
reactive
} from 'vue';
import { announcement, messageselectMessage,courseselectCourse } from '@/api/index/index.js'
import { announcement, messageselectMessage, courseselectCourse } from '@/api/index/index.js'
import {
onLoad,
onReachBottom
} from '@dcloudio/uni-app'
import contentlist from './components/contentlist.vue'
import http from '@/http/http.js'
let datas = reactive({
noticeList: [], //
@ -90,22 +90,30 @@ onReachBottom(() => {
++datas.page
getrecomVideo()
})
//
function goMsg(url) {
// if (url.indexOf('/pages/') !== -1 || url.indexOf('/me/') !== -1) {
uni.navigateTo({
url: '/pages/me/prizeDraw'
});
// } else {
//#ifndef H5
// uni.navigateTo({
// url: '/pages/index/webView?url=' + url
// });
//#endif
//#ifdef H5
// window.location.href = url;
//#endif
// }
}
//
async function getPop() {
let res = await announcement()
if (res.code == 0) {
if (res.data && res.data.state == 1) {
datas.ruleShow = true
datas.rule_title = res.data.title
datas.rule_content = res.data.content
}
} else {
uni.showToast({
title: res.msg,
duration: 1000,
icon: 'none'
});
if (res.state == 1) {
datas.ruleShow = true
datas.rule_title = res.title
datas.rule_content = res.content
}
}
//
@ -117,15 +125,7 @@ function moreVideo() {
//
async function getMsg() {
let res = await messageselectMessage()
if (res.code == 0) {
datas.noticeList = res.data.list
} else {
uni.showToast({
title: res.msg,
duration: 1000,
icon: 'none'
});
}
datas.noticeList = res.list
}
//
async function getrecomVideo(sort, active = 1) {
@ -140,18 +140,10 @@ async function getrecomVideo(sort, active = 1) {
classifyId: ''
})
if (res.code == 0) {
if (datas.page == 1) {
datas.list = res.data.list
} else {
datas.list = [...datas.list, ...res.data.list]
}
if (datas.page == 1) {
datas.list = res.list
} else {
uni.showToast({
title: res.msg,
duration: 1000,
icon: 'none'
});
datas.list = [...datas.list, ...res.list]
}
}
</script>
@ -225,6 +217,10 @@ async function getrecomVideo(sort, active = 1) {
}
.navTop-active {
color: #cb5d68;
}
.navTop {
display: flex;
align-items: center;

View File

@ -2,47 +2,58 @@
<view>
<u-sticky :enable="true">
<view class="search-box">
<u-search bg-color="#f2f2f2" style="width: 100%;" placeholder="搜索更多资源" :focus="true" :show-action="true"
<u-search bg-color="#f2f2f2" style="width: 100%;" placeholder="搜索更多资源" :show-action="true"
:animation="true" action-text="取消" v-model="datas.keyword" @custom="goBack()"
@search="doSearch(false)"></u-search>
@search="doSearch()"></u-search>
</view>
</u-sticky>
</view>
<view class="search-keyword">
<view class="search-keyword" v-if="datas.isSearch">
<view class="keyword-block" v-if="datas.hotKeywordList.length != 0">
<view class="keyword-list-header">
<view>热搜</view>
</view>
<view class="keyword" v-if="forbid == ''">
<view v-for="(keyword, index) in datas.hotKeywordList" @tap="doSearchs(keyword)" :key="index"
v-if="keyword">
<view class="keyword">
<view v-for="(keyword, index) in datas.hotKeywordList" @tap="doSearchs(keyword)" :key="index">
{{ keyword }}
</view>
</view>
<view class="hide-hot-tis" v-else>
<view>当前搜热已隐藏</view>
</view>
</view>
</view>
<view class="search-list" v-else>
<view class="search-list-box">
<videoList @success="posterSuccess" :list="datas.keywordList" />
</view>
</view>
</template>
<script setup>
import { selectCourseTitles } from '@/api/index/index.js'
import videoList from './videoList.vue'
import {
reactive
} from 'vue';
import {
onShow
onShow, onReachBottom
} from '@dcloudio/uni-app'
let datas = reactive({
hotKeywordList: [], //
keywordList: [],//
keyword: "",//
page: 1,
isSearch: true,
})
onShow(() => {
getList()
})
function posterSuccess() {
}
function getList() {
if (uni.getStorageSync('moreSearch')) {
datas.hotKeywordList = (uni.getStorageSync('moreSearch')).split(',')
@ -50,14 +61,28 @@ function getList() {
datas.hotKeywordList = []
}
}
onReachBottom(() => {
++datas.page
doSearch()
})
function doSearchs(keyWord) {
datas.keyword = keyWord
doSearch()
}
//
async function doSearch() {
datas.isSearch = false
let res = await selectCourseTitles({
title: datas.keyword,
limit: 20,
page: 1,
page: datas.page,
})
datas.keywordList = res.data.list
if (datas.page == 1) {
datas.keywordList = res.list
} else {
datas.keywordList = [...datas.keywordList, ...res.list]
}
}
//
@ -83,20 +108,59 @@ function goBack() {
}
.keyword-block {
padding: 10upx 0;
padding: 10rpx 0;
}
.keyword {
width: 94%;
padding: 3px 3%;
display: flex;
flex-flow: wrap;
justify-content: flex-start;
>view {
display: flex;
justify-content: center;
align-items: center;
border-radius: 60upx;
padding: 0 20upx;
margin: 10upx 20upx 10upx 0;
height: 60upx;
font-size: 28upx;
// background-color: rgb(242, 242, 242);
background: #E6EBFF;
color: #6b6b6b;
}
}
.keyword-block .keyword-list-header {
width: 94%;
padding: 10upx 3%;
font-size: 27upx;
padding: 10rpx 3%;
font-size: 27rpx;
color: #333;
display: flex;
justify-content: space-between;
}
.keyword-block .keyword-list-header image {
width: 40upx;
height: 40upx;
width: 40rpx;
height: 40rpx;
}
.search-list {
width: 100%;
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: center;
.search-list-box {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
width: 686rpx;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,219 @@
<!-- 瀑布流 -->
<template>
<view class="list ">
<view class="list-box ">
<!-- 左边数据 -->
<view class="list-box-ite">
<view @click="clickItem(item)" class="list-box-ite-item" v-for="(item, index) in datas.arrListLeft"
:key="index">
<view class="list-box-ite-item-img">
<u-lazy-load border-radius="24rpx 24rpx 0 0" :image="item.titleImg"></u-lazy-load>
<view class="list-box-ite-item-img-t" v-if="item.over == 1">
{{ item.courseDetailsCount ? item.courseDetailsCount : 0 }}
</view>
<view class="list-box-ite-item-img-t" v-else>
更新至{{ item.courseDetailsCount ? item.courseDetailsCount : 0 }}
</view>
</view>
<view class="list-box-ite-item-txt">
<view class="list-box-ite-item-txt-t">
{{ item.title }}
</view>
<view class="list-box-ite-item-txt-l" v-if="item.courseLabel">
{{ item.courseLabel }}
</view>
</view>
</view>
</view>
<!-- 右边数据 -->
<view class="list-box-ite">
<view @click="clickItem(item)" class="list-box-ite-item" v-for="(item, index) in datas.arrListRight"
:key="index">
<view class="list-box-ite-item-img">
<u-lazy-load border-radius="24rpx 24rpx 0 0" :image="item.titleImg"></u-lazy-load>
<view class="list-box-ite-item-img-t" v-if="item.over == 1">
{{ item.courseDetailsCount ? item.courseDetailsCount : 0 }}
</view>
<view class="list-box-ite-item-img-t" v-else>
更新至{{ item.courseDetailsCount ? item.courseDetailsCount : 0 }}
</view>
</view>
<view class="list-box-ite-item-txt">
<view class="list-box-ite-item-txt-t">
{{ item.title }}
</view>
<view class="list-box-ite-item-txt-l" v-if="item.courseLabel">
{{ item.courseLabel }}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
reactive,
watch
} from "vue";
import {
onShow, onReachBottom
} from '@dcloudio/uni-app'
const props = defineProps({
list: {
type: Array,
default: []
},
})
let datas = reactive({
arrListLeft: [], //
arrListRight: [], //
})
onShow(() => {
spliceArrayListr()
})
watch(() => props.list, () => {
spliceArrayListr()
})
function spliceArrayListr() {
datas.arrListRight = []
datas.arrListLeft = []
props.list.map((item, index) => {
if (index % 2 === 0) {
datas.arrListLeft.push(item)
} else {
datas.arrListRight.push(item)
}
})
}
</script>
<style lang="scss">
.list {
width: 100%;
height: auto;
display: flex;
align-items: center;
justify-content: center;
.list-box {
width: 686rpx;
height: 100%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.list-box-ite {
width: calc((100% - 20rpx) / 2);
height: auto;
}
.list-box-ite-item {
width: 100%;
height: auto;
margin-bottom: 20rpx;
.list-box-ite-item-img {
width: 100%;
border-radius: 24rpx 24rpx 0 0;
min-height: 300rpx;
position: relative;
image {
width: 100%;
min-height: 300rpx;
border-radius: 24rpx 24rpx 0 0;
}
.list-box-ite-item-img-t {
position: absolute;
bottom: 10rpx;
right: 0;
max-width: 80%;
border-radius: 10rpx;
background-color: rgba(51, 51, 51, 0.7);
color: #FFFFFF;
font-size: 22rpx;
padding: 10rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.list-box-ite-item-txt {
padding: 10rpx 20rpx;
background-color: #ffffff;
border-radius: 0 0 24rpx 24rpx;
}
.list-box-ite-item-txt-t {
color: #333333;
font-size: 30rpx;
font-weight: bold;
}
.list-box-ite-item-txt-l {
color: #999999;
font-size: 22rpx;
margin-top: 10rpx;
}
}
.list-box-item {
width: calc((100% - 20rpx) / 2);
// height: 100%;
min-height: 320rpx;
border-radius: 24rpx;
background-color: #ffffff;
margin-bottom: 20rpx;
}
.list-box-item-img {
width: 100%;
height: 200rpx;
border-radius: 24rpx 24rpx 0 0;
image {
width: 100%;
height: 200rpx;
border-radius: 24rpx 24rpx 0 0;
}
}
.list-box-item-txt {
width: 100%;
// height: 120rpx;
padding: 20rpx 0;
border-radius: 0 0 24rpx 24rpx;
}
.list-box-item-txt-t {
width: 100%;
padding: 0 20rpx;
color: #333333;
font-size: 30rpx;
font-weight: bold;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.list-box-item-txt-l {
color: #999999;
font-size: 22rpx;
margin-top: 10rpx;
padding: 0 20rpx;
}
}
</style>

View File

@ -0,0 +1,249 @@
## 1.9.72023-08-02
本次更新:
- 调整 `draw-before` 为自定义函数,且该函数为必备函数,转盘能否启动,将根据该函数中调用 `callback` 时传递的 `Boolean` 进行判断
## 1.9.6.22023-07-28
本次更新:
- 更新示例项目
## 1.9.6.12023-07-28
本次更新:
- 修复参数文档排版问题
## 1.9.62023-07-28
本次更新:
- 新增`drawStartBefore`钩子,请看说明文档
## 1.9.52023-07-12
本次更新:
- 优化 `duration` 参数的变更需要刷新才能生效的开发体验
- 新增 `duration``ringCount` 设置不合理时的 `console` 提醒
## 1.9.42023-07-06
本次更新:
- 修复某些情况文字无法换行的问题
- 参数`strMaxLen`设置为`0`时不限制文字长度
## 1.9.32023-06-12
本次更新:
- 新增 `selfTime` 参数,查看文档说明
- 示例项目分离 `uni-popup` 用例为独立页面进行展示
## 1.9.22023-05-22
本次更新:
- 新增 `renderDelay` 参数,请查看文档
- 示例项目新增 `ui-popup` 组件的用例,使用 `uni-popup` 包裹转盘时,请前往 `uni-popup` 组件文档关注平台兼容性问题
## 1.9.12023-03-07
本次更新:
- 新增 `selfRotaty` 自转参数视觉效果上存在细微瑕疵欢迎PR
## 1.8.332022-07-04
本次更新:
- 统一奖品图片下载方式
## 1.8.322022-06-20
本次更新:
- 调整平台兼容性(因 HBX 存在 bug导致平台兼容性的更新多次失败近几次的更新都可以忽略
## 1.8.312022-06-20
本次更新:
- 无意义的更新,请忽略
## 1.8.302022-06-20
本次更新:
- 调整平台兼容性
## 1.8.292022-06-19
本次更新:
- 调整平台兼容信息
## 1.8.282022-06-19
本次更新:
- 修复单个尺寸过大的 canvas 在 H5/APP-vue iOS/Safari 中存在可能无法绘制成功的问题
## 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 插件模式

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
{
"id": "almost-lottery",
"displayName": "Almost-Lottery抽奖转盘",
"version": "1.9.7",
"description": "【荣获2021插件大赛三等奖】提供奇数、缓存等众多配置项更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中完美支持APP、各平台小程序、H5、PC同时提供 uniCloud 云端版本",
"keywords": [
"转盘",
"抽奖",
"转盘抽奖",
"大转盘",
"大转盘抽奖"
],
"repository": "https://github.com/ialmost/almost-components_uniapp",
"engines": {
"HBuilderX": "^3.7.11"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"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",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "n"
}
}
}
}
}

View File

@ -0,0 +1,174 @@
# 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-before="handleDrawBefore"
@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 (callback) {
// 这里需要处理你抽奖之前的逻辑
// 请查看示例项目中的代码
// 必须调用 callback 并传递一个布尔值,布尔值为 true 时,转盘才会开始旋转
let flag = true
callback(flag)
},
// 本次抽奖开始
handleDrawStart () {
// 这里需要处理你的抽奖逻辑,并得出中奖物品的 prizeIndex
// 请查看示例项目中的代码
},
// 本次抽奖结束
handleDrawEnd () {
// 完成抽奖后,这里处理你拿到结果后的逻辑
// 请查看示例项目中的代码
},
// 抽奖转盘绘制完成
handleDrawFinish (res) {
// 抽奖转盘准备就绪后,这里处理你的逻辑
// 请查看示例项目中的代码
// console.log('抽奖转盘绘制完成', res)
}
}
}
```
## API
#### Props
参数 | 说明 | 类型 | 默认值
:---|:---|:---|:---
pixelRatio | 移动端设计稿的像素比基准值,**涉及到 `rpx` 的适配问题** | *`Number`* | `2`
canvasId | Canvas的标识**多画板情况下需要配置不同的标识** | *`String`* | `'almostLottery'`
renderDelay | 转盘的渲染延时,**转盘被包裹在 uni-popup 组件内且开启了动画,请设置成最少 300** | *`Number`* | `0`
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'`
selfRotaty | 是否开启自转,开启后`duration`和`ringCount`参数不生效 | *`Boolean`* | `false`
selfTime | 开启自转时,最少转多少毫秒 | *`Number`* | `1000`
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 | 奖品名称长度限制,为`0`时不限制,**文字竖向时不生效** | *`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-before | 转盘旋转之前触发,**该事件必须默认写入到 `template` 中,不可删除** | `callback(Boolean)`
@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,298 @@
/**
* 存储 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) => {
uni.downloadFile({
url: fileUrl,
success: (res) => {
resolve({
ok: true,
data: res.errMsg,
tempFilePath: res.tempFilePath
})
},
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'))
})
}

764
pages/me/prizeDraw.vue Normal file
View File

@ -0,0 +1,764 @@
<template>
<view class="almost-lottery min-page" >
<!-- <u-navbar title="" back-icon-color="#fff" :background="background" immersive :border-bottom="false"
title-color="#fff"></u-navbar> -->
<!-- head -->
<view class="almost-lottery__head">
<view class="btn-group u-flex u-row-between">
<view :class="['action', isApple && 'action-shadow']" @click="toRed">
<text class="pack"></text>
<text class="content">
红包
<text class="num">{{ totalMoney }}</text>
</text>
</view>
<view :class="['action', isApple && 'action-shadow']" @click="toGift">
<text class="gift"></text>
<text class="content">我的奖品</text>
</view>
</view>
<!-- <view class="tip"><text class="tip-content">每次抽奖消耗 {{ goldNum }} 金币不限次数</text></view> -->
</view>
<!-- action -->
<!-- <view class="almost-lottery__action-dev" @tap="handleInitCanvas" v-if="isDev">
<text class="text">重新生成画板-开发模式使用</text>
</view>
<view class="almost-lottery__action-dev" @tap="handleCheckPopup">
<text class="text">查看 uni-popup 用例</text>
</view> -->
<!-- lottery -->
<view class="almost-lottery__wheel">
<view class="almost-lottery__count">
<text class="text">剩余免费抽奖 {{ freeNum }} </text>
</view>
<!-- <button @click="showLingPop">showLingPop</button> -->
<almost-lottery :lottery-size="lotteryConfig.lotterySize" :action-size="lotteryConfig.actionSize"
:selfTime="selfTime" :ring-count="2" :duration="1" :self-rotaty="false" :img-circled="true"
:canvasCached="true" :prize-list="prizeList" :prize-index="prizeIndex" @reset-index="prizeIndex = -1" @draw-before="handleDrawBefore"
@draw-start="handleDrawStart" @draw-end="handleDrawEnd" @finish="handleDrawFinish"
v-if="prizeList.length" />
</view>
<!-- rule -->
<view class="almost-lottery__rule" style="padding-bottom: 80rpx;">
<view class="rule-head">
<view class="line"></view>
<text class="title">活动规则</text>
<view class="line"></view>
</view>
<view class="rule-body" >
<view class="item">
<view class="number">1</view>
<view class="text">
<text>抽奖细则</text>
<text>每日前{{ freeNumDay }}次付款均可获得抽奖机会</text>
</view>
</view>
<view class="item item-rule" >
<view class="number">2</view>
<view class="text" >
<text>奖励说明红包奖励将自动发放到红包余额已绑定支付宝账号将会自动发起提现其余奖品则需联系客服领取</text>
<!-- <text>b.金币奖系统会即时转入金币账户可在平台内使用</text> -->
</view>
</view>
<!-- <template >
<view class="item">
<view class="number">3</view>
<view class="text">本次活动由XXXXXXX发起</view>
</view>
<view class="item">
<view class="number">4</view>
<view class="text">本活动仅限17岁以上用户参加</view>
</view>
<view class="item">
<view class="number">5</view>
<view class="text">本活动最终解释权归XXXXXXX所有</view>
</view>
</template> -->
</view>
</view>
<ling-qu ref="refLingqu" @close="lingquClose"></ling-qu>
<button style="visibility: hidden;" @click="wKCRevSZRBgtJMfzgyqn"></button>
<button style="visibility: hidden;" @click="IqfnoiUhUSVgyqIbBStZ"></button>
<button style="visibility: hidden;" @click="GqaRbIwfqGWwDJgyqSLh"></button>
</view>
</template>
<script>
import AlmostLottery from '@/uni_modules/almost-lottery/components/almost-lottery/almost-lottery.vue';
import lingQu from '@/components/pop-ling-qu.vue';
import { clearCacheFile, clearStore } from '@/uni_modules/almost-lottery/utils/almost-utils.js';
import { drawCount, selectUserMoney, selectDiscSpinning } from '@/api/index/index.js';
export default {
name: 'Home',
components: {
AlmostLottery,
lingQu
},
data() {
return {
// 1
// 2
// 3
source: 1,
background: {
background: 'transparent'
},
//
totalMoney: 0,
//
result: '',
//
isDev: true,
option: {},
//
lotteryConfig: {
// rpx
lotterySize: 600,
// rpx
actionSize: 200
},
selfTime: 2000,
// UI
// 稿
// lotteryBg: require('@/uni_modules/almost-lottery/static/almost-lottery/almost-lottery__bg2x.png'),
//
// actionBg: require('@/uni_modules/almost-lottery/static/almost-lottery/almost-lottery__action2x.png'),
//
//
prizeList: [],
//
onStock: true,
//
prizeIndex: -1,
//
prizeing: false,
//
//
onFrontend: false,
//
prizeWeightMax: 0,
//
prizeWeightArr: [],
//
//
goldCoin: 20,
//
freeNum: 0,
//
goldNum: 20,
//
freeNumDay: 0
};
},
computed: {
isApple() {
return uni.getSystemInfoSync().platform === 'ios';
}
},
methods: {
lingquClose(key) {
console.log('lingquClose');
console.log(key);
if (key && key == 'isBindAliPay') {
uni.navigateTo({
url: '/me/invite/zhifubao'
});
}
},
toRed() {
uni.navigateTo({
url: '/me/yaoqing/ymg-yaoqing-tixian'
});
},
toGift() {
console.log('1');
uni.navigateTo({
url: '/me/giftczgw/giftczgw?source=' + this.source
});
},
//
handleInitCanvas() {
clearCacheFile();
clearStore();
this.prizeList = [];
this.getPrizeList();
},
// popup
handleCheckPopup() {
uni.navigateTo({
url: '/pages/popup/popup'
});
},
//
async getPrizeList() {
uni.showLoading({
title: '奖品准备中...'
});
//
let res = await this.requestApiGetPrizeList();
console.log('获取奖品列表', res);
if (res.ok) {
let data = res.data;
if (data.length) {
this.prizeList = data;
console.log('已获取到奖品列表数据,开始绘制抽奖转盘');
//
if (console.time) {
console.time('绘制转盘用时');
}
//
//
if (this.onFrontend) {
//
this.prizeWeightArr = this.prizeList.map((item) => item.prizeWeight);
let prizeWeightArrSort = [...this.prizeWeightArr];
prizeWeightArrSort.sort((a, b) => b - a);
//
this.prizeWeightMax = this.prizeWeightMax > 0 ? this.prizeWeightMax : prizeWeightArrSort[0];
}
}
} else {
uni.hideLoading();
uni.showToast({
title: '获取奖品失败',
mask: true,
icon: 'none'
});
}
},
//
// Promise
//
async requestApiGetPrizeList() {
const res = await selectDiscSpinning({ source: this.source });
return {
ok: true,
data: res.records.map((v) => {
return {
...v,
prizeId: v.id,
prizeName: v.name,
prizeStock: 10,
prizeWeight: 200,
prizeImage: v.url
// prizeImage: require('@/static/git.png')
};
})
};
return;
// return new Promise((resolve, reject) => {
// let requestTimer = setTimeout(() => {
// clearTimeout(requestTimer)
// requestTimer = null
// // prizeStock
// // prizeWeight
// resolve({
// ok: true,
// data: [{
// prizeId: 1,
// prizeName: '0.1',
// prizeStock: 10,
// prizeWeight: 200,
// prizeImage: require('@/static/git.png')
// },
// {
// prizeId: 2,
// prizeName: '10',
// prizeStock: 0,
// prizeWeight: 50,
// prizeImage: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/56f085e0-bcfe-11ea-b244-a9f5e5565f30.png'
// },
// {
// prizeId: 3,
// prizeName: '5',
// prizeStock: 1,
// prizeWeight: 80
// },
// {
// prizeId: 4,
// prizeName: '50',
// prizeStock: 0,
// prizeWeight: 10,
// prizeImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAB2klEQVRIia3Wv09TURjG8U+u2kQZcNENh2ok0cnJ0R+L0RB2g/EvwITEwRgnV0Zx0fAHYBh0khBD4sBAmAgDiwQNg4qwmSBDAYf7Fi+n97al9UlObnvO836f3tO355b2GsAYZrCBPRzGdSPmx8J3YtXwDDsB7TR2wl/rNmAYK13C07ES9W11E9s9BjTHdnBKVf8PAcWgehpwCssF0w/5FzqLgw7AA7zDQ/wqzC8H90jjSeFMYW0Yr8PzALfjOh7z1wve2YTzpLlQw2ay+KZ1N7vS24SziVqGexhKzB07pEJXk/dDwTetdZ9HewwZLWFNw2oyudZjQFNrCW8109pqS32GpPX1TOu586fPkLR+IEMjmbzYZ0ha34Atx/fwW58hXxPeFixo7Yg7PQbcKmEtZFgsMb/C4AkDBuUnQKpFuFZI3cZuvP6C+8g6wDP572O95C4Og49/W/YJl/G5YPqO9yV3dh4f8LMCfhjcI93Afix8xAX5SVx2YBY10yagEdxjmiwY7uISXuIFrlSEPG0TMllWcAbzYViXd0rzeXC6ImSiImA+eKU6h7mCeV/eCBMnCJkLzpHSztnFCJ7L//ZkOFv1iRLtRd1IcLpSHVP4jccVnkexPqXkmd7UX15b7tiz29ReAAAAAElFTkSuQmCC'
// },
// {
// prizeId: 5,
// prizeName: '1',
// prizeStock: 3,
// prizeWeight: 3000,
// prizeImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABCElEQVRoge3YMa4BURSH8Y8o7UAp0WgkotBZwluAfhqlZSgUGr23ENUUCpppJnTswAIUSCaTiziZJ8d9/193zdzrfMltABF5plb+oLscDoAV0Pn8OC/lwDhL0k35QT3wstcIuM61Cj0IhXiNuAvOFwr5SgrxRiHeKMSbhnHfAVgU1i1gajhnBpwK6wnQtgxkDTlmSTq/L7rLYQ9byG+WpLvCOT8YQ6K5WgrxRiHeKMQbhXijEG8U4o1CvIkmxPrDquwMrI37KlFJSJake2BUxVlW0VytaEKsV6t5+8Ohak3rRmtIH9hav/QvRHO1FOKNQrwJheQfn+I9wflCIeNHLzuQc51PRP6rC1ZeIm1I8cC5AAAAAElFTkSuQmCC'
// },
// {
// prizeId: 6,
// prizeName: '0.2',
// prizeStock: 8,
// prizeWeight: 120
// },
// {
// prizeId: 7,
// prizeName: '',
// prizeStock: 100,
// prizeWeight: 10000
// },
// {
// prizeId: 8,
// prizeName: '100',
// prizeStock: 100,
// prizeWeight: 3000
// }
// ]
// })
// }, 200)
// })
},
//
async handleDrawBefore(callback) {
console.log('抽奖开始之前');
let flag = false;
//
if (this.freeNum > 0) {
flag = true;
} else {
flag = false;
uni.showToast({
title: '抽奖次数不足',
icon: 'none'
});
}
callback(flag);
},
//
handleDrawStart() {
console.log('触发抽奖按钮');
if (this.prizeing) return;
this.prizeing = true;
this.tryLotteryDraw();
},
//
tryLotteryDraw() {
console.log('旋转开始,获取中奖下标......');
//
if (this.onFrontend) {
this.localGetPrizeIndex();
} else {
this.remoteGetPrizeIndex();
}
},
//
localGetPrizeIndex() {
console.warn('###当前处于前端控制中奖概率,安全起见,强烈建议由后端控制###');
//
// prizeId
if (!this.prizeWeightMax || !this.prizeWeightArr.length) {
console.warn('###当前已开启前端控制中奖概率,但是奖品数据列表中的 prizeWeight 参数似乎配置不正确###');
return;
}
console.log('当前权重最大值为 =>', this.prizeWeightMax);
// 使 Math.ceil 0
let randomWeight = Math.ceil(Math.random() * this.prizeWeightMax);
console.log('本次权重随机数 =>', randomWeight);
//
let tempMaxArrs = [];
this.prizeList.forEach((item) => {
if (item.prizeWeight >= randomWeight) {
tempMaxArrs.push(item.prizeWeight);
}
});
console.log('tempMaxArrs', tempMaxArrs);
//
//
let tempMaxArrsLen = tempMaxArrs.length;
if (tempMaxArrsLen) {
tempMaxArrs.sort((a, b) => a - b);
//
if (tempMaxArrsLen > 1) {
//
let sameCount = 0;
for (let i = 0; i < tempMaxArrs.length; i++) {
if (tempMaxArrs[i] === tempMaxArrs[0]) {
sameCount++;
}
}
// 1
if (sameCount === 1) {
this.prizeIndex = this.prizeWeightArr.indexOf(tempMaxArrs[0]);
} else {
//
let sameWeight = tempMaxArrs[0];
let sameWeightArr = [];
let sameWeightItem = {};
this.prizeWeightArr.forEach((item, index) => {
if (item === sameWeight) {
sameWeightArr.push({
prizeWeight: item,
index
});
}
});
console.log('sameWeightArr', sameWeightArr);
sameWeightItem = sameWeightArr[Math.floor(Math.random() * sameWeightArr.length)];
console.log('sameWeightItem', sameWeightItem);
this.prizeIndex = sameWeightItem.index;
}
} else {
this.prizeIndex = this.prizeWeightArr.indexOf(tempMaxArrs[0]);
}
}
console.log('本次抽中奖品 =>', this.prizeList[this.prizeIndex].prizeName);
//
if (this.onStock) {
console.log('本次奖品库存 =>', this.prizeList[this.prizeIndex].prizeStock);
}
},
//
//
async remoteGetPrizeIndex() {
this.result = '';
console.warn('###当前处于模拟的请求接口,并返回了中奖信息###');
const res = await this.$Request.getT('/app/discSpinning/draw', { source: this.source });
this.freeNum--;
// this.getCount()
console.log(res);
if (res.code != 0) {
return uni.showToast({
title: res.msg
});
}
this.result = res.data;
let list = [...this.prizeList];
// prizeId prizeId
const arr = list.filter((v) => v.type == res.data.type);
let prizeId = arr[0].prizeId;
// prizeId
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.prizeId === prizeId) {
//
this.prizeIndex = i;
break;
}
}
console.log('本次抽中奖品 =>', this.prizeList[this.prizeIndex].prizeName);
},
//
handleDrawEnd() {
console.log('旋转结束,执行拿到结果后到逻辑');
//
// const prize = this.prizeList[this.prizeIndex]
const prize = this.result;
let { name } = prize;
let tipContent = '';
if (name.type == 1) {
tipContent = '很遗憾,没有中奖,请再接再厉!';
} else {
tipContent = `恭喜您,获得 ${name}${this.result.type == 2 ? this.result.number + '元' : ''} `;
}
const _this = this;
console.log(this.result);
this.showLingPop({ ...this.result });
this.result = '';
this.prizeing = false;
return;
// uni.showModal({
// content: tipContent,
// showCancel: false,
// success() {
// const {
// orderId,id
// }=_this.result
// _this.$Request.postJson('app/discSpinning/receive',_this.result).then(res=>{
// _this.result=''
// console.log(res)
// if(res.code==0){
// uni.showToast({
// title: '',
// icon:'none'
// })
// _this.getRedPack()
// }else{
// uni.showToast({
// title: '',
// icon:'none'
// })
// }
// })
// },
// complete: () => {
// this.prizeing = false
// }
// })
},
//
handleDrawFinish(res) {
console.log('抽奖转盘绘制完成', res);
if (res.ok) {
//
if (console.timeEnd) {
console.timeEnd('绘制转盘用时');
}
}
let stoTimer = setTimeout(() => {
stoTimer = null;
uni.hideLoading();
// uni.showToast({
// title: res.msg,
// mask: true,
// icon: 'none'
// })
}, 50);
},
async getRedPack() {
const res = await selectUserMoney();
this.totalMoney = res.amount;
},
async getCount() {
const res = await drawCount({ source: this.source });
this.freeNum = res.count || 0;
this.freeNumDay = res.sum || 0;
},
lingquClose() {
// this.getCount()
this.getRedPack();
},
showLingPop(data) {
this.$refs.refLingqu.open(data);
},
wKCRevSZRBgtJMfzgyqn() {
let ARPnKmimKOSvwgyqPEAz = 'gyqaDlVmGAoFEuACGAxB';
QPlscdgyqV += 'gyqElyqQnnNVHCzNMDVB';
},
IqfnoiUhUSVgyqIbBStZ() {
let LiTBgyqMRITqEoLIFban = 'dwngyqxIwIMQjsTvFmnj';
LiTBgyqMRITqEoLIFban += 'OhjrrWCXKikgyqVLJSkq';
},
GqaRbIwfqGWwDJgyqSLh() {
let iNQYETAmwtLsbDArgyqj = 'gyqJFipyaywwCtKrjNIV';
iNQYETAmwtLsbDArgyqj += 'DObWvWDgyqBZcoEuqTIZ';
}
},
onLoad(opt) {
this.option = opt;
if (opt.source) {
this.source = opt.source;
}
this.prizeList = [];
this.getCount();
this.getRedPack();
this.getPrizeList();
},
onUnload() {
uni.hideLoading();
}
};
</script>
<style lang="scss" scoped>
.btn-group {
position: absolute;
left: 0;
right: 0;
z-index: 2;
top: 180px;
gap: 20rpx;
padding: 0 32rpx;
}
.almost-lottery {
flex: 1;
background-color: #ff893f;
}
.almost-lottery__head {
position: relative;
width: 100%;
height: 460rpx;
background: url('@/static/index/top-bg.png') no-repeat center center/cover;
.action {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
height: 88rpx;
line-height: 88rpx;
margin: 0 auto;
color: #ffffff;
font-size: 32rpx;
background-color: rgba(255, 136, 61, 1);
border-radius: 44rpx;
}
.action-shadow {
box-shadow: 0px 14rpx 0px 0px rgba(235, 112, 36, 1);
}
.pack {
width: 44rpx;
height: 44rpx;
margin-right: 10rpx;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
background-image: url('~static/red-pack.png');
}
.gift {
width: 44rpx;
height: 44rpx;
margin-right: 10rpx;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
background-image: url('~static/gift.png');
}
.num {
color: #f9fc31;
}
.tip {
position: relative;
top: 428rpx;
color: #ffffff;
font-size: 24rpx;
text-align: center;
}
}
.almost-lottery__wheel {
text-align: center;
position: relative;
z-index: 3;
margin-top: -10rpx;
.almost-lottery__count {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 20rpx 0 40rpx 0;
}
.text {
color: #ffffff;
font-size: 24rpx;
}
}
.almost-lottery__rule {
padding: 0 28rpx;
color: #fff8cb;
.rule-head {
display: flex;
justify-content: space-around;
align-items: center;
margin: 40rpx 0;
.line {
flex: 1;
height: 1px;
background-color: #fff3a5;
}
.title {
width: 280rpx;
color: #f63857;
line-height: 70rpx;
text-align: center;
margin: 0 20rpx;
border-radius: 8rpx;
background-image: linear-gradient(0deg, rgba(255, 242, 158, 1), rgba(255, 244, 168, 1));
}
}
.rule-body {
color: #fff8cb;
font-size: 24rpx;
padding: 10rpx 0 40rpx;
.item {
display: flex;
margin-bottom: 10rpx;
}
.number {
position: relative;
top: 4rpx;
width: 28rpx;
height: 28rpx;
line-height: 28rpx;
text-align: center;
color: #f63857;
background: #fff8cb;
border-radius: 50%;
margin-right: 10rpx;
}
.text {
flex: 1;
}
.item-rule .text {
display: flex;
flex-direction: column;
}
}
}
.almost-lottery__action-dev {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 400rpx;
height: 80rpx;
border-radius: 10rpx;
text-align: center;
background-color: red;
margin: 0 auto 40rpx;
.text {
color: #ffffff;
font-size: 28rpx;
}
}
.almost-lottery__popup-wrap {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
.almost-lottery {
background: transparent;
}
}
</style>

View File

@ -1,11 +1,415 @@
<template>
ios
anzhuo
<view style="background-color: #F3F4F8;">
<view class="task_bg u-absolute">
<image class="task_bg" src="@/static/task/task_bg.png"></image>
</view>
<view class="icon">
<image class="task_icon1" src="@/static/task/task_icon1.png"></image>
<view class="icon_title">新人好礼送不停</view>
<image class="task_icon2" src="@/static/task/task_icon2.png"></image>
</view>
<view class="contentTop ">
<view>
<image src="@/static/task/renwubg.png" mode=""></image>
<view>已连续签到 <text class="num">{{ datas.signDays }}</text> </view>
</view>
<view>
<view v-for="item in datas.signInList" :key="item.id"
:style="item.status == 1 ? 'color:#EFA765' : 'color:#999'">
{{ item.status == 1 ? '已签到' : '待签到' }}
<view :style="item.status == 1 ? 'color:#999' : 'color:#EFA765'">
{{ item.signDay.substr(5, 8) }}
</view>
<image v-if="item.status == 0" src="@/static/task/xing (1).png" mode=""></image>
<image v-else src="@/static/task/xing (2).png" mode=""> </image>
</view>
</view>
</view>
<view class="content " v-if="datas.list.length">
<view class="cell " v-for="(item, index) in datas.list" :key="index">
<view class="cell_left ">
<view class="cell_title ">
<view class=" title" :style="{ alignSelf: item.rewardImg ? 'center' : 'flex-start' }">{{
item.title
}}</view>
<image v-if="item.rewardImg" class="cell_icon" :src="item.rewardImg" mode=""></image>
<view class="tip">{{ item.rewardDetail }}</view>
</view>
<view class="subhead ">{{ item.detail }}</view>
</view>
<view class="cell_right ">
<view v-if="item.disabled" class="btn "
:style="{ backgroundColor: item.buttonBgColor, color: item.buttonFontColor }"
@tap="goNav(item)">
{{ item.type == 1 ? item.buttonTitle : item.number ? `${item.discNumber}/${item.number}` : (
item.discNumber <= 0 ? item.buttonTitle : `剩余${item.discNumber}`) }} </view>
<view v-else class="btn disabled">
{{ item.type == 1 ? item.buttonTitle : item.number ? `${item.discNumber}/${item.number}`
: (item.discNumber <= 0 ? item.buttonTitle : `剩余${item.discNumber}`) }} <view
v-if="item.buttonUnderContent && item.buttonUnderUrl" class=" tip"
@tap="goNav(item, item.buttonUnderUrl)">
{{ item.buttonUnderContent }}
</view>
</view>
</view>
</view>
</view>
<!-- 激励视频广告 -->
<!-- <ad-rewarded-video v-if="adRewardedVideo" ref="adRewardedVideo" adpid="1531580352" :loadnext="true"
v-slot:default="{ loading, error }" :url-callback="datas.urlCallback" @load="onadload" @close="onadclose"
@error="onaderror">
<view class="ad-error" v-if="error">{{ error }}</view>
</ad-rewarded-video> -->
</view>
</template>
<script setup>
import {
reactive,
getCurrentInstance, nextTick
} from "vue";
import {
onShow, onReachBottom
} from '@dcloudio/uni-app'
import { getUserSignData, selectTaskCenter, taskReceive } from '@/api/task/index.js'
const currentInstance = getCurrentInstance()
let datas = reactive({
signDays: 0,
signInList: [],
list: [],
// ios
isExamine: uni.getStorageSync('isExamine'),
urlCallback: {},
adRewardedVideo: true,
})
onShow(() => {
getTaskdata()
getsignIn()
// nextTick(() => {
// currentInstance.ctx.$refs.adRewardedVideo.load();
// })
})
//广
async function onadclose(e) {
const detail = e.detail
if (detail && detail.isEnded) {
//
// /sqx_fast/app/ad/state
let res = await this.$Request.getT('app/ad/state', {
extraKey: this.urlCallback.extra
})
this.$Request.getT('/app/common/type/921').then(res => {
if (res.code == 0) {
console.log(res)
uni.showToast({
title: '获得' + res.data.value + '分钟免费时长',
icon: 'none'
})
}
})
} else {
// 退
}
}
async function goNav(item, url) {
// jumpType (integer, optional): 1 2 ,
// type (integer, optional): 1 2 9 ,
if (url) {
console.log(url, 'debug12')
let urls = ''
if (url == '/pages/task/prizeList') {
urls = url + '?source=2'
}
if (url == '/pages/task/receiveMember') {
urls = url + `?standard=${item.discNumber == null ? true : false}&taskId=${item.id}`
}
uni.navigateTo({
url: urls
})
} else {
if (item.jumpType == 0) {
if (item.title.indexOf("新人福利") != -1) {
let res = await taskReceive({ id: item.id })
if (res.code == 0) {
uni.showToast({
title: res.id == 15 ? '签到成功' : '领取成功',
icon: 'none'
})
setTimeout(() => {
getTaskdata()
getsignIn()
}, 1000)
} else {
uni.switchTab({
url: '/pages/index/index'
})
}
} else if (item.buttonTitle.indexOf("观看视频") != -1) {
// 广
datas.urlCallback = {
userId: uni.getStorageSync('userId'),
extra: uni.getStorageSync('userId') + "" + new Date().getTime(),
}
currentInstance.ctx.$refs.adRewardedVideo.show();
} else {
uni.switchTab({
url: "/pages/index/index"
})
}
} else if (item.jumpType == 1) {
let buttonUrl = item.buttonUrl
console.log(buttonUrl, 'debug')
if (item.title && item.title.indexOf('每周打卡奖励') != -1) {
buttonUrl = item.buttonUrl + '?source=2'
}
if (item.title && item.title.indexOf('每月打卡奖励1') != -1) {
buttonUrl = item.buttonUrl + '?source=3'
}
if (item.title && item.title.indexOf('每月打卡奖励2') != -1) {
buttonUrl = `${item.buttonUrl}?standard=${item.discNumber == null ? true : false}&taskId=${item.id}`
}
uni.navigateTo({
url: buttonUrl
})
} else if (item.jumpType == 3) {
uni.switchTab({
url: item.buttonUrl
})
}
}
}
function onadload(e) {
datas.adRewardedVideo = true;
}
//
async function getsignIn() {
let res = await getUserSignData()
datas.signInList = res.recordList
datas.signDays = res.signDays
}
//
async function getTaskdata() {
let res = await selectTaskCenter()
if (!datas.isExamine) {
let arrData = []
res.forEach(ele => {
if (ele.title.indexOf('分享奖励') == -1 && ele.title.indexOf('新人福利') == -1 &&
ele.title.indexOf('观看视频奖励') == -1) {
arrData.push(ele)
}
})
datas.list = arrData
} else {
datas.list = res
}
}
</script>
<style>
<style scoped lang="scss">
.u-absolute {
position: absolute;
}
.task_bg {
width: 100% !important;
height: 494rpx !important;
}
.icon {
position: relative;
padding: 32rpx;
.icon_title {
color: #000000;
margin-left: 28rpx;
top: -20rpx;
font-size: 32rpx;
font-weight: bold;
position: relative;
}
.task_icon1 {
width: 74rpx !important;
height: 78rpx !important;
margin-left: 235rpx;
position: relative;
}
.task_icon2 {
width: 126rpx !important;
height: 120rpx !important;
top: -40rpx;
position: relative;
}
}
.contentTop {
border-radius: 16rpx;
top: -115rpx;
padding: 28rpx;
margin-bottom: 32rpx;
position: relative;
background-color: #ffffff;
color: #666666;
margin-left: 32rpx;
margin-right: 32rpx;
>view:first-child {
>image {
width: 100%;
height: 78rpx;
position: absolute;
left: 0;
top: 0;
z-index: 1
}
>view {
z-index: 9999;
position: absolute;
text-align: center;
width: 98%;
.num {
color: #EC6F48;
margin: 0 10rpx;
}
}
}
>view:last-child {
margin-top: 100rpx;
display: flex;
align-items: center;
justify-content: space-between;
>view {
position: relative;
>view {
margin-top: 20rpx;
width: 86rpx;
height: 94rpx;
background: linear-gradient(180deg, #FFF7E3 0%, #FFFFFF 100%);
border-radius: 14rpx 14rpx 0rpx 0rpx;
}
>image {
width: 52rpx;
height: 48rpx;
position: absolute;
top: 110rpx;
left: 14rpx;
}
}
}
}
.content {
border-radius: 16rpx;
top: -115rpx;
padding: 28rpx;
background-color: #ffffff;
color: #666666;
margin-left: 32rpx;
margin-right: 32rpx;
position: relative;
border-radius: 16rpx;
top: -115rpx;
padding: 28rpx;
.cell {
padding: 32rpx 0;
border-bottom: 2rpx solid #EBEBEB;
display: flex;
justify-content: space-between;
.cell_left {
flex-direction: column;
margin-top: 10rpx;
.cell_title {
margin-bottom: 12rpx;
color: #333;
display: flex;
.title {
display: flex;
flex-shrink: 0;
font-size: 28rpx;
font-weight: bold;
}
.cell_icon {
width: 48rpx !important;
height: 34rpx !important;
margin-left: 16rpx;
}
.tip {
margin-left: 22rpx;
color: #FC5B67;
font-size: 24rpx;
}
}
.subhead {
color: #999;
font-size: 24rpx;
}
}
.cell_right {
flex-direction: column;
align-items: center;
flex-shrink: 0;
margin-left: 20rpx;
.btn {
width: 148rpx;
height: 56rpx;
line-height: 56rpx;
text-align: center;
background-color: #EC6F48;
color: #FFFFFF;
border-radius: 12rpx 12rpx 12rpx 12rpx;
font-size: 24rpx;
font-weight: bold;
}
.tip {
color: #EC6F48;
height: 44rpx;
line-height: 44rpx;
margin-top: 5rpx;
font-size: 22rpx;
}
.opt {
color: #D39B7E;
background-color: #FBF3EB;
}
.disabled {
color: #999999;
background-color: #E2E2E2;
}
}
}
.cell:last-child {
border-bottom: none;
}
}
</style>

89
pages/task/prizeList.vue Normal file
View File

@ -0,0 +1,89 @@
<!-- 任务中心 -->
<template>
<view class="container">
<view class="task_bg "></view>
<view class="content " v-if="datas.list.length">
<view class="cell " v-for="(item, index) in datas.list" :key="index">
<image class="cell_icon" :src="item.url"></image>
<view class="name ">{{ item.name }}</view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import { selectDiscSpinning } from '@/api/task/index.js'
let datas = reactive({
list: [],
source: null
})
onLoad(options => {
datas.source = options.source
getList()
})
async function getList() {
let res = await selectDiscSpinning({ source: datas.source })
datas.list = res.records
}
</script>
<style lang="scss">
.container {
width: 100%;
height: 100vh;
.task_bg {
width: 100%;
height: 100%;
position: absolute;
top: 0;
background: url('../../static/task/prize_bg.png') no-repeat top center / cover;
}
.content {
z-index: 9;
padding: 370rpx 28rpx 0 28rpx;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
position: relative;
.cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background: linear-gradient(315deg, #FFFFFF 0%, #F9E3D9 100%);
border-radius: 32rpx 32rpx 32rpx 32rpx;
border: 4rpx solid #FFFFFF;
margin-bottom: 48rpx;
padding: 36rpx 28rpx;
.cell_icon {
width: 244rpx !important;
height: 198rpx !important;
border-radius: 16rpx !important;
margin-bottom: 32rpx;
}
.name {
font-weight: bold;
font-size: 28rpx;
color: #333333;
}
}
}
}
</style>

View File

@ -0,0 +1,193 @@
<!-- 任务中心 -->
<template>
<view class="container">
<view class="task_bg ">
<image class="task_bg" src="@/static/task/member_bg.png" mode=""></image>
<view class="title ">每月打卡免费领</view>
</view>
<view class="content" v-if="datas.list.length">
<view class="title">
<view>打卡奖励</view>
<view class="tip">每月免费领</view>
</view>
<view class="cell " v-for="(item, index) in datas.list" :key="index">
<view class="cell_left ">
<image class="cell_icon" :src="item.url"></image>
<view class="cell_title ">
<view class=" title">{{ item.name }}</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import { selectDiscSpinning } from '@/api/task/index.js'
let datas = reactive({
list: [],
source: null
})
onLoad(options => {
datas.source = options.source
getList()
})
async function getList() {
let res = await selectDiscSpinning({ source: datas.source })
datas.list = res.records
}
</script>
<style lang="scss">
.container {
width: 100%;
height: 100vh;
// display: flex;
// flex-direction: column;
.task_bg {
width: 100% !important;
height: 470rpx !important;
position: relative;
.title {
width: 314rpx;
height: 72rpx;
line-height: 72rpx;
text-align: center;
border-radius: 108rpx 108rpx 108rpx 108rpx;
border: 6rpx solid #3D6DC6;
font-weight: bold;
font-size: 34rpx;
color: #3D6DC6;
position: absolute;
top: 210rpx;
left: 26rpx;
}
}
.content {
color: #666666;
height: calc(100% - 410rpx);
border-radius: 16rpx;
top: -60rpx;
z-index: 9;
padding: 64rpx 28rpx;
background: linear-gradient(-45deg, #DDEAFC 0%, #FFFFFF 100%);
border-radius: 32rpx 32rpx 0rpx 0rpx;
overflow-y: auto;
position: relative;
>.title {
display: flex;
align-items: center;
font-weight: bold;
font-size: 32rpx;
color: #333333;
margin-bottom: 22rpx;
.tip {
font-weight: 400;
font-size: 24rpx;
color: #666666;
margin-left: 10rpx;
}
}
.cell {
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(90deg, #FFF4E0 0%, #FFFBF5 100%);
border-radius: 8rpx 8rpx 8rpx 8rpx;
margin-bottom: 32rpx;
padding: 32rpx 28rpx;
.cell_left {
display: flex;
flex-direction: row;
align-items: center;
.cell_icon {
width: 84rpx !important;
height: 84rpx !important;
margin-right: 30rpx;
border-radius: 4rpx !important;
}
.cell_title {
display: flex;
align-items: center;
color: #333;
.title {
font-weight: bold;
font-size: 28rpx;
color: #333333;
}
.tip {
font-weight: 400;
font-size: 24rpx;
color: #999999;
}
}
}
.cell_right {
flex-direction: column;
align-items: center;
flex-shrink: 0;
margin-left: 20rpx;
.btn {
width: 148rpx;
height: 56rpx;
line-height: 56rpx;
text-align: center;
background: #6DA9F7;
border-radius: 12rpx 12rpx 12rpx 12rpx;
font-weight: 500;
font-size: 24rpx;
color: #FFFFFF;
border: 2rpx solid transparent;
}
.opt {
color: #D39B7E;
background-color: #FBF3EB;
}
.disabled {
color: #999999;
background-color: #E2E2E2;
}
.receive {
background: rgba(178, 223, 255, 0.62);
border: 2rpx solid #6DA9F7;
color: #6DA9F7;
}
}
}
.cell:last-child {
border-bottom: none;
}
}
}
</style>

View File

@ -6,19 +6,21 @@
<image class="img" :src="item.titleImg" mode="aspectFill"></image>
</view>
<view class="info">
<view class="title">
{{ item.title }}
</view>
<view class="record">看到{{ item.courseDetailsName }}</view>
<view class="btm">
<view class="num">
<view v-if="item.courseDetailsCount">更新{{ item.courseDetailsCount }}</view>
<view class="top">
<view class="title">
{{ item.title }}
</view>
<view class="record">看到{{ item.courseDetailsName }}</view>
</view>
<view class="btm">
<view class="num">更新{{ item.courseDetailsCount }}</view>
<view class="btn">继续观看</view>
</view>
</view>
</view>
</view>
<emprty-card v-if="!listData.list.length && listData.status == 'nomore'" />
<up-loadmore :status="listData.status" />
</view>
</template>
@ -43,7 +45,8 @@ const typeList = ref([
const listData = reactive({
list: [],
page: 1,
size: 10
size: 10,
status: 'loading'
});
//
@ -54,9 +57,9 @@ async function selectByUserIdAjax() {
limit: listData.size,
classify: type.value
});
console.log(res);
if (res.code === 0) {
listData.list = res.data.records;
listData.list = res.records;
if (res.currPage >= res.totalPage) {
listData.status = 'nomore';
}
} catch (error) {
console.log(error);
@ -87,7 +90,7 @@ onLoad((e) => {
}
.list {
.item {
padding: 28upx 0;
padding-bottom: 28upx;
display: flex;
.cover {
width: 150upx;
@ -101,23 +104,31 @@ onLoad((e) => {
}
}
.info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
.title {
font-size: 32upx;
font-weight: bold;
}
.record {
color: $uni-zj-color-primary;
justify-content: space-between;
.top {
.title {
font-size: 32upx;
font-weight: bold;
}
.record {
color: $uni-zj-color-primary;
}
}
.btm {
display: flex;
justify-content: space-between;
padding-top: 28upx;
.num {
color: #999;
}
.btn {
padding: 4upx 12upx;
padding: 8upx 12upx;
color: #fff;
background: $uni-zj-color-primary;
border-radius: 10upx;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

BIN
static/index/top-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

BIN
static/task/member_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

BIN
static/task/prize_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
static/task/renwubg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/task/task_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

BIN
static/task/task_icon1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
static/task/task_icon2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
static/task/xing (1).png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/task/xing (2).png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,249 @@
## 1.9.72023-08-02
本次更新:
- 调整 `draw-before` 为自定义函数,且该函数为必备函数,转盘能否启动,将根据该函数中调用 `callback` 时传递的 `Boolean` 进行判断
## 1.9.6.22023-07-28
本次更新:
- 更新示例项目
## 1.9.6.12023-07-28
本次更新:
- 修复参数文档排版问题
## 1.9.62023-07-28
本次更新:
- 新增`drawStartBefore`钩子,请看说明文档
## 1.9.52023-07-12
本次更新:
- 优化 `duration` 参数的变更需要刷新才能生效的开发体验
- 新增 `duration``ringCount` 设置不合理时的 `console` 提醒
## 1.9.42023-07-06
本次更新:
- 修复某些情况文字无法换行的问题
- 参数`strMaxLen`设置为`0`时不限制文字长度
## 1.9.32023-06-12
本次更新:
- 新增 `selfTime` 参数,查看文档说明
- 示例项目分离 `uni-popup` 用例为独立页面进行展示
## 1.9.22023-05-22
本次更新:
- 新增 `renderDelay` 参数,请查看文档
- 示例项目新增 `ui-popup` 组件的用例,使用 `uni-popup` 包裹转盘时,请前往 `uni-popup` 组件文档关注平台兼容性问题
## 1.9.12023-03-07
本次更新:
- 新增 `selfRotaty` 自转参数视觉效果上存在细微瑕疵欢迎PR
## 1.8.332022-07-04
本次更新:
- 统一奖品图片下载方式
## 1.8.322022-06-20
本次更新:
- 调整平台兼容性(因 HBX 存在 bug导致平台兼容性的更新多次失败近几次的更新都可以忽略
## 1.8.312022-06-20
本次更新:
- 无意义的更新,请忽略
## 1.8.302022-06-20
本次更新:
- 调整平台兼容性
## 1.8.292022-06-19
本次更新:
- 调整平台兼容信息
## 1.8.282022-06-19
本次更新:
- 修复单个尺寸过大的 canvas 在 H5/APP-vue iOS/Safari 中存在可能无法绘制成功的问题
## 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 插件模式

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
{
"id": "almost-lottery",
"displayName": "Almost-Lottery抽奖转盘",
"version": "1.9.7",
"description": "【荣获2021插件大赛三等奖】提供奇数、缓存等众多配置项更有抽奖概率、抽奖次数、付费抽奖等功能内置于示例项目中完美支持APP、各平台小程序、H5、PC同时提供 uniCloud 云端版本",
"keywords": [
"转盘",
"抽奖",
"转盘抽奖",
"大转盘",
"大转盘抽奖"
],
"repository": "https://github.com/ialmost/almost-components_uniapp",
"engines": {
"HBuilderX": "^3.7.11"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"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",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "n"
}
}
}
}
}

View File

@ -0,0 +1,174 @@
# 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-before="handleDrawBefore"
@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 (callback) {
// 这里需要处理你抽奖之前的逻辑
// 请查看示例项目中的代码
// 必须调用 callback 并传递一个布尔值,布尔值为 true 时,转盘才会开始旋转
let flag = true
callback(flag)
},
// 本次抽奖开始
handleDrawStart () {
// 这里需要处理你的抽奖逻辑,并得出中奖物品的 prizeIndex
// 请查看示例项目中的代码
},
// 本次抽奖结束
handleDrawEnd () {
// 完成抽奖后,这里处理你拿到结果后的逻辑
// 请查看示例项目中的代码
},
// 抽奖转盘绘制完成
handleDrawFinish (res) {
// 抽奖转盘准备就绪后,这里处理你的逻辑
// 请查看示例项目中的代码
// console.log('抽奖转盘绘制完成', res)
}
}
}
```
## API
#### Props
参数 | 说明 | 类型 | 默认值
:---|:---|:---|:---
pixelRatio | 移动端设计稿的像素比基准值,**涉及到 `rpx` 的适配问题** | *`Number`* | `2`
canvasId | Canvas的标识**多画板情况下需要配置不同的标识** | *`String`* | `'almostLottery'`
renderDelay | 转盘的渲染延时,**转盘被包裹在 uni-popup 组件内且开启了动画,请设置成最少 300** | *`Number`* | `0`
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'`
selfRotaty | 是否开启自转,开启后`duration`和`ringCount`参数不生效 | *`Boolean`* | `false`
selfTime | 开启自转时,最少转多少毫秒 | *`Number`* | `1000`
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 | 奖品名称长度限制,为`0`时不限制,**文字竖向时不生效** | *`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-before | 转盘旋转之前触发,**该事件必须默认写入到 `template` 中,不可删除** | `callback(Boolean)`
@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,298 @@
/**
* 存储 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) => {
uni.downloadFile({
url: fileUrl,
success: (res) => {
resolve({
ok: true,
data: res.errMsg,
tempFilePath: res.tempFilePath
})
},
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'))
})
}