修复了大部分样式引起的小问题
This commit is contained in:
parent
e7256ae88e
commit
636041d6fa
@ -104,7 +104,7 @@
|
||||
<view class="game-topbar">
|
||||
<text class="game-topbar-title">对对碰游戏</text>
|
||||
<view class="game-close-btn" @tap="closeGame">
|
||||
<text>×</text>
|
||||
<text class="game-close-btn-text">×</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -2366,7 +2366,7 @@ onLoad((opts) => {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
|
||||
text {
|
||||
.game-close-btn-text {
|
||||
font-size: 48rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1;
|
||||
|
||||
@ -69,9 +69,9 @@
|
||||
>
|
||||
<text class="choice-number">{{ item.number || item.position || index + 1 }}</text>
|
||||
<view class="choice-status">
|
||||
<text v-if="item.status === 'sold' || item.is_sold">已售</text>
|
||||
<text v-else-if="isChoiceSelected(item)">已选</text>
|
||||
<text v-else>可选</text>
|
||||
<text class="choice-status-text" v-if="item.status === 'sold' || item.is_sold">已售</text>
|
||||
<text class="choice-status-text" v-else-if="isChoiceSelected(item)">已选</text>
|
||||
<text class="choice-status-text" v-else>可选</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -960,7 +960,7 @@ watch([activityId, currentIssueId], ([newActId, newIssueId]) => {
|
||||
font-size: 20rpx;
|
||||
color: $text-tertiary;
|
||||
|
||||
text {
|
||||
.choice-status-text {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
|
||||
@ -4,7 +4,14 @@
|
||||
<view class="loading" v-if="loading">加载中...</view>
|
||||
<view v-else-if="isOutOfStock" class="empty">商品库存不足,由于市场价格存在波动,请联系客服核实价格和补充库存</view>
|
||||
<view v-else-if="detail.id" class="detail-wrap">
|
||||
<image v-if="detail.main_image" class="main-image" :src="detail.main_image" mode="widthFix" />
|
||||
<!-- 商品图片轮播 -->
|
||||
<swiper class="main-image-swiper" v-if="imageList.length > 0" circular autoplay interval="3000" duration="500">
|
||||
<swiper-item v-for="(img, index) in imageList" :key="index">
|
||||
<image class="main-image" :src="img" mode="aspectFill" @tap="previewImage(index)" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- 单张图片显示 -->
|
||||
<image v-else-if="mainImage" class="main-image" :src="mainImage" mode="aspectFill" @tap="previewImage(0)" />
|
||||
<view class="info-card">
|
||||
<view class="title">{{ detail.title || detail.name || '-' }}</view>
|
||||
<view class="price-row">
|
||||
@ -37,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getProductDetail } from '../../api/appUser'
|
||||
import { redeemProductByPoints } from '../../utils/request.js'
|
||||
@ -46,11 +53,53 @@ const detail = ref({})
|
||||
const loading = ref(false)
|
||||
const isOutOfStock = ref(false)
|
||||
|
||||
// 计算属性:处理商品图片
|
||||
const imageList = computed(() => {
|
||||
if (!detail.value) return []
|
||||
|
||||
// 优先使用 album 字段
|
||||
if (detail.value.album) {
|
||||
// 如果 album 是数组,直接返回
|
||||
if (Array.isArray(detail.value.album)) {
|
||||
return detail.value.album.filter(img => img) // 过滤掉空值
|
||||
}
|
||||
// 如果 album 是字符串,转换为数组
|
||||
if (typeof detail.value.album === 'string') {
|
||||
return [detail.value.album]
|
||||
}
|
||||
}
|
||||
|
||||
// 降级使用 main_image 字段
|
||||
if (detail.value.main_image) {
|
||||
return [detail.value.main_image]
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
// 单张图片(用于非轮播显示)
|
||||
const mainImage = computed(() => {
|
||||
if (imageList.value.length > 0) {
|
||||
return imageList.value[0]
|
||||
}
|
||||
return detail.value.main_image || ''
|
||||
})
|
||||
|
||||
function formatPrice(p) {
|
||||
if (p === undefined || p === null) return '0.00'
|
||||
return (Number(p) / 100).toFixed(2)
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
function previewImage(index) {
|
||||
if (imageList.value.length > 0) {
|
||||
uni.previewImage({
|
||||
urls: imageList.value,
|
||||
current: index
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化积分显示 - 不四舍五入,保留两位小数
|
||||
function formatPoints(value) {
|
||||
if (value === undefined || value === null) return '0.00'
|
||||
@ -71,13 +120,21 @@ async function fetchDetail(id) {
|
||||
try {
|
||||
const res = await getProductDetail(id)
|
||||
detail.value = res || {}
|
||||
console.log(detail.value);
|
||||
if (detail.value.code === 20002 || detail.value.message === '商品缺货') {
|
||||
// 商品缺货时标记状态,这样页面会显示缺货提示而不是"商品不存在"
|
||||
// request.js 中已经会弹出提示窗口
|
||||
isOutOfStock.value = true
|
||||
console.log('[商品详情] 商品缺货')
|
||||
}
|
||||
console.log('[商品详情] 原始数据:', detail.value)
|
||||
|
||||
// 处理库存字段 - 将 in_stock 转换为 stock
|
||||
if (detail.value.in_stock !== undefined) {
|
||||
detail.value.stock = detail.value.in_stock ? 99 : 0
|
||||
}
|
||||
|
||||
console.log('[商品详情] 处理后数据:', detail.value)
|
||||
|
||||
if (detail.value.code === 20002 || detail.value.message === '商品缺货') {
|
||||
// 商品缺货时标记状态,这样页面会显示缺货提示而不是"商品不存在"
|
||||
// request.js 中已经会弹出提示窗口
|
||||
isOutOfStock.value = true
|
||||
console.log('[商品详情] 商品缺货')
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
// 检查是否是商品缺货错误 (code: 20002)
|
||||
@ -233,6 +290,13 @@ onLoad((opts) => {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 商品图片轮播 */
|
||||
.main-image-swiper {
|
||||
width: 100%;
|
||||
height: 750rpx;
|
||||
background: $bg-secondary;
|
||||
}
|
||||
|
||||
.main-image {
|
||||
width: 100%;
|
||||
height: 750rpx;
|
||||
|
||||
@ -130,7 +130,7 @@
|
||||
:class="{ disabled: !canSendCode || sendingCode || countdown > 0 }"
|
||||
@tap="handleSendCode"
|
||||
>
|
||||
<text>{{ sendingCode ? '发送中' : (countdown > 0 ? `${countdown}s` : '获取验证码') }}</text>
|
||||
<text class="send-code-btn-text">{{ sendingCode ? '发送中' : (countdown > 0 ? `${countdown}s` : '获取验证码') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -990,7 +990,7 @@ function fetchExtraData(userId) {
|
||||
justify-content: center;
|
||||
margin-left: 16rpx;
|
||||
|
||||
text {
|
||||
.send-code-btn-text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
<view class="page">
|
||||
<view class="bg-decoration"></view>
|
||||
|
||||
<!-- 自定义 tabBar -->
|
||||
<!-- #ifdef MP-TOUTIAO -->
|
||||
<customTabBarToutiao />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-TOUTIAO -->
|
||||
<customTabBar />
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- [NEW] 全新左右布局布局容器 -->
|
||||
<view class="shop-layout">
|
||||
|
||||
@ -39,6 +47,9 @@
|
||||
<view class="search-bar">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input class="search-input" v-model="keyword" placeholder="搜好物" @confirm="onSearchConfirm" />
|
||||
<view class="filter-btn" @tap="togglePriceFilter">
|
||||
<text class="filter-icon">🔽</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 频道切换 (商品/优惠券) -->
|
||||
<view class="tab-pill">
|
||||
@ -54,6 +65,33 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格区间筛选面板 (独立浮层) -->
|
||||
<view class="price-filter-panel" :class="{ expanded: showPriceFilter }">
|
||||
<view class="filter-row">
|
||||
<view class="price-input-group">
|
||||
<input class="price-input" v-model="priceMin" type="digit" placeholder="最低价" />
|
||||
<text class="price-separator">-</text>
|
||||
<input class="price-input" v-model="priceMax" type="digit" placeholder="最高价" />
|
||||
</view>
|
||||
<view class="filter-actions">
|
||||
<view class="filter-btn-reset" @tap="resetPriceFilter">重置</view>
|
||||
<view class="filter-btn-confirm" @tap="applyPriceFilter">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 快捷价格区间 -->
|
||||
<view class="quick-price-ranges">
|
||||
<view
|
||||
v-for="range in priceRanges"
|
||||
:key="range.key"
|
||||
class="price-range-tag"
|
||||
:class="{ active: isRangeActive(range.key) }"
|
||||
@tap="selectQuickPrice(range)"
|
||||
>
|
||||
{{ range.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容滚动容器 (带缩放动画) -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
@ -81,7 +119,7 @@
|
||||
<text class="points-val">{{ p.points }}</text>
|
||||
<text class="points-unit">积分</text>
|
||||
</view>
|
||||
<view class="redeem-btn-sm" @tap.stop="onRedeemTap(p)">+</view>
|
||||
<view class="redeem-btn-sm" v-if="p.stock > 0" @tap.stop="onRedeemTap(p)">+</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -111,7 +149,7 @@
|
||||
</view>
|
||||
|
||||
<view v-if="!items.length && !loading" class="empty-mini">
|
||||
<image src="/static/empty.png" mode="widthFix" />
|
||||
<image class="empty-mini-img" src="/static/empty.png" mode="widthFix" />
|
||||
<text>空空如也</text>
|
||||
</view>
|
||||
|
||||
@ -153,6 +191,9 @@ const hasMore = ref(true)
|
||||
const categories = ref([])
|
||||
const selectedCategoryId = ref(0)
|
||||
|
||||
// 标记是否已经初始化加载过数据
|
||||
let hasInitialized = false
|
||||
|
||||
// 趣味加载文字数组
|
||||
const loadingTexts = [
|
||||
'请稍等,正在努力加载中。。。',
|
||||
@ -201,6 +242,7 @@ watch(loading, (newVal) => {
|
||||
// 价格筛选相关
|
||||
const priceMin = ref('')
|
||||
const priceMax = ref('')
|
||||
const showPriceFilter = ref(false)
|
||||
const priceRanges = [
|
||||
{ key: 'all', label: '全部', min: null, max: null },
|
||||
{ key: '0-100', label: '0-100', min: 0, max: 100 },
|
||||
@ -351,6 +393,8 @@ function applyPriceFilter() {
|
||||
hasMore.value = true
|
||||
allItems.value = []
|
||||
loadItems()
|
||||
// 关闭筛选面板
|
||||
showPriceFilter.value = false
|
||||
}
|
||||
|
||||
function resetPriceFilter() {
|
||||
@ -362,9 +406,12 @@ function resetPriceFilter() {
|
||||
hasMore.value = true
|
||||
allItems.value = []
|
||||
loadItems()
|
||||
// 关闭筛选面板
|
||||
showPriceFilter.value = false
|
||||
}
|
||||
|
||||
function selectQuickPrice(range) {
|
||||
vibrateShort()
|
||||
activePriceRange.value = range.key
|
||||
if (range.min !== null) {
|
||||
priceMin.value = range.min.toString()
|
||||
@ -381,12 +428,20 @@ function selectQuickPrice(range) {
|
||||
hasMore.value = true
|
||||
allItems.value = []
|
||||
loadItems()
|
||||
// 关闭筛选面板
|
||||
showPriceFilter.value = false
|
||||
}
|
||||
|
||||
function isRangeActive(range) {
|
||||
return activePriceRange.value === range.key
|
||||
}
|
||||
|
||||
// 切换价格筛选面板显示/隐藏
|
||||
function togglePriceFilter() {
|
||||
vibrateShort()
|
||||
showPriceFilter.value = !showPriceFilter.value
|
||||
}
|
||||
|
||||
function onSearchConfirm() {
|
||||
// 搜索时重新加载数据
|
||||
page.value = 1
|
||||
@ -494,11 +549,15 @@ onShow(() => {
|
||||
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
allItems.value = []
|
||||
loadItems()
|
||||
fetchCategories()
|
||||
// 只在首次加载时初始化数据,避免从详情页返回时重复加载
|
||||
if (!hasInitialized) {
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
allItems.value = []
|
||||
loadItems()
|
||||
fetchCategories()
|
||||
hasInitialized = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -590,11 +649,18 @@ onUnmounted(() => {
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
padding: 20rpx 24rpx;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.03);
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(30rpx);
|
||||
-webkit-backdrop-filter: blur(30rpx);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.search-wrap {
|
||||
display: flex;
|
||||
@ -614,6 +680,124 @@ onUnmounted(() => {
|
||||
.search-icon { font-size: 24rpx; opacity: 0.4; }
|
||||
.search-input { flex: 1; margin-left: 12rpx; font-size: 24rpx; }
|
||||
|
||||
/* 筛选按钮 */
|
||||
.filter-btn {
|
||||
margin-left: 12rpx;
|
||||
padding: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.filter-icon {
|
||||
font-size: 20rpx;
|
||||
opacity: 0.5;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
/* 价格筛选面板 */
|
||||
.price-filter-panel {
|
||||
position: absolute;
|
||||
top: 120rpx;
|
||||
left: 24rpx;
|
||||
right: 24rpx;
|
||||
padding: 20rpx;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-out;
|
||||
z-index: 100;
|
||||
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.12);
|
||||
pointer-events: none;
|
||||
|
||||
&.expanded {
|
||||
max-height: 500rpx;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.price-input-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 12rpx;
|
||||
padding: 0 16rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
|
||||
.price-input {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.price-separator {
|
||||
margin: 0 12rpx;
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.filter-btn-reset,
|
||||
.filter-btn-confirm {
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-btn-reset {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.filter-btn-confirm {
|
||||
background: $gradient-brand;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 快捷价格区间 */
|
||||
.quick-price-ranges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.price-range-tag {
|
||||
padding: 10rpx 20rpx;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
border-radius: 20rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.active {
|
||||
background: $gradient-brand;
|
||||
color: #fff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-pill {
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
@ -638,6 +822,8 @@ onUnmounted(() => {
|
||||
height: 0; /* 关键:在 flex 布局中需要设置为 0 才能正确计算高度 */
|
||||
background: #fcfcfc;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 缩放动画的核心部分 */
|
||||
@ -691,10 +877,30 @@ onUnmounted(() => {
|
||||
position: relative;
|
||||
background: #fafafa;
|
||||
overflow: hidden;
|
||||
image {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.thumb-wrap .product-thumb {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/* 库存标签 */
|
||||
.stock-tag {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4rpx);
|
||||
-webkit-backdrop-filter: blur(4rpx);
|
||||
z-index: 2;
|
||||
|
||||
&.out {
|
||||
background: rgba(255, 59, 48, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.product-thumb { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
||||
.product-info { padding: 16rpx; }
|
||||
.product-title {
|
||||
@ -722,7 +928,17 @@ onUnmounted(() => {
|
||||
.c-price { font-size: 20rpx; color: #999; }
|
||||
.c-btn { background: #333; color: #fff; font-size: 22rpx; padding: 6rpx 20rpx; border-radius: 10rpx; }
|
||||
|
||||
.empty-mini { padding-top: 100rpx; text-align: center; color: #ccc; font-size: 24rpx; image { width: 160rpx; display: block; margin: 0 auto 10rpx; } }
|
||||
.empty-mini {
|
||||
padding-top: 100rpx;
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.empty-mini-img {
|
||||
width: 160rpx;
|
||||
display: block;
|
||||
margin: 0 auto 10rpx;
|
||||
}
|
||||
.mini-loading { padding-top: 60rpx; display: flex; justify-content: center; }
|
||||
.pulse { width: 40rpx; height: 40rpx; background: $brand-primary; border-radius: 50%; opacity: 0.3; animation: pulse 1s infinite; }
|
||||
@keyframes pulse { from { transform: scale(1); opacity: 0.3; } to { transform: scale(2); opacity: 0; } }
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { getUserProfile } from '../api/appUser'
|
||||
|
||||
// 标记本次应用运行期间是否已经检查过手机号绑定状态
|
||||
let hasCheckedPhoneBound = false
|
||||
|
||||
/**
|
||||
* 检查用户是否已绑定手机号(同步,仅检查本地)
|
||||
* @returns {boolean} 是否已绑定手机号
|
||||
@ -23,9 +26,16 @@ export function hasPhoneBound() {
|
||||
*/
|
||||
export async function checkPhoneBound() {
|
||||
try {
|
||||
// 如果本次应用运行期间已经检查过且已绑定,则跳过检查
|
||||
if (hasCheckedPhoneBound) {
|
||||
console.log('[checkPhoneBound] 本次应用运行期间已检查过绑定状态,跳过重复检查')
|
||||
return true
|
||||
}
|
||||
|
||||
// 优先使用同步检查
|
||||
if (hasPhoneBound()) {
|
||||
console.log('[checkPhoneBound] 用户已通过手机号登录,跳过绑定检查')
|
||||
hasCheckedPhoneBound = true
|
||||
return true
|
||||
}
|
||||
|
||||
@ -41,6 +51,7 @@ export async function checkPhoneBound() {
|
||||
console.log('[checkPhoneBound] 已检测到手机号,允许通过:', mobile)
|
||||
// 缓存手机号
|
||||
uni.setStorageSync('phone_number', mobile)
|
||||
hasCheckedPhoneBound = true
|
||||
return true
|
||||
}
|
||||
|
||||
@ -65,6 +76,7 @@ export async function checkPhoneBound() {
|
||||
console.log('[checkPhoneBound] 降级检查本地缓存:', phoneNumber ? phoneNumber : '未找到')
|
||||
|
||||
if (phoneNumber) {
|
||||
hasCheckedPhoneBound = true
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const BASE_URL = 'http://127.0.0.1:9991'
|
||||
// const BASE_URL = 'https://kdy.1024tool.vip'
|
||||
//const BASE_URL = 'http://127.0.0.1:9991'
|
||||
const BASE_URL = 'https://kdy.1024tool.vip'
|
||||
let authModalShown = false
|
||||
|
||||
function handleAuthExpired() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user