Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75cfcd66ee | ||
|
|
7955b37ec5 | ||
|
|
9e6ad4cf50 | ||
|
|
6c55d32f90 |
@@ -131,9 +131,8 @@ const goCalibration = async () => {
|
|||||||
<template>
|
<template>
|
||||||
<view :style="{ paddingTop: capsuleHeight + 'px' }">
|
<view :style="{ paddingTop: capsuleHeight + 'px' }">
|
||||||
<AppBackground :type="bgType" :bgColor="bgColor" />
|
<AppBackground :type="bgType" :bgColor="bgColor" />
|
||||||
<slot v-if="$slots.header" name="header"></slot>
|
|
||||||
<Header
|
<Header
|
||||||
v-else-if="!isHome"
|
v-if="!isHome"
|
||||||
:class="headerClass"
|
:class="headerClass"
|
||||||
:title="title"
|
:title="title"
|
||||||
:onBack="onBack"
|
:onBack="onBack"
|
||||||
@@ -160,7 +159,7 @@ const goCalibration = async () => {
|
|||||||
:bounces="false"
|
:bounces="false"
|
||||||
:show-scrollbar="false"
|
:show-scrollbar="false"
|
||||||
:style="{
|
:style="{
|
||||||
height: `calc(100vh - ${capsuleHeight + (($slots.header || !isHome) ? 50 : 0)}px - ${
|
height: `calc(100vh - ${capsuleHeight + (isHome ? 0 : 50)}px - ${
|
||||||
$slots.bottom && showBottom ? (isIOS ? '75px' : '65px') : '0px'
|
$slots.bottom && showBottom ? (isIOS ? '75px' : '65px') : '0px'
|
||||||
})`,
|
})`,
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -125,8 +125,6 @@ onBeforeUnmount(() => {
|
|||||||
<view
|
<view
|
||||||
:style="[{ color: whiteBackArrow ? '#fff' : '#000' }, titleStyle]"
|
:style="[{ color: whiteBackArrow ? '#fff' : '#000' }, titleStyle]"
|
||||||
>
|
>
|
||||||
<slot v-if="$slots.title" name="title"></slot>
|
|
||||||
<template v-else>
|
|
||||||
<view
|
<view
|
||||||
v-if="currentPage === 'pages/point-book'"
|
v-if="currentPage === 'pages/point-book'"
|
||||||
class="user-header"
|
class="user-header"
|
||||||
@@ -192,7 +190,6 @@ onBeforeUnmount(() => {
|
|||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
</block>
|
</block>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
<view v-if="pointBook" class="point-book-info">
|
<view v-if="pointBook" class="point-book-info">
|
||||||
<text>{{ pointBook.bowType.name }}</text>
|
<text>{{ pointBook.bowType.name }}</text>
|
||||||
|
|||||||
@@ -133,29 +133,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subPackages": [
|
"subPackages": [
|
||||||
{
|
|
||||||
"root": "pages/coin",
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "rules"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "earning-records"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "exchange-records"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "nearby-stores"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "product-detail"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"root": "pages/device",
|
"root": "pages/device",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
cumulative: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
available: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="balance-panel">
|
|
||||||
<view class="balance-list">
|
|
||||||
<view class="balance-item">
|
|
||||||
<text class="balance-item__label">累计金币:</text>
|
|
||||||
<text class="balance-item__value">{{ cumulative }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="balance-list__line" />
|
|
||||||
<view class="balance-item">
|
|
||||||
<text class="balance-item__label">可兑换金币:</text>
|
|
||||||
<text class="balance-item__value">{{ available }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.balance-panel {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8rpx 28rpx 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-list {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 706rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
margin-top: 0;
|
|
||||||
border: 2rpx solid rgba(255, 217, 71, 0.25);
|
|
||||||
border-radius: 12rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: rgba(255, 217, 71, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-item {
|
|
||||||
width: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-item__label {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-item__value {
|
|
||||||
color: #ffd947;
|
|
||||||
font-size: 30rpx;
|
|
||||||
line-height: 42rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-list__line {
|
|
||||||
width: 2rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
|
||||||
margin: 0 22rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
text: {
|
|
||||||
type: String,
|
|
||||||
default: "暂无金币获取记录。",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="empty-state">
|
|
||||||
<image src="/static/coin/empty-coin-record.png" mode="aspectFit" />
|
|
||||||
<text>{{ text }}</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.empty-state {
|
|
||||||
width: 100%;
|
|
||||||
padding-top: 280rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state > image {
|
|
||||||
width: 162rpx;
|
|
||||||
height: 190rpx;
|
|
||||||
margin-bottom: 26rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import Header from "@/components/Header.vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
onBack: {
|
|
||||||
type: Function,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="coin-header">
|
|
||||||
<Header title="" :onBack="onBack">
|
|
||||||
<template #title>
|
|
||||||
<view class="coin-header__title-group">
|
|
||||||
<text
|
|
||||||
:class="[
|
|
||||||
'coin-header__title',
|
|
||||||
subtitle ? 'coin-header__title--with-subtitle' : '',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ title }}
|
|
||||||
</text>
|
|
||||||
<text v-if="subtitle" class="coin-header__subtitle">
|
|
||||||
{{ subtitle }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</Header>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.coin-header {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 20;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coin-header__title-group {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 430rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coin-header__title {
|
|
||||||
color: #e7ba80;
|
|
||||||
font-size: 30rpx;
|
|
||||||
line-height: 42rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coin-header__title--with-subtitle {
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coin-header__subtitle {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 20rpx;
|
|
||||||
line-height: 28rpx;
|
|
||||||
font-weight: 400;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
menus: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["select"]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="quick-menu">
|
|
||||||
<view
|
|
||||||
v-for="item in menus"
|
|
||||||
:key="item.key"
|
|
||||||
class="quick-menu__item"
|
|
||||||
@click="emit('select', item)"
|
|
||||||
>
|
|
||||||
<image class="quick-menu__icon" :src="item.icon" mode="aspectFit" />
|
|
||||||
<text>{{ item.label }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.quick-menu {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
padding: 26rpx 46rpx 30rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-menu__item {
|
|
||||||
width: 142rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
color: #fae6bc;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-menu__icon {
|
|
||||||
width: 116rpx;
|
|
||||||
height: 116rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
records: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: "earning",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view
|
|
||||||
class="record-table"
|
|
||||||
:class="{ 'record-table--exchange': mode === 'exchange' }"
|
|
||||||
>
|
|
||||||
<view class="record-table__header">
|
|
||||||
<template v-if="mode === 'exchange'">
|
|
||||||
<text>兑换内容</text>
|
|
||||||
<text>兑换类型</text>
|
|
||||||
<text>时间</text>
|
|
||||||
<text>金币使用</text>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<text>时间</text>
|
|
||||||
<text>类型</text>
|
|
||||||
<text>金币获取</text>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
v-for="item in records"
|
|
||||||
:key="item.id"
|
|
||||||
class="record-table__row"
|
|
||||||
>
|
|
||||||
<template v-if="mode === 'exchange'">
|
|
||||||
<text class="record-table__exchange-content">{{ item.content }}</text>
|
|
||||||
<text class="record-table__exchange-type">{{ item.type }}</text>
|
|
||||||
<view class="record-table__time record-table__exchange-time">
|
|
||||||
<text>{{ item.date }}</text>
|
|
||||||
<text>{{ item.time }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="record-table__amount record-table__exchange-amount">
|
|
||||||
{{ item.amount }}
|
|
||||||
</text>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<view class="record-table__time">
|
|
||||||
<text>{{ item.date }}</text>
|
|
||||||
<text>{{ item.time }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="record-table__type">{{ item.type }}</text>
|
|
||||||
<text class="record-table__amount">{{ item.amount }}</text>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.record-table {
|
|
||||||
width: 670rpx;
|
|
||||||
margin: 20rpx auto 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 2rpx solid rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__header,
|
|
||||||
.record-table__row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__header {
|
|
||||||
height: 66rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__header > text {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-right: 2rpx solid rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__header > text:nth-child(1),
|
|
||||||
.record-table__time {
|
|
||||||
width: 182rpx;
|
|
||||||
flex: 0 0 182rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__header > text:nth-child(2),
|
|
||||||
.record-table__type {
|
|
||||||
width: 304rpx;
|
|
||||||
flex: 0 0 304rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__header > text:nth-child(3),
|
|
||||||
.record-table__amount {
|
|
||||||
width: 180rpx;
|
|
||||||
flex: 0 0 180rpx;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__row {
|
|
||||||
height: 86rpx;
|
|
||||||
border-top: 2rpx solid rgba(255, 255, 255, 0.35);
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__row > view,
|
|
||||||
.record-table__row > text {
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-right: 2rpx solid rgba(255, 255, 255, 0.35);
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__time {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__time > text:last-child {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__type {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__row > .record-table__amount {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #ffffff;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table__exchange-content,
|
|
||||||
.record-table__exchange-type {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 8rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table--exchange .record-table__header > text:nth-child(1),
|
|
||||||
.record-table__exchange-content {
|
|
||||||
width: 286rpx;
|
|
||||||
flex: 0 0 286rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table--exchange .record-table__header > text:nth-child(2),
|
|
||||||
.record-table__exchange-type {
|
|
||||||
width: 110rpx;
|
|
||||||
flex: 0 0 110rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table--exchange .record-table__header > text:nth-child(3),
|
|
||||||
.record-table__exchange-time {
|
|
||||||
width: 160rpx;
|
|
||||||
flex: 0 0 160rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-table--exchange .record-table__header > text:nth-child(4),
|
|
||||||
.record-table__exchange-amount {
|
|
||||||
width: 110rpx;
|
|
||||||
flex: 0 0 110rpx;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
const emit = defineEmits(["authorize"]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="location-state">
|
|
||||||
<image src="/static/coin/location-permission.png" mode="aspectFit" />
|
|
||||||
<text>授权获取你的定位,以便推荐附近门店。</text>
|
|
||||||
<button hover-class="none" @click="emit('authorize')">立即授权</button>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.location-state {
|
|
||||||
width: 100%;
|
|
||||||
padding-top: 190rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 32rpx;
|
|
||||||
line-height: 44rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-state > image {
|
|
||||||
width: 168rpx;
|
|
||||||
height: 190rpx;
|
|
||||||
margin-bottom: 52rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-state > button {
|
|
||||||
width: 360rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
margin-top: 44rpx;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 36rpx;
|
|
||||||
background-color: #ffd947;
|
|
||||||
color: #22222e;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 72rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-state > button::after {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
variant: {
|
|
||||||
type: String,
|
|
||||||
default: "gold",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="exchange-notice" :class="`exchange-notice--${variant}`">
|
|
||||||
<image src="/static/coin/icon-notice.png" mode="aspectFit" />
|
|
||||||
<text>暂不支持线上兑换,请前往线下门店进行兑换。</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.exchange-notice {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 504rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 16rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: rgba(255, 255, 255, 0.65);
|
|
||||||
font-size: 20rpx;
|
|
||||||
line-height: 28rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exchange-notice--gold {
|
|
||||||
border: 2rpx solid rgba(255, 217, 71, 0.28);
|
|
||||||
background-color: rgba(255, 217, 71, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.exchange-notice--red {
|
|
||||||
width: 492rpx;
|
|
||||||
height: 44rpx;
|
|
||||||
margin: 0 0 0 40rpx;
|
|
||||||
border: none;
|
|
||||||
background-color: rgba(255, 96, 96, 0.3);
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.exchange-notice > image {
|
|
||||||
width: 24rpx;
|
|
||||||
height: 24rpx;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from "vue";
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
backgrounds: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
productImage: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const current = ref(0);
|
|
||||||
|
|
||||||
const onChange = (event) => {
|
|
||||||
current.value = event.detail.current;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="hero-swiper">
|
|
||||||
<swiper class="hero-swiper__body" :duration="260" @change="onChange">
|
|
||||||
<swiper-item v-for="(background, index) in backgrounds" :key="index">
|
|
||||||
<view class="hero-swiper__item">
|
|
||||||
<image class="hero-swiper__background" :src="background" mode="scaleToFill" />
|
|
||||||
<image class="hero-swiper__product" :src="productImage" mode="aspectFit" />
|
|
||||||
</view>
|
|
||||||
</swiper-item>
|
|
||||||
</swiper>
|
|
||||||
<view class="hero-swiper__dots">
|
|
||||||
<view
|
|
||||||
v-for="(_, index) in backgrounds"
|
|
||||||
:key="index"
|
|
||||||
class="hero-swiper__dot"
|
|
||||||
:class="{ 'hero-swiper__dot--active': current === index }"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.hero-swiper,
|
|
||||||
.hero-swiper__body,
|
|
||||||
.hero-swiper__item {
|
|
||||||
position: relative;
|
|
||||||
width: 750rpx;
|
|
||||||
height: 750rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-swiper__background,
|
|
||||||
.hero-swiper__product {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-swiper__dots {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 20rpx;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-swiper__dot {
|
|
||||||
width: 12rpx;
|
|
||||||
height: 12rpx;
|
|
||||||
margin: 0 6rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: rgba(34, 34, 46, 0.55);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-swiper__dot--active {
|
|
||||||
background-color: #ffd947;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
const DEFAULT_PRODUCT_IMAGE = "/static/coin/product-hero-bg.png";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
product: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const productImage = computed(() => {
|
|
||||||
const image = props.product?.image;
|
|
||||||
return typeof image === "string" && image.trim()
|
|
||||||
? image
|
|
||||||
: DEFAULT_PRODUCT_IMAGE;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="product-card">
|
|
||||||
<view class="product-card__image-wrap">
|
|
||||||
<image class="product-card__image" :src="productImage" mode="aspectFit" />
|
|
||||||
</view>
|
|
||||||
<text class="product-card__name">{{ product.name }}</text>
|
|
||||||
<view class="product-card__footer">
|
|
||||||
<text>{{ product.cost }}金币</text>
|
|
||||||
<text class="product-card__divider">|</text>
|
|
||||||
<text>剩余{{ product.stock }}个</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.product-card {
|
|
||||||
width: 336rpx;
|
|
||||||
height: 418rpx;
|
|
||||||
padding: 10rpx 10rpx 14rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 2rpx solid rgba(255, 217, 71, 0.1);
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: rgba(84, 67, 29, 0.2);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__image-wrap {
|
|
||||||
position: relative;
|
|
||||||
width: 312rpx;
|
|
||||||
height: 312rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__image {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__name {
|
|
||||||
display: block;
|
|
||||||
margin-top: 12rpx;
|
|
||||||
color: #fff0c9;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 4rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
font-size: 22rpx;
|
|
||||||
line-height: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__footer > text:first-child {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__footer > text:last-child {
|
|
||||||
color: rgba(255, 255, 255, 0.78);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card__divider {
|
|
||||||
margin: 0 10rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.45);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
defineProps({
|
|
||||||
store: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="store-card">
|
|
||||||
<image class="store-card__photo" :src="store.image" mode="aspectFill" />
|
|
||||||
<text class="store-card__name">{{ store.name }}</text>
|
|
||||||
<view class="store-card__info">
|
|
||||||
<view class="store-card__row">
|
|
||||||
<text>地址:{{ store.address }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="store-card__row">
|
|
||||||
<text>电话:{{ store.phone }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="store-card__row">
|
|
||||||
<text>营业时间:{{ store.hours }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.store-card {
|
|
||||||
width: 650rpx;
|
|
||||||
margin: 22rpx auto 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-card__photo {
|
|
||||||
width: 650rpx;
|
|
||||||
height: 324rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-card__name {
|
|
||||||
display: block;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
margin-left: 10rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 32rpx;
|
|
||||||
line-height: 44rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-card__info {
|
|
||||||
width: 584rpx;
|
|
||||||
margin-top: 12rpx;
|
|
||||||
margin-left: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-card__row {
|
|
||||||
margin-top: 0;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 40rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
export const coinSummary = {
|
|
||||||
storeName: "广州大学城北旗舰体验店",
|
|
||||||
cumulative: 9999,
|
|
||||||
available: 8800,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const quickMenus = [
|
|
||||||
{
|
|
||||||
key: "rules",
|
|
||||||
label: "金币规则",
|
|
||||||
icon: "/static/coin/icon-rules.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "earningRecords",
|
|
||||||
label: "获取明细",
|
|
||||||
icon: "/static/coin/icon-earning-records.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "exchangeRecords",
|
|
||||||
label: "兑换记录",
|
|
||||||
icon: "/static/coin/icon-exchange-records.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "nearbyStores",
|
|
||||||
label: "附近门店",
|
|
||||||
icon: "/static/coin/icon-nearby-store.png",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const rewardProducts = Array.from({ length: 6 }, (_, index) => ({
|
|
||||||
id: index + 1,
|
|
||||||
name: "射灵公仔",
|
|
||||||
cost: 3000,
|
|
||||||
stock: 100,
|
|
||||||
background: "/static/coin/product-card-bg.png",
|
|
||||||
image: "/static/coin/product-card-item.png",
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const coinRules = [
|
|
||||||
"金币获取规则:登录个人账号后,使用射灵智能弓体验个人训练、好友约战、排位赛三种模式,每射出一箭并判为有效,即可根据该箭的实际环数,获得等额金币。如:用户在个人训练中,射出一箭并获得9环,即获得9金币。",
|
|
||||||
"累计金币:累计金币为用户历史获得的所有金币,不会过期、不受兑换影响。",
|
|
||||||
"可兑换金币:可兑换金币为用户可用于兑换奖品的金币,用户使用金币兑换相应礼品后,将扣除相应金币。同时,可兑换金币有1年有效期,自金币获得之日起开始计算。",
|
|
||||||
"射灵星球官方可能会不定期举办各类活动,有机会额外获得更多机会,具体以实际公告为准。",
|
|
||||||
"金币以每个门店/俱乐部为单位独立计算与兑换核销,暂不支持跨门店、跨俱乐部计算,具体以各门店实际公告为准。",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const earningRecords = Array.from({ length: 5 }, (_, index) => ({
|
|
||||||
id: index + 1,
|
|
||||||
date: "2026-07-12",
|
|
||||||
time: "13:23:43",
|
|
||||||
type: "个人训练-射箭8环",
|
|
||||||
amount: "+8",
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const exchangeRecords = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
content: "苹果Airpods Pro耳机一对",
|
|
||||||
type: "个人兑换",
|
|
||||||
date: "2026-07-12",
|
|
||||||
time: "13:23:43",
|
|
||||||
amount: "-2000",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
content: "金币过期",
|
|
||||||
type: "金币过期",
|
|
||||||
date: "2026-07-12",
|
|
||||||
time: "13:23:43",
|
|
||||||
amount: "-1500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
content: "苹果Airpods Pro耳机一对",
|
|
||||||
type: "队伍兑换",
|
|
||||||
date: "2026-07-12",
|
|
||||||
time: "13:23:43",
|
|
||||||
amount: "-1500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
content: "人工扣除",
|
|
||||||
type: "人工扣除",
|
|
||||||
date: "2026-07-12",
|
|
||||||
time: "13:23:43",
|
|
||||||
amount: "-1500",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const nearbyStore = {
|
|
||||||
name: "射灵星球智能弓体验馆-广州大学城北店",
|
|
||||||
address: "广州市番禺区大学城青蓝街1栋b4(广州大学城北)",
|
|
||||||
phone: "030-23392934",
|
|
||||||
hours: "10:00-20:00",
|
|
||||||
image: "/static/coin/store-photo.png",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const productDetail = {
|
|
||||||
name: "射灵星球定制毛巾两条",
|
|
||||||
cumulative: 9999,
|
|
||||||
stock: 10,
|
|
||||||
backgrounds: [
|
|
||||||
"/static/coin/product-hero-bg.png",
|
|
||||||
"/static/coin/product-hero-bg.png",
|
|
||||||
"/static/coin/product-hero-bg.png",
|
|
||||||
"/static/coin/product-hero-bg.png",
|
|
||||||
],
|
|
||||||
image: "/static/coin/product-hero-item.png",
|
|
||||||
descriptions: [
|
|
||||||
"本礼品具体型号为 苹果Airpods Pro二代无线降噪耳机(国行版)。颜色随机,不支持指定。",
|
|
||||||
"兑换请当场进行开箱验货,一经兑换,不支持无理由退换。",
|
|
||||||
"本次活动礼品为促销赠品,保证全新完好,具备正常使用功能。礼品非售卖商品,不支持单独退换;若收到礼品存在质量瑕疵,请收货7 日内凭完整包装联系客服核实处理,提供维修 / 更换方案。品牌赠品官方保修请参照品牌厂商政策。",
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from "vue";
|
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import CoinHeader from "./components/CoinHeader.vue";
|
|
||||||
import CoinRecordList from "./components/CoinRecordList.vue";
|
|
||||||
import CoinEmptyState from "./components/CoinEmptyState.vue";
|
|
||||||
import { earningRecords } from "./data";
|
|
||||||
|
|
||||||
const showEmpty = ref(false);
|
|
||||||
|
|
||||||
onLoad((options = {}) => {
|
|
||||||
// 预留空态入口,接口接入后由真实列表长度控制。
|
|
||||||
showEmpty.value = options.state === "empty";
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Container :bgType="6" :isHome="true">
|
|
||||||
<template #header>
|
|
||||||
<CoinHeader title="金币获取明细" />
|
|
||||||
</template>
|
|
||||||
<view class="records-page">
|
|
||||||
<CoinEmptyState v-if="showEmpty" />
|
|
||||||
<CoinRecordList v-else :records="earningRecords" />
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.records-page {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from "vue";
|
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import CoinHeader from "./components/CoinHeader.vue";
|
|
||||||
import CoinRecordList from "./components/CoinRecordList.vue";
|
|
||||||
import CoinEmptyState from "./components/CoinEmptyState.vue";
|
|
||||||
import { exchangeRecords } from "./data";
|
|
||||||
|
|
||||||
const showEmpty = ref(false);
|
|
||||||
|
|
||||||
onLoad((options = {}) => {
|
|
||||||
// 预留空态入口,接口接入后由真实列表长度控制。
|
|
||||||
showEmpty.value = options.state === "empty";
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Container :bgType="6" :isHome="true">
|
|
||||||
<template #header>
|
|
||||||
<CoinHeader title="金币兑换记录" />
|
|
||||||
</template>
|
|
||||||
<view class="records-page">
|
|
||||||
<CoinEmptyState v-if="showEmpty" text="暂无金币兑换记录。" />
|
|
||||||
<CoinRecordList v-else mode="exchange" :records="exchangeRecords" />
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.records-page {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import CoinHeader from "./components/CoinHeader.vue";
|
|
||||||
import CoinBalancePanel from "./components/CoinBalancePanel.vue";
|
|
||||||
import CoinQuickMenu from "./components/CoinQuickMenu.vue";
|
|
||||||
import OfflineExchangeNotice from "./components/OfflineExchangeNotice.vue";
|
|
||||||
import RewardProductCard from "./components/RewardProductCard.vue";
|
|
||||||
import { coinSummary, quickMenus, rewardProducts } from "./data";
|
|
||||||
|
|
||||||
const menuRoutes = {
|
|
||||||
rules: "/pages/coin/rules",
|
|
||||||
earningRecords: "/pages/coin/earning-records",
|
|
||||||
exchangeRecords: "/pages/coin/exchange-records",
|
|
||||||
nearbyStores: "/pages/coin/nearby-stores",
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMenuSelect = (menu) => {
|
|
||||||
const url = menuRoutes[menu.key];
|
|
||||||
if (!url) {
|
|
||||||
uni.showToast({
|
|
||||||
title: "功能开发中",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.navigateTo({ url });
|
|
||||||
};
|
|
||||||
|
|
||||||
const toProductDetail = () => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: "/pages/coin/product-detail",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Container :bgType="6" :isHome="true">
|
|
||||||
<template #header>
|
|
||||||
<CoinHeader title="我的金币" :subtitle="coinSummary.storeName" />
|
|
||||||
</template>
|
|
||||||
<view class="coin-page">
|
|
||||||
<CoinBalancePanel
|
|
||||||
:cumulative="coinSummary.cumulative"
|
|
||||||
:available="coinSummary.available"
|
|
||||||
/>
|
|
||||||
<CoinQuickMenu :menus="quickMenus" @select="onMenuSelect" />
|
|
||||||
<OfflineExchangeNotice />
|
|
||||||
<view class="reward-section">
|
|
||||||
<view class="reward-grid">
|
|
||||||
<view
|
|
||||||
v-for="product in rewardProducts"
|
|
||||||
:key="product.id"
|
|
||||||
class="reward-grid__item"
|
|
||||||
@click="toProductDetail"
|
|
||||||
>
|
|
||||||
<RewardProductCard :product="product" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.coin-page {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
padding-bottom: 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-section {
|
|
||||||
padding: 20rpx 28rpx 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-grid {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-grid__item {
|
|
||||||
width: 336rpx;
|
|
||||||
margin-right: 22rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reward-grid__item:nth-child(2n) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from "vue";
|
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import CoinHeader from "./components/CoinHeader.vue";
|
|
||||||
import LocationPermissionState from "./components/LocationPermissionState.vue";
|
|
||||||
import StoreCard from "./components/StoreCard.vue";
|
|
||||||
import { nearbyStore } from "./data";
|
|
||||||
|
|
||||||
const showPermissionState = ref(false);
|
|
||||||
|
|
||||||
onLoad((options = {}) => {
|
|
||||||
// 预留未授权态,接口接入后替换为微信定位授权结果。
|
|
||||||
showPermissionState.value = options.state === "empty";
|
|
||||||
});
|
|
||||||
|
|
||||||
const authorizeLocation = () => {
|
|
||||||
showPermissionState.value = false;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Container :bgType="6" :isHome="true">
|
|
||||||
<template #header>
|
|
||||||
<CoinHeader title="附近门店" />
|
|
||||||
</template>
|
|
||||||
<view class="stores-page">
|
|
||||||
<LocationPermissionState
|
|
||||||
v-if="showPermissionState"
|
|
||||||
@authorize="authorizeLocation"
|
|
||||||
/>
|
|
||||||
<template v-else>
|
|
||||||
<StoreCard :store="nearbyStore" />
|
|
||||||
<view class="store-placeholder">
|
|
||||||
<image
|
|
||||||
class="store-placeholder__background"
|
|
||||||
src="/static/coin/store-placeholder-bg.png"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<text class="stores-page__more">更多门店敬请期待~</text>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.stores-page {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
padding-bottom: 54rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-placeholder {
|
|
||||||
position: relative;
|
|
||||||
width: 650rpx;
|
|
||||||
height: 324rpx;
|
|
||||||
margin: 44rpx auto 0;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-placeholder__background {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stores-page__more {
|
|
||||||
display: block;
|
|
||||||
width: 650rpx;
|
|
||||||
margin: 26rpx auto 0;
|
|
||||||
color: rgba(255, 255, 255, 0.72);
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import CoinHeader from "./components/CoinHeader.vue";
|
|
||||||
import ProductHeroSwiper from "./components/ProductHeroSwiper.vue";
|
|
||||||
import OfflineExchangeNotice from "./components/OfflineExchangeNotice.vue";
|
|
||||||
import { productDetail } from "./data";
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Container :bgType="6" :isHome="true">
|
|
||||||
<template #header>
|
|
||||||
<CoinHeader title="商品详情" />
|
|
||||||
</template>
|
|
||||||
<view class="product-detail">
|
|
||||||
<ProductHeroSwiper
|
|
||||||
:backgrounds="productDetail.backgrounds"
|
|
||||||
:productImage="productDetail.image"
|
|
||||||
/>
|
|
||||||
<view class="product-detail__summary">
|
|
||||||
<text class="product-detail__name">{{ productDetail.name }}</text>
|
|
||||||
<view class="product-detail__balance">
|
|
||||||
<text>累计金币:</text>
|
|
||||||
<text class="product-detail__amount">{{ productDetail.cumulative }}</text>
|
|
||||||
<text>(剩余{{ productDetail.stock }}个)</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<OfflineExchangeNotice variant="red" />
|
|
||||||
<view class="product-detail__content">
|
|
||||||
<text
|
|
||||||
v-for="(description, index) in productDetail.descriptions"
|
|
||||||
:key="index"
|
|
||||||
class="product-detail__paragraph"
|
|
||||||
>
|
|
||||||
{{ description }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.product-detail {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
padding-bottom: 60rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #22222e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-detail__summary {
|
|
||||||
padding: 30rpx 40rpx 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-detail__name {
|
|
||||||
color: #ffd947;
|
|
||||||
font-size: 52rpx;
|
|
||||||
line-height: 74rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-detail__balance {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
margin-top: 10rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-detail__amount {
|
|
||||||
margin-right: 6rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 36rpx;
|
|
||||||
line-height: 50rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-detail__content {
|
|
||||||
padding: 38rpx 40rpx 60rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-bottom: 2rpx solid rgba(255, 217, 71, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-detail__paragraph {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 40rpx;
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import CoinHeader from "./components/CoinHeader.vue";
|
|
||||||
import { coinRules } from "./data";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Container :bgType="6" :isHome="true">
|
|
||||||
<template #header>
|
|
||||||
<CoinHeader title="金币规则" />
|
|
||||||
</template>
|
|
||||||
<view class="rules-page">
|
|
||||||
<view
|
|
||||||
v-for="(rule, index) in coinRules"
|
|
||||||
:key="index"
|
|
||||||
class="rules-page__item"
|
|
||||||
>
|
|
||||||
<text>{{ index + 1 }}、{{ rule }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.rules-page {
|
|
||||||
width: 100%;
|
|
||||||
padding: 28rpx 38rpx 60rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rules-page__item {
|
|
||||||
margin-bottom: 26rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.88);
|
|
||||||
font-size: 28rpx;
|
|
||||||
line-height: 52rpx;
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, onUnmounted, ref} from "vue";
|
import {onMounted, onUnmounted, ref, watch} from "vue";
|
||||||
import {onShareAppMessage, onShareTimeline, onShow} from "@dcloudio/uni-app";
|
import {onShareAppMessage, onShareTimeline, onShow} from "@dcloudio/uni-app";
|
||||||
import Container from "@/components/Container.vue";
|
import Container from "@/components/Container.vue";
|
||||||
import AppFooter from "@/components/AppFooter.vue";
|
import AppFooter from "@/components/AppFooter.vue";
|
||||||
@@ -52,20 +52,41 @@ const otaInfo = ref({
|
|||||||
forceUpdate: false,
|
forceUpdate: false,
|
||||||
});
|
});
|
||||||
const isStartingOta = ref(false);
|
const isStartingOta = ref(false);
|
||||||
|
let isCheckingOta = false;
|
||||||
|
let otaCheckQueuedForOnline = false;
|
||||||
let otaProgressTimer = null;
|
let otaProgressTimer = null;
|
||||||
let otaStatusTimer = null;
|
let otaStatusTimer = null;
|
||||||
let otaTimeoutTimer = null;
|
let otaTimeoutTimer = null;
|
||||||
|
let otaResultTimer = null;
|
||||||
|
let otaStatusPollCount = 0;
|
||||||
|
let otaUpdateRunId = 0;
|
||||||
|
// 首页 OTA 轮询采用请求次数和总时长双重兜底,避免更新中状态长期卡住。
|
||||||
|
const OTA_TASK_STATUS_POLL_INTERVAL = 2000;
|
||||||
|
const OTA_TASK_STATUS_MAX_POLL_COUNT = 15;
|
||||||
|
const OTA_TASK_STATUS_TIMEOUT = 30000;
|
||||||
|
|
||||||
// 清理首页 OTA 更新定时器,避免弹窗关闭或页面卸载后继续轮询。
|
// 清理首页 OTA 更新定时器,避免弹窗关闭或页面卸载后继续轮询。
|
||||||
const clearOtaUpdateTimers = () => {
|
const clearOtaUpdateTimers = () => {
|
||||||
clearInterval(otaProgressTimer);
|
clearInterval(otaProgressTimer);
|
||||||
clearTimeout(otaStatusTimer);
|
clearTimeout(otaStatusTimer);
|
||||||
clearTimeout(otaTimeoutTimer);
|
clearTimeout(otaTimeoutTimer);
|
||||||
|
clearTimeout(otaResultTimer);
|
||||||
otaProgressTimer = null;
|
otaProgressTimer = null;
|
||||||
otaStatusTimer = null;
|
otaStatusTimer = null;
|
||||||
otaTimeoutTimer = null;
|
otaTimeoutTimer = null;
|
||||||
|
otaResultTimer = null;
|
||||||
|
otaStatusPollCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 使当前 OTA 运行失效,确保旧请求返回后不会继续轮询或覆盖新状态。
|
||||||
|
const invalidateOtaUpdateRun = () => {
|
||||||
|
otaUpdateRunId += 1;
|
||||||
|
clearOtaUpdateTimers();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOtaUpdateRunActive = (runId) =>
|
||||||
|
runId === otaUpdateRunId && otaState.value === "update_progress";
|
||||||
|
|
||||||
// 启动首页 OTA 本地进度动画,最终成功失败以后端任务状态为准。
|
// 启动首页 OTA 本地进度动画,最终成功失败以后端任务状态为准。
|
||||||
const startOtaProgressAnimation = () => {
|
const startOtaProgressAnimation = () => {
|
||||||
clearInterval(otaProgressTimer);
|
clearInterval(otaProgressTimer);
|
||||||
@@ -91,21 +112,51 @@ const applyOtaVersionInfo = (versionInfo) => {
|
|||||||
|
|
||||||
// 检查当前设备盒子是否存在可升级版本。
|
// 检查当前设备盒子是否存在可升级版本。
|
||||||
const checkOtaUpdate = async () => {
|
const checkOtaUpdate = async () => {
|
||||||
let versionInfo;
|
if (isCheckingOta || otaVisible.value) return;
|
||||||
|
isCheckingOta = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
versionInfo = await getHardwareBoxVersionAPI();
|
let deviceStatus;
|
||||||
} catch (err) {
|
try {
|
||||||
|
deviceStatus = await getDeviceBatteryAPI();
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (deviceStatus?.online !== true || otaVisible.value) return;
|
||||||
|
|
||||||
|
let versionInfo;
|
||||||
|
try {
|
||||||
|
versionInfo = await getHardwareBoxVersionAPI();
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!versionInfo?.needUpdate || otaVisible.value) return;
|
||||||
|
applyOtaVersionInfo(versionInfo);
|
||||||
|
|
||||||
|
const dismissedAt = uni.getStorageSync("ota_dismissed_at");
|
||||||
|
const now = Date.now();
|
||||||
|
if (!otaInfo.value.forceUpdate && dismissedAt && now - dismissedAt < 24 * 60 * 60 * 1000) return;
|
||||||
|
otaState.value = "new_version";
|
||||||
|
otaVisible.value = true;
|
||||||
|
} finally {
|
||||||
|
isCheckingOta = false;
|
||||||
|
const shouldRecheckForOnline = otaCheckQueuedForOnline;
|
||||||
|
otaCheckQueuedForOnline = false;
|
||||||
|
if (shouldRecheckForOnline && !otaVisible.value) {
|
||||||
|
void checkOtaUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设备由 WS 通知上线时补查 OTA;已显示任何 OTA 弹窗时保留当前流程和结果。
|
||||||
|
watch(online, (nextOnline, previousOnline) => {
|
||||||
|
if (previousOnline !== false || nextOnline !== true || otaVisible.value) return;
|
||||||
|
if (isCheckingOta) {
|
||||||
|
otaCheckQueuedForOnline = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!versionInfo?.needUpdate) return;
|
void checkOtaUpdate();
|
||||||
applyOtaVersionInfo(versionInfo);
|
});
|
||||||
|
|
||||||
const dismissedAt = uni.getStorageSync("ota_dismissed_at");
|
|
||||||
const now = Date.now();
|
|
||||||
if (!otaInfo.value.forceUpdate && dismissedAt && now - dismissedAt < 24 * 60 * 60 * 1000) return;
|
|
||||||
otaState.value = "new_version";
|
|
||||||
otaVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 拼接 OTA WiFi 页参数,让未连 WiFi 的设备继续使用同一份版本信息。
|
// 拼接 OTA WiFi 页参数,让未连 WiFi 的设备继续使用同一份版本信息。
|
||||||
const getOtaWifiUrl = () => {
|
const getOtaWifiUrl = () => {
|
||||||
@@ -125,61 +176,78 @@ const handleOtaDismiss = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 将首页 OTA 直连更新流程标记为失败。
|
// 将首页 OTA 直连更新流程标记为失败。
|
||||||
const failHomeOtaUpdate = () => {
|
const failHomeOtaUpdate = (runId) => {
|
||||||
clearOtaUpdateTimers();
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
|
invalidateOtaUpdateRun();
|
||||||
isStartingOta.value = false;
|
isStartingOta.value = false;
|
||||||
otaState.value = "update_failure";
|
otaState.value = "update_failure";
|
||||||
otaVisible.value = true;
|
otaVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 将首页 OTA 直连更新流程标记为成功。
|
// 将首页 OTA 直连更新流程标记为成功。
|
||||||
const completeHomeOtaUpdate = () => {
|
const completeHomeOtaUpdate = (runId) => {
|
||||||
clearOtaUpdateTimers();
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
|
invalidateOtaUpdateRun();
|
||||||
isStartingOta.value = false;
|
isStartingOta.value = false;
|
||||||
otaProgress.value = 100;
|
otaProgress.value = 100;
|
||||||
setTimeout(() => {
|
const completedRunId = otaUpdateRunId;
|
||||||
|
otaResultTimer = setTimeout(() => {
|
||||||
|
otaResultTimer = null;
|
||||||
|
if (completedRunId !== otaUpdateRunId) return;
|
||||||
otaState.value = "update_success";
|
otaState.value = "update_success";
|
||||||
otaVisible.value = true;
|
otaVisible.value = true;
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 轮询首页直接发起的 OTA 更新任务状态。
|
// 轮询首页直接发起的 OTA 更新任务状态。
|
||||||
const pollHomeOtaTaskStatus = (taskId) => {
|
const pollHomeOtaTaskStatus = async (taskId, runId) => {
|
||||||
clearTimeout(otaStatusTimer);
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
otaStatusTimer = setTimeout(async () => {
|
|
||||||
try {
|
otaStatusPollCount += 1;
|
||||||
const taskStatus = await getHardwareBoxTaskStatusAPI(taskId);
|
try {
|
||||||
const status = Number(taskStatus?.status);
|
const taskStatus = await getHardwareBoxTaskStatusAPI(taskId);
|
||||||
if (status === 2) {
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
completeHomeOtaUpdate();
|
|
||||||
return;
|
const status = Number(taskStatus?.status);
|
||||||
}
|
if (status === 2) {
|
||||||
if (status === 3) {
|
completeHomeOtaUpdate(runId);
|
||||||
failHomeOtaUpdate();
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (status === 0 || status === 1) {
|
|
||||||
pollHomeOtaTaskStatus(taskId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
failHomeOtaUpdate();
|
|
||||||
} catch (err) {
|
|
||||||
failHomeOtaUpdate();
|
|
||||||
}
|
}
|
||||||
}, 3000);
|
if (status === 3) {
|
||||||
|
failHomeOtaUpdate(runId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (status === 0 || status === 1) {
|
||||||
|
if (otaStatusPollCount >= OTA_TASK_STATUS_MAX_POLL_COUNT) {
|
||||||
|
failHomeOtaUpdate(runId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
otaStatusTimer = setTimeout(() => {
|
||||||
|
otaStatusTimer = null;
|
||||||
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
|
void pollHomeOtaTaskStatus(taskId, runId);
|
||||||
|
}, OTA_TASK_STATUS_POLL_INTERVAL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
failHomeOtaUpdate(runId);
|
||||||
|
} catch (err) {
|
||||||
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
|
failHomeOtaUpdate(runId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设备盒子已连 WiFi 时,从首页直接传空 WiFi 信息发起 OTA 更新。
|
// 设备盒子已连 WiFi 时,从首页直接传空 WiFi 信息发起 OTA 更新。
|
||||||
const startHomeOtaUpdate = async () => {
|
const startHomeOtaUpdate = async () => {
|
||||||
|
invalidateOtaUpdateRun();
|
||||||
|
const runId = otaUpdateRunId;
|
||||||
otaState.value = "update_progress";
|
otaState.value = "update_progress";
|
||||||
otaVisible.value = true;
|
otaVisible.value = true;
|
||||||
otaProgress.value = 0;
|
otaProgress.value = 0;
|
||||||
startOtaProgressAnimation();
|
startOtaProgressAnimation();
|
||||||
otaTimeoutTimer = setTimeout(() => {
|
otaTimeoutTimer = setTimeout(() => {
|
||||||
if (otaState.value === "update_progress") {
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
failHomeOtaUpdate();
|
failHomeOtaUpdate(runId);
|
||||||
}
|
}, OTA_TASK_STATUS_TIMEOUT);
|
||||||
}, 5 * 60 * 1000);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updateResult = await sendHardwareBoxUpdateAPI({
|
const updateResult = await sendHardwareBoxUpdateAPI({
|
||||||
@@ -188,13 +256,15 @@ const startHomeOtaUpdate = async () => {
|
|||||||
wifiPassword: "",
|
wifiPassword: "",
|
||||||
resourceUrl: otaInfo.value.resourceUrl,
|
resourceUrl: otaInfo.value.resourceUrl,
|
||||||
});
|
});
|
||||||
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
if (!updateResult?.taskId) {
|
if (!updateResult?.taskId) {
|
||||||
failHomeOtaUpdate();
|
failHomeOtaUpdate(runId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pollHomeOtaTaskStatus(updateResult.taskId);
|
void pollHomeOtaTaskStatus(updateResult.taskId, runId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failHomeOtaUpdate();
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
|
failHomeOtaUpdate(runId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,6 +310,7 @@ const handleOtaDone = () => {
|
|||||||
|
|
||||||
// 处理 OTA 更新失败后的重试按钮,重新走立即更新判断流程。
|
// 处理 OTA 更新失败后的重试按钮,重新走立即更新判断流程。
|
||||||
const handleOtaRetry = () => {
|
const handleOtaRetry = () => {
|
||||||
|
invalidateOtaUpdateRun();
|
||||||
handleOtaUpdate();
|
handleOtaUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -351,7 +422,7 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clearOtaUpdateTimers();
|
invalidateOtaUpdateRun();
|
||||||
});
|
});
|
||||||
|
|
||||||
onShareAppMessage(() => {
|
onShareAppMessage(() => {
|
||||||
|
|||||||
@@ -1,35 +1,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import Signin from "@/components/Signin.vue";
|
import Signin from "@/components/Signin.vue";
|
||||||
import SButton from "@/components/SButton.vue";
|
import SButton from "@/components/SButton.vue";
|
||||||
import Avatar from "@/components/Avatar.vue";
|
|
||||||
import AppBackground from "@/components/AppBackground.vue";
|
|
||||||
import DeviceChargingDialog from "@/components/DeviceChargingDialog.vue";
|
import DeviceChargingDialog from "@/components/DeviceChargingDialog.vue";
|
||||||
import { tempBindOrgAPI } from "@/apis";
|
import { tempBindOrgAPI } from "@/apis";
|
||||||
import useStore from "@/store";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
const { user } = storeToRefs(store);
|
|
||||||
|
|
||||||
const scene = ref("");
|
const scene = ref("");
|
||||||
const status = ref("idle");
|
const status = ref("idle");
|
||||||
const errorMessage = ref("");
|
const errorMessage = ref("");
|
||||||
const showSignin = ref(false);
|
const showSignin = ref(false);
|
||||||
const binding = ref(false);
|
const binding = ref(false);
|
||||||
const bindInfo = ref({});
|
|
||||||
|
|
||||||
const successInfo = computed(() => {
|
|
||||||
const result = bindInfo.value || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
avatar: user.value.avatar,
|
|
||||||
nickName: user.value.nickName || "--",
|
|
||||||
mobile: result.mobile || "--",
|
|
||||||
storeName: result.storeName || "--",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getToken = () => {
|
const getToken = () => {
|
||||||
try {
|
try {
|
||||||
@@ -59,17 +40,9 @@ const bindOrg = async () => {
|
|||||||
binding.value = true;
|
binding.value = true;
|
||||||
status.value = "binding";
|
status.value = "binding";
|
||||||
errorMessage.value = "";
|
errorMessage.value = "";
|
||||||
bindInfo.value = {};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = (await tempBindOrgAPI(scene.value)) || {};
|
await tempBindOrgAPI(scene.value);
|
||||||
if (result.success !== true) {
|
|
||||||
status.value = "failed";
|
|
||||||
errorMessage.value = result.msg || "授权登录失败,请稍后重试。";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindInfo.value = result;
|
|
||||||
status.value = "success";
|
status.value = "success";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.type === "AUTH_INVALID") {
|
if (error?.type === "AUTH_INVALID") {
|
||||||
@@ -110,69 +83,45 @@ onLoad((options) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<AppBackground :type="6" bgColor="#20202c" />
|
|
||||||
|
|
||||||
<view class="content">
|
<view class="content">
|
||||||
<text class="title">登录门店Pad端</text>
|
<text class="title">机构设备绑定</text>
|
||||||
|
|
||||||
<view
|
<view v-if="status === 'idle' || status === 'binding'" class="state">
|
||||||
v-if="status === 'idle' || status === 'binding'"
|
<text class="state-title">正在绑定</text>
|
||||||
class="state message-state"
|
<text class="description">正在绑定机构设备,请稍候……</text>
|
||||||
>
|
|
||||||
<text class="state-title">正在授权登录</text>
|
|
||||||
<text class="description">正在登录门店Pad端,请稍候……</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else-if="status === 'login'" class="state login-state">
|
<view v-else-if="status === 'login'" class="state">
|
||||||
<image
|
<text class="state-title">请先登录</text>
|
||||||
class="login-icon"
|
<text class="description">
|
||||||
src="../static/org-bind/login-icon.png"
|
{{
|
||||||
mode="aspectFit"
|
errorMessage ||
|
||||||
/>
|
"登录后即可绑定当前机构设备。关闭登录窗口后,也可以再次点击下方按钮继续。"
|
||||||
<text class="login-title">{{
|
}}
|
||||||
errorMessage || "请先登录小程序"
|
</text>
|
||||||
}}</text>
|
<SButton width="560rpx" :rounded="20" :onClick="openSignin">
|
||||||
<SButton width="600rpx" :rounded="22" :onClick="openSignin">
|
<text>立即登录</text>
|
||||||
<text>微信授权登录</text>
|
|
||||||
</SButton>
|
</SButton>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else-if="status === 'success'" class="state success-state">
|
<view v-else-if="status === 'success'" class="state">
|
||||||
<view class="avatar-wrap">
|
<text class="state-title">绑定成功</text>
|
||||||
<Avatar
|
<text class="description">
|
||||||
:src="successInfo.avatar"
|
您的账号已成功绑定机构设备,请返回 iPad 继续操作。
|
||||||
:size="176"
|
</text>
|
||||||
sizeUnit="rpx"
|
|
||||||
imageMode="aspectFill"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
class="success-badge"
|
|
||||||
src="../static/org-bind/green-gou.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<text class="success-title">已成功授权登录射灵星球门店Pad端。</text>
|
|
||||||
<view class="account-info">
|
|
||||||
<text>登录账号:{{ successInfo.nickName }}</text>
|
|
||||||
<text>手机号:{{ successInfo.mobile }}</text>
|
|
||||||
<text>登录门店:{{ successInfo.storeName }}</text>
|
|
||||||
<text class="warm-tip">
|
|
||||||
温馨提示:离开门店时候记得在Pad退出登录哦!
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else-if="status === 'failed'" class="state message-state">
|
<view v-else-if="status === 'failed'" class="state">
|
||||||
<text class="state-title">授权登录失败</text>
|
<text class="state-title">绑定失败</text>
|
||||||
<text class="description">{{ errorMessage }}</text>
|
<text class="description">{{ errorMessage }}</text>
|
||||||
<SButton width="600rpx" :rounded="22" :onClick="bindOrg">
|
<SButton width="560rpx" :rounded="20" :onClick="bindOrg">
|
||||||
<text>重新登录</text>
|
<text>重新绑定</text>
|
||||||
</SButton>
|
</SButton>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="state message-state">
|
<view v-else class="state">
|
||||||
<text class="state-title">二维码无效</text>
|
<text class="state-title">二维码无效</text>
|
||||||
<text class="description">请重新扫描门店Pad端上的小程序码。</text>
|
<text class="description">请重新扫描机构设备上的小程序码。</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -190,93 +139,32 @@ onLoad((options) => {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: calc(var(--status-bar-height) + 64rpx) 48rpx 80rpx;
|
padding: calc(var(--status-bar-height) + 120rpx) 48rpx 80rpx;
|
||||||
|
background-color: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: #d8ad69;
|
font-size: 44rpx;
|
||||||
font-size: 30rpx;
|
font-weight: 600;
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.state {
|
.state {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-top: 120rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-state,
|
|
||||||
.success-state {
|
|
||||||
margin-top: 320rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-state {
|
|
||||||
margin-top: 240rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-icon {
|
|
||||||
width: 176rpx;
|
|
||||||
height: 176rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
margin: 28rpx 0 60rpx;
|
|
||||||
font-size: 40rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrap {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-badge {
|
|
||||||
position: absolute;
|
|
||||||
right: -2rpx;
|
|
||||||
bottom: 4rpx;
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-title {
|
|
||||||
width: 572rpx;
|
|
||||||
margin-top: 28rpx;
|
|
||||||
color: #fed847;
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 48rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-info {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 572rpx;
|
|
||||||
margin-top: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 28rpx;
|
|
||||||
line-height: 52rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warm-tip {
|
|
||||||
margin-top: 40rpx;
|
|
||||||
color: #b8b8bd;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-title {
|
.state-title {
|
||||||
font-size: 38rpx;
|
font-size: 36rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,6 @@ const toFristTryPage = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const toCoinPage = () => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: "/pages/coin/index",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const toBeVipPage = () => {
|
const toBeVipPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/member/be-vip",
|
url: "/pages/member/be-vip",
|
||||||
@@ -113,7 +108,6 @@ const buildVersion = typeof __BUILD_TIME__ !== 'undefined' ? __BUILD_TIME__ : ''
|
|||||||
<text v-if="user.trio > 0" :style="{ color: '#259249' }">已完成</text>
|
<text v-if="user.trio > 0" :style="{ color: '#259249' }">已完成</text>
|
||||||
<text v-else :style="{ color: '#CC311F' }">未完成</text>
|
<text v-else :style="{ color: '#CC311F' }">未完成</text>
|
||||||
</UserItem>
|
</UserItem>
|
||||||
<UserItem title="我的金币" :onClick="toCoinPage" />
|
|
||||||
<UserItem title="会员" :onClick="toBeVipPage">
|
<UserItem title="会员" :onClick="toBeVipPage">
|
||||||
<view
|
<view
|
||||||
v-if="user.sVip === true"
|
v-if="user.sVip === true"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 742 B |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 41 KiB |