update:暂存金币相关

This commit is contained in:
2026-08-19 10:14:17 +08:00
parent d60c32506f
commit 63f3cada0f
36 changed files with 1303 additions and 2 deletions
@@ -0,0 +1,76 @@
<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>
@@ -0,0 +1,34 @@
<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>
+86
View File
@@ -0,0 +1,86 @@
<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>
@@ -0,0 +1,50 @@
<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>
@@ -0,0 +1,188 @@
<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>
@@ -0,0 +1,51 @@
<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>
@@ -0,0 +1,53 @@
<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>
@@ -0,0 +1,82 @@
<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>
@@ -0,0 +1,96 @@
<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>
+63
View File
@@ -0,0 +1,63 @@
<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>
+114
View File
@@ -0,0 +1,114 @@
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 日内凭完整包装联系客服核实处理,提供维修 / 更换方案。品牌赠品官方保修请参照品牌厂商政策。",
],
};
+35
View File
@@ -0,0 +1,35 @@
<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>
+35
View File
@@ -0,0 +1,35 @@
<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>
+91
View File
@@ -0,0 +1,91 @@
<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>
+81
View File
@@ -0,0 +1,81 @@
<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>
+95
View File
@@ -0,0 +1,95 @@
<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>
+38
View File
@@ -0,0 +1,38 @@
<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>