guessing-miniprogram-fortend/src/pages/charts/charts.vue

1108 lines
26 KiB
Vue

<template>
<view class="page-activate-detail">
<mescroll-body
@init="mescrollInit"
@down="downCallback"
@up="upCallback"
:up="{
empty: {
use: false,
},
}"
>
<view class="activate-cover" v-if="detail.cover">
<image class="activate-cover" mode="aspectFill" :src="detail.cover" />
</view>
<view class="activate-box" v-if="detail.id">
<image
class="bg"
mode="widthFix"
:src="`${config.baseUrl}/images/mini-bg.jpg`"
></image>
<view class="activate-info" v-if="detail">
<view class="activate-title-box">
<view class="activate-title">{{ detail.name }}</view>
<view class="activate-rule" @click="showRuleModal(detail.rules)"
>活动规则</view
>
</view>
<view class="time">
<view>{{ detail.start_at }}</view>
<view>至</view>
<view>{{ detail.end_at }}</view>
</view>
</view>
<view class="activate-data-list">
<up-tabs
:current="tabIndex"
:list="[
{ name: '竞猜列表' },
{ name: '成绩排行' },
{ name: '竞猜记录' },
]"
@click="changeTabs"
lineColor="#1d2b5c"
:scrollable="false"
:enableFlex="true"
itemStyle="width: 50%; text-align: center; font-size: 14px; line-height: 40px;"
v-if="isDetailPage"
></up-tabs>
<view v-if="tabIndex === 0 && gameListLength">
<view
class="guess-date-group"
v-for="(block, game_day) in gameList"
:key="game_day"
>
<view class="date">{{ game_day }}</view>
<view class="table-cell" v-for="(item, key) in block" :key="key">
<view class="activate-name text-xs">{{ item.name }}</view>
<view class="guess-vs">
<view class="imgbar">
<image class="small-img" :src="item.home_logo" />
<view class="name text-4e text-xs">{{
item.home_field
}}</view>
</view>
<view class="imgbar">
<view class="small-img">VS</view>
<view class="name text-4e text-xs">{{
item.little_game_at
}}</view>
</view>
<view class="imgbar">
<image class="small-img" :src="item.away_logo" />
<view class="name text-4e text-xs">{{ item.away }}</view>
</view>
</view>
<view class="result">
<button
class="btn text-white text-xs bg-blue"
v-if="!item.has_guess && !item.game_at_comparison_now"
@click="showGuessModal(item)"
>
去竞猜
</button>
<button
class="btn text-white text-xs bg-gray"
v-if="!item.has_guess && item.game_at_comparison_now"
@click="showGuessModal(item)"
>
已结束
</button>
<button
class="btn text-white text-xs bg-gray"
@click="showGuessModal(item)"
v-if="item.has_guess"
>
已竞猜
</button>
<view
class="text-xs text-center"
:class="item.has_guess_right ? 'text-red' : 'text-green'"
v-if="item.state == 2 && item.has_guess"
>
<text>{{ item.has_guess_right ? '+' : '' }}</text
><text>{{ item.mark }}</text>
</view>
</view>
</view>
</view>
</view>
<view v-if="tabIndex === 1">
<view class="guess-rank-group">
<view class="guess-rank-info">
<view class="guess-rank-me">
<view class="label">当前排行</view>
<view class="value">{{ gameRankNumber || '无排名' }}</view>
</view>
<view class="guess-rank-btns">
<up-button
class="more-btn"
:customStyle="{
margin: 0,
width: '75px',
height: '28px',
backgroundColor: '#1D2B5C',
color: '#fff',
}"
shape="circle"
text="去领奖"
@click="$goToPage('packages/pages/award/award')"
v-if="detail.has_gift"
></up-button>
<up-button
class="more-btn"
:customStyle="{
margin: 0,
width: '75px',
height: '28px',
backgroundColor: '#1D2B5C',
color: '#fff',
}"
shape="circle"
text="去分享"
openType="share"
></up-button>
</view>
<view class="divider"></view>
<view class="guess-rank-list">
<view class="guess-rank-item">
<view class="rank-num rank-img"></view>
<view class="rank-user"> </view>
<view class="rank-text">次数</view>
<view class="rank-score">成绩</view>
</view>
</view>
<view class="empty-rank" v-if="!gameRankList.length">
快去竞猜,争与排名赢大礼
</view>
<view
@click="handleGameRank(item)"
class="guess-rank-list"
v-for="(item, index) in gameRankList"
:key="index"
>
<view class="guess-rank-item">
<view class="rank-num rank-img">
<image
class="rank-img"
mode="aspectFit"
:src="`${
'/static/images/honor-' + (index + 1) + '.png'
}`"
v-if="index + 1 <= 3"
>
</image>
<view v-else>{{ index + 1 }}</view>
</view>
<view class="rank-user">
<image
class="rank-user-img"
mode="aspectFit"
:src="`${
item.avatar || '/static/images/example-avatar1.png'
}`"
></image>
<view class="rank-user-nickname">{{
item.nick_name
}}</view>
</view>
<view class="rank-text"
>{{ item.join_times }}中{{ item.right_times }}</view
>
<view class="rank-score">{{ item.mark }}</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="tabIndex === 2">
<Item
v-for="(item, index) in activeLogs"
:key="index"
:data="item"
></Item>
<mescroll-empty v-if="!activeLogs.length"></mescroll-empty>
</view>
</view>
</view>
<up-modal
:show="showRuleModalVisible"
:width="335"
:closeOnClickOverlay="true"
:showConfirmButton="false"
:showCancelButton="false"
@close="hideRuleModal"
>
<view class="slot-content-rule">
<view class="title">活动规则</view>
<view class="content">{{ ruleContent }}</view>
</view>
</up-modal>
</mescroll-body>
<QuizModal
:gameInfo="gameInfo"
@hideGuessModal="hideGuessModal"
@submitGuessResult="submitGuessResult"
@userGuessChange="userGuessChange"
v-model:show="showGuessModalVisible"
></QuizModal>
</view>
</template>
<script setup>
import QuizModal from '@/components/quiz/modal.vue'
import { getCurrentInstance, ref } from 'vue'
import {
queryActivitiesDetail,
queryGamesList,
queryRankList,
queryLatestActivitiesDetail,
activityGameLogs,
} from '@/api/xinjiang_guess'
import dayjs from 'dayjs'
import config from '@/common/config'
import { submitGuess } from '@/api/xinjiang_guess'
import useAuthUser from '@/utils/hooks/useAuthUser'
import Item from '@/components/quiz/item.vue'
const vm = getCurrentInstance()
const isDetailPage = ref(true)
const pages = getCurrentPages()
const page = pages[pages.length - 1]
if (page.route.includes('packages')) {
isDetailPage.value = true
} else {
isDetailPage.value = false
}
function useGameDetail() {
const detailLoading = ref(false)
const detail = ref({})
async function getActivitiesDetail(params) {
detailLoading.value = true
queryActivitiesDetail(params)
.then((res) => {
detail.value = res.data
detail.value.start_at = dayjs(detail.value.start_at).format(
'YYYY-MM-DD'
)
detail.value.end_at = dayjs(detail.value.end_at).format('YYYY-MM-DD')
detailLoading.false = false
})
.catch((err) => {
console.error(err)
detailLoading.value = false
})
}
function getLatestActivitiesDetail(params) {
return new Promise((resolve, reject) => {
detailLoading.value = true
queryLatestActivitiesDetail(params)
.then((res) => {
detail.value = res.data.activity
detail.value.start_at = dayjs(detail.value.start_at).format(
'YYYY-MM-DD'
)
detail.value.end_at = dayjs(detail.value.end_at).format('YYYY-MM-DD')
detailLoading.false = false
detailId.value = res.data.activity.id
resolve(detail)
})
.catch((err) => {
console.error(err)
detailLoading.value = false
reject(err)
})
})
}
return {
detailLoading,
detail,
getActivitiesDetail,
getLatestActivitiesDetail,
}
}
function useGame() {
const gameListLoading = ref(false)
const gameList = ref([])
function gameGroupWithDay(data) {
let result = {}
data.forEach((item) => {
result[item.game_day] = []
})
data.forEach((item) => {
const isExpired = dayjs(item.game_at).isBefore(dayjs())
item.is_expired = isExpired
result[item.game_day].push(item)
})
return result
}
function getGameList(params) {
gameListLoading.value = true
queryGamesList(params)
.then((res) => {
gameList.value = gameGroupWithDay(res.data.games)
gameListLoading.value = false
})
.catch((err) => {
console.error(err)
gameListLoading.value = false
})
}
return {
gameListLoading,
gameList,
gameListLength: Object.values(gameList).length,
getGameList,
}
}
function useGameRank() {
const gameRankListLoading = ref(false)
const gameRankList = ref([])
const gameRankNumber = ref(0)
function getGameRankList(params) {
gameRankListLoading.value = true
queryRankList(params)
.then((res) => {
gameRankList.value = res.data.rank_list
gameRankNumber.value = res.data.rank_number
gameRankListLoading.value = false
})
.catch((err) => {
console.error(err)
gameRankListLoading.value = false
})
}
return {
gameRankListLoading,
gameRankList,
gameRankNumber,
getGameRankList,
}
}
function useActiveLog() {
const activeLogsLoading = ref(false)
const activeLogs = ref([])
function getActiveLogs(params) {
activeLogsLoading.value = true
activityGameLogs(params)
.then((res) => {
activeLogs.value = res.data.list
activeLogsLoading.value = false
})
.catch((err) => {
console.error(err)
activeLogsLoading.value = false
})
}
return {
activeLogsLoading,
activeLogs,
getActiveLogs,
}
}
function useGuessRuleModal() {
const showRuleModalVisible = ref(false)
const ruleContent = ref('')
function showRuleModal(rules) {
ruleContent.value = rules
showRuleModalVisible.value = true
}
function hideRuleModal() {
showRuleModalVisible.value = false
}
return {
showRuleModalVisible,
ruleContent,
showRuleModal,
hideRuleModal,
}
}
const {
detailLoading,
detail,
getActivitiesDetail,
getLatestActivitiesDetail,
} = useGameDetail()
const { gameListLoading, gameList, gameListLength, getGameList } = useGame()
const { gameRankListLoading, gameRankList, gameRankNumber, getGameRankList } =
useGameRank()
const { activeLogsLoading, activeLogs, getActiveLogs } = useActiveLog()
const { showRuleModalVisible, ruleContent, showRuleModal, hideRuleModal } =
useGuessRuleModal()
const tabIndex = ref(0)
function changeTabs(tab) {
gameList.value = []
gameRankList.value = []
tabIndex.value = tab.index
getMescroll().resetUpScroll()
}
const gameInfo = ref(null)
const readonly = ref(false)
const showGuessModalVisible = ref(false)
const userGuess = ref('0:0')
const gameScore = ref('')
const submitGuessLoading = ref(false)
function userGuessChange(score) {
if (readonly.value) {
return
}
userGuess.value = score
}
function showGuessModal(item) {
const { user } = useAuthUser()
if (!user || user.is_need_bind_phone) {
vm.proxy.$goToPage(
'packages/pages/login/login?previous=activeDetail&url=/packages/pages/activeDetail/activeDetail&id=' +
detailId.value
)
return
}
console.log('gameInfo', item)
gameInfo.value = item
gameScore.value = item.game_score
readonly.value = false
if (gameInfo.value.has_guess) {
readonly.value = true
}
if (gameInfo.value.game_at_comparison_now) {
readonly.value = true
}
if (readonly.value) {
userGuess.value = item.has_guess
} else {
userGuess.value = '0:0'
}
showGuessModalVisible.value = true
}
function hideGuessModal() {
showGuessModalVisible.value = false
setTimeout(() => {
userGuess.value = '0:0'
}, 300)
}
async function submitGuessResult() {
if(e){
userGuess.value = e
}
try {
uni.showLoading({
title: '加载中...',
})
submitGuessLoading.value = true
const params = {
game: gameInfo.value.id,
score: userGuess.value,
}
console.log('submitGuessResult', params)
const res = await submitGuess(params, { custom: { catch: true } })
getMescroll().resetUpScroll()
uni.showToast({
title: res.message,
icon: 'none',
})
setTimeout(hideGuessModal, 0)
} catch (err) {
console.error(err)
} finally {
submitGuessLoading.value = false
}
}
const detailId = ref(0)
if (isDetailPage.value) {
tabIndex.value = 0
} else {
tabIndex.value = 1
}
function getData(page = 1, per_page = 10) {
return new Promise(async (resolve, reject) => {
if (isDetailPage.value) {
getActivitiesDetail({ id: detailId.value })
switch (parseInt(tabIndex.value)) {
default:
getGameList({
activity_id: detailId.value,
page: 1,
per_page: 10,
})
resolve()
break
case 0:
getGameList({
activity_id: detailId.value,
page: 1,
per_page: 10,
})
resolve()
break
case 1:
getGameRankList({
activity_id: detailId.value,
page: 1,
per_page: 10,
})
resolve()
break
case 2:
getActiveLogs({
activity_id: detailId.value,
page: page,
per_page: per_page,
})
resolve()
break
}
} else {
getLatestActivitiesDetail({ page, per_page }).then(
(leatestActiviteDetail) => {
getGameRankList({
activity_id: leatestActiviteDetail.value.id,
page,
per_page,
})
resolve({
list: gameRankList.value,
})
}
)
}
})
}
import {
onShow,
onLoad,
onPullDownRefresh,
onPageScroll,
onReachBottom,
onShareAppMessage,
} from '@dcloudio/uni-app'
import useMescroll from '@/uni_modules/mescroll-uni/hooks/useMescroll.js'
const { mescrollInit, downCallback, getMescroll } = useMescroll(
onPageScroll,
onReachBottom
)
const list = ref([])
const upCallback = (mescroll) => {
typeof getData === 'function' &&
getData(mescroll.num, mescroll.size)
.then((data) => {
let curPageData = [] // 当前页数据
if (Array.isArray(data)) {
curPageData = data
}
if (mescroll.num == 1) list.value = [] // 第一页需手动制空列表
list.value = list.value.concat(curPageData) //追加新数据
console.log(data)
//联网成功的回调,隐藏下拉刷新和上拉加载的状态;
//mescroll会根据传的参数,自动判断列表如果无任何数据,则提示空;列表无下一页数据,则提示无更多数据;
//方法一(推荐): 后台接口有返回列表的总页数 totalPage
//mescroll.endByPage(curPageData.length, totalPage); //必传参数(当前页的数据个数, 总页数)
//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
//mescroll.endBySize(curPageData.length, totalSize); //必传参数(当前页的数据个数, 总数据量)
//方法三(推荐): 您有其他方式知道是否有下一页 hasNext
//mescroll.endSuccess(curPageData.length, hasNext); //必传参数(当前页的数据个数, 是否有下一页true/false)
//方法四 (不推荐),会存在一个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据当前页的数据个数判断,则需翻到第三页才会知道无更多数据.
mescroll.endSuccess(curPageData.length) // 请求成功, 结束加载
})
.catch(() => {
mescroll.endErr() // 请求失败, 结束加载
})
}
onLoad((query) => {
detailId.value = query.id
if (query.tab) {
tabIndex.value = query.tab
}
const vm = getCurrentInstance()
setTimeout(() => {
vm.proxy.mpShare.title = detail.value.share_title || ''
if (isDetailPage.value) {
vm.proxy.mpShare.path = `/packages/pages/activeDetail/activeDetail?id=${detail.value.id}`
console.log('mpSharePath detail', vm.proxy.mpShare.path)
} else {
vm.proxy.mpShare.path = `/pages/charts/charts`
console.log('mpSharePath charts', vm.proxy.mpShare.path)
}
vm.proxy.mpShare.imageUrl = detail.value.share_image || ''
}, 1500)
})
const handleGameRank = (e) => {
vm.proxy.$goToPage({
url: 'pages/quiz/logs',
params: { activity_id: detailId.value, user_id: e.user_id },
})
}
</script>
<style lang="scss" scoped>
.page-activate-detail {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
.activate-cover {
width: 100%;
height: 188px;
flex-shrink: 0;
}
.activate-box {
position: relative;
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
width: calc(100vw - 24px);
min-height: 100vh;
background-color: #1b2f85;
color: #fff;
.bg {
position: absolute;
top: 0;
left: 0;
width: 100vw;
}
.activate-info {
font-size: 16px;
font-weight: bold;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 999;
}
.activate-title-box {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 24px;
}
.activate-title {
font-size: 16px;
}
.activate-rule {
font-size: 12px;
margin-top: 4px;
padding: 4px 8px;
flex-shrink: 0;
}
.activate-rule {
font-size: 12px;
}
.time {
display: flex;
gap: 2px;
font-size: 12px;
color: #fff;
}
}
.slot-content-rule {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
.title {
font-size: 16px;
font-weight: 500;
}
.content {
font-size: 12px;
font-weight: 400;
white-space: pre-wrap;
}
}
}
.u-modal {
.u-modal__content {
padding: 12px !important;
}
}
.guess-model {
.slot-content {
display: flex;
flex-direction: column;
gap: 12px;
}
.guess-title {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
gap: 58px;
}
.guess-table {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 12px;
.table-td {
border: 1px solid #bbb;
width: 48px !important;
}
.table-td.result-active {
background-color: red;
color: #fff;
}
.table-td.active {
background-color: #1d2b5c;
color: #fff;
}
.row-1 {
height: calc(30px * 1 - 2px) !important;
line-height: calc(30px * 1 - 2px) !important;
}
.row-2 {
height: calc(30px * 2 - 2px) !important;
line-height: calc(30px * 2 - 2px) !important;
}
.row-3 {
height: calc(30px * 3 - 8px) !important;
line-height: calc(30px * 3 - 8px) !important;
}
.row {
width: 300px;
height: 30px;
line-height: 30px;
flex-shrink: 0;
display: flex;
text-align: center;
.col-1 {
width: calc(292px / 12 * 1);
}
.col-2 {
width: calc(292px / 12 * 2);
}
.col-10 {
display: flex;
justify-content: flex-start;
align-self: flex-start;
flex-grow: 1;
.table-td {
width: 47.7px !important;
border: 1px solid #bbb;
}
.col-6 {
width: calc(47.7px * 3 + 4px) !important;
}
}
.col-12 {
width: 292px;
}
}
.herder-title {
background-color: #1d2b5c;
color: #fff;
.table-td {
width: 100% !important;
}
}
.row-title {
background-color: #d9d9d9;
width: 48px !important;
justify-content: center !important;
}
.row-content {
display: flex;
flex-wrap: wrap;
width: calc(300px - 48px - 3px) !important;
}
}
.guess-btns {
display: flex;
justify-content: space-around;
width: 300px;
flex-shrink: 0;
}
}
.activate-data-list {
display: flex;
flex-direction: column;
gap: 4px;
border-radius: 12px;
background: #fff;
padding-bottom: 16px;
min-height: 300px;
z-index: 999;
}
.guess-date-group {
color: #000;
.date {
height: 20px;
line-height: 20px;
background: #1d2b5c;
color: #fff;
padding: 2px 8px;
}
.table-cell {
padding: 12px 20px 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
.activate-name {
width: 150px;
text-align: left;
}
.guess-vs {
display: flex;
gap: 12px;
align-items: center;
}
.imgbar {
min-width: 44px;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.name {
width: 44px;
}
}
.small-img {
width: 32px;
height: 32px;
line-height: 32px;
border-radius: 50%;
margin-bottom: 12px;
}
.result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
line-height: 1;
margin-top: 10px;
gap: 4px;
}
.btn {
width: 48px;
height: 22px;
line-height: 22px;
text-align: center;
border-radius: 10px;
padding: 0 !important;
}
.bg-blue {
background-color: #1d2b5c;
}
.bg-gray {
background-color: #605e5e;
margin-bottom: 6px;
}
.text-green {
color: #008000;
}
}
}
.guess-rank-group {
padding: 12px 34px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 12px;
color: #000;
.guess-rank-info {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
}
.guess-rank-me {
display: flex;
justify-content: space-between;
align-items: center;
.label {
font-size: 16px;
font-weight: 500;
}
.value {
font-size: 24px;
font-weight: 500;
color: red;
width: 75px;
text-align: center;
}
}
.guess-rank-btns {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 4px;
}
.divider {
width: 100%;
height: 1px;
background-color: #999;
}
.empty-rank {
margin: 0 auto;
margin-top: 20px;
}
.guess-rank-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
.guess-rank-item {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
height: 32px;
gap: 12px;
.rank-num,
.rank-img {
width: 24px;
height: 24px;
vertical-align: middle;
text-align: center;
flex-shrink: 0;
}
.rank-user {
display: flex;
gap: 8px;
align-items: center;
text-align: left;
width: 150px;
.rank-user-img {
width: 32px;
height: 32px;
flex-shrink: 0;
}
.rank-user-nickname {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
}
.rank-text,
.rank-score {
width: 75px;
text-align: right;
}
}
}
}
</style>