修复了大部分样式引起的小问题

This commit is contained in:
tsui110 2026-02-07 00:58:10 +08:00
parent e7256ae88e
commit 636041d6fa
7 changed files with 334 additions and 42 deletions

View File

@ -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>
@ -2365,13 +2365,13 @@ onLoad((opts) => {
justify-content: center;
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;
}
&:active {
background: rgba(255, 255, 255, 0.2);
}

View File

@ -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);

View File

@ -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,15 +120,23 @@ 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)
const errorCode = e?.data?.code || e?.code
const errorMessage = e?.data?.message || e?.message || e?.msg
@ -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;

View File

@ -125,12 +125,12 @@
class="field-input"
maxlength="6"
/>
<view
class="send-code-btn"
<view
class="send-code-btn"
: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>
@ -989,14 +989,14 @@ function fetchExtraData(userId) {
align-items: center;
justify-content: center;
margin-left: 16rpx;
text {
.send-code-btn-text {
font-size: 24rpx;
color: #fff;
font-weight: 600;
white-space: nowrap;
}
&.disabled {
background: $text-tertiary;
}

View File

@ -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,11 +47,14 @@
<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">
<view
class="tab-pill-item"
<view
class="tab-pill-item"
v-for="tab in tabs" :key="tab.id"
:class="{ active: currentTab === tab.id }"
@tap="switchTab(tab.id)"
@ -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;
}
/* 缩放动画的核心部分 */
@ -685,16 +871,36 @@ onUnmounted(() => {
padding-top: 120%; /* 偶数项更高,产生交错效果 */
}
.thumb-wrap {
width: 100%;
padding-top: 100%;
position: relative;
.thumb-wrap {
width: 100%;
padding-top: 100%;
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; } }

View File

@ -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
}

View File

@ -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() {