diff --git a/App.vue b/App.vue
index f4eedfd..388ac50 100644
--- a/App.vue
+++ b/App.vue
@@ -74,7 +74,10 @@ export default {
const isFirst = uni.getStorageSync('is_first_launch')
if (!isFirst || isFirst === 'true') {
// 首次使用 → 引导页
- uni.reLaunch({ url: '/pages/onboarding/onboarding' })
+ // 延迟一帧再跳:启动早期首页还未就绪,立即 reLaunch 可能报 timeout
+ setTimeout(() => {
+ uni.reLaunch({ url: '/pages/onboarding/onboarding', fail: () => {} })
+ }, 100)
}
// 否则正常进入首页(index)
}
diff --git a/common/api.js b/common/api.js
index 01fdcd5..6e783b4 100644
--- a/common/api.js
+++ b/common/api.js
@@ -9,6 +9,22 @@ import UniAppWebSocket from './uni-websocket'
// ==========================================
export const API_HOST = 'https://dev.wash-painting.cn'
+// ==========================================
+// 401 跳登录页防重锁:token 失效时多个接口会同时返回 401,
+// 各自 reLaunch 会并发冲突导致 reLaunch:fail timeout,只放行第一次
+// ==========================================
+let authRedirectLock = false
+function redirectToLogin() {
+ if (authRedirectLock) return
+ authRedirectLock = true
+ setTimeout(() => { authRedirectLock = false }, 3000)
+ const pages = getCurrentPages()
+ const current = pages.length ? pages[pages.length - 1].route : ''
+ // 已在登录页则不重复跳转
+ if (current === 'pages/login/login') return
+ uni.reLaunch({ url: '/pages/login/login', fail: () => {} })
+}
+
// ==========================================
// HTTP 请求函数(供 SDK 内部调用)
// 参考 goodBooth 项目的 fetchsomething
@@ -49,7 +65,7 @@ function httpRequest(url, params) {
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('is_logged_in')
if (!silent) {
- uni.reLaunch({ url: '/pages/login/login' })
+ redirectToLogin()
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none', duration: 2500 })
}
reject({ msg: '登录已过期,请重新登录', code })
diff --git a/common/constants.js b/common/constants.js
index 66a60e4..f3852ac 100644
--- a/common/constants.js
+++ b/common/constants.js
@@ -38,16 +38,44 @@ export const BRANDS = {
{ name: '江小白', product: '表达瓶', degree: 40 }
],
beer: [
+ // 国产主流
{ name: '青岛啤酒', product: '经典', degree: 4.3 },
+ { name: '青岛啤酒', product: '全麦白啤', degree: 4.7 },
+ { name: '青岛啤酒', product: '黑啤', degree: 5.5 },
{ name: '雪花', product: '勇闯天涯', degree: 3.3 },
+ { name: '雪花', product: '纯生', degree: 3.6 },
+ { name: '雪花', product: 'superX', degree: 4.0 },
{ name: '百威', product: '经典', degree: 5.0 },
+ { name: '百威', product: '锦兔', degree: 5.0 },
+ { name: '哈尔滨', product: '冰纯', degree: 4.5 },
+ { name: '哈尔滨', product: '小麦王', degree: 3.6 },
+ { name: '燕京', product: 'U8', degree: 2.5 },
+ { name: '燕京', product: '原浆白啤', degree: 4.8 },
+ { name: '乌苏', product: '大乌苏', degree: 4.0 },
+ { name: '珠江', product: '纯生', degree: 3.6 },
+ { name: '泰山', product: '7天原浆', degree: 4.7 },
+ // 国际品牌
{ name: '喜力', product: '星银', degree: 5.0 },
{ name: '科罗娜', product: 'Extra', degree: 4.5 },
{ name: '嘉士伯', product: '特醇', degree: 5.0 },
- { name: '燕京', product: 'U8', degree: 2.5 },
- { name: '哈尔滨', product: '冰纯', degree: 4.5 },
- { name: '精酿', product: '自选', degree: 6.0 },
- { name: '朝日', product: '超爽', degree: 5.0 }
+ { name: '朝日', product: '超爽', degree: 5.0 },
+ { name: '麒麟', product: '一番榨', degree: 5.0 },
+ { name: '三得利', product: '高端麦芽', degree: 5.5 },
+ { name: '札幌', product: '黑标', degree: 5.0 },
+ { name: '虎牌', product: '经典', degree: 5.0 },
+ { name: '生力', product: '清爽', degree: 5.0 },
+ // 德式/白啤
+ { name: '福佳', product: '白啤', degree: 4.9 },
+ { name: '1664', product: '白啤', degree: 5.0 },
+ { name: '柏龙', product: '小麦啤', degree: 5.5 },
+ { name: '艾丁格', product: '小麦啤', degree: 5.3 },
+ // 精酿
+ { name: '鹅岛', product: 'IPA', degree: 5.9 },
+ { name: '熊猫精酿', product: '蜂蜜生姜艾尔', degree: 5.0 },
+ { name: '京A', product: '工人淡色艾尔', degree: 5.0 },
+ { name: '拳击猫', product: 'TKO IPA', degree: 6.5 },
+ { name: '高大师', product: '婴儿肥IPA', degree: 5.6 },
+ { name: '精酿', product: '自选', degree: 6.0 }
],
wine: [
{ name: '拉菲', product: '传奇', degree: 13 },
diff --git a/pages/index/index.vue b/pages/index/index.vue
index cea7497..3f398ce 100644
--- a/pages/index/index.vue
+++ b/pages/index/index.vue
@@ -190,10 +190,10 @@ export default {
}
},
onShow() {
- // 首次启动跳转引导页
+ // 首次启动跳转引导页由 App.vue onLaunch 统一处理,
+ // 这里不再重复 reLaunch,避免两个跳转并发导致 reLaunch:fail timeout
const isFirst = uni.getStorageSync('is_first_launch')
if (isFirst === 'true') {
- uni.reLaunch({ url: '/pages/onboarding/onboarding' })
return
}
this.loadData()
diff --git a/pages/record/record.vue b/pages/record/record.vue
index 16dca2f..e7eb7cb 100644
--- a/pages/record/record.vue
+++ b/pages/record/record.vue
@@ -48,13 +48,18 @@
品牌
- {{ brand.name }}
+
+ {{ brandList.filter(b => b.name === brand.name).length > 1 ? brand.name + '·' + brand.product : brand.name }}
+
+
+
+ {{ showAllBrands ? '收起 ▴' : '更多 ▾' }}
@@ -81,14 +86,16 @@
-
+
{{ currentDrink.unit === 'ml' ? 'ml' : currentDrink.unit }}
@@ -97,6 +104,8 @@
class="degree-input text-num"
type="digit"
v-model="degreeStr"
+ placeholder="度数"
+ placeholder-class="input-placeholder"
@input="onAmountChange"
/>
°
@@ -339,7 +348,10 @@ export default {
currentDrink: { category: '', brand: '', product: '', amount: 0, unit: 'ml', degree: 0 },
drinks: [],
brandList: [],
+ showAllBrands: false,
+ maxVisibleBrands: 8,
amountStr: '',
+ amountFocused: false,
degreeStr: '',
// Step 2
@@ -363,6 +375,10 @@ export default {
}
},
computed: {
+ // 品牌列表默认只展示前 maxVisibleBrands 个,点“更多”后展开全部
+ visibleBrands() {
+ return this.showAllBrands ? this.brandList : this.brandList.slice(0, this.maxVisibleBrands)
+ },
currentCups() {
const ml = unitToMl(parseFloat(this.amountStr) || 0, this.currentDrink.unit)
return calcStandardCups(ml, parseFloat(this.degreeStr) || 0)
@@ -423,6 +439,7 @@ export default {
this.currentDrink.degree = cat.defaultDegree
this.degreeStr = String(cat.defaultDegree)
this.brandList = BRANDS[cat.id] || []
+ this.showAllBrands = false
},
selectBrand(brand) {
this.currentDrink.brand = brand.name
@@ -522,6 +539,7 @@ export default {
this.amountStr = ''
this.degreeStr = ''
this.brandList = []
+ this.showAllBrands = false
},
async publishRecord() {
const recordData = {
@@ -725,6 +743,13 @@ export default {
margin-bottom: $sp-md;
}
+/* 更多/收起按钮:虚线边框与普通品牌标签区分 */
+.tag-more {
+ border: 2rpx dashed var(--border-faint, rgba(255, 255, 255, 0.2));
+ background: transparent;
+ color: $amber;
+}
+
.input-row {
margin-bottom: $sp-sm;
}
@@ -767,9 +792,19 @@ export default {
.amount-inline {
display: flex;
- align-items: baseline;
+ align-items: center;
gap: $sp-sm;
flex: 1;
+ background: $bg-elevated;
+ border: 2rpx solid var(--border-faint, rgba(255, 255, 255, 0.12));
+ border-radius: $radius-md;
+ padding: $sp-xs $sp-lg;
+ transition: border-color $duration-fast $ease-out;
+}
+
+/* 聚焦时高亮边框,强化可输入感知(小程序不支持 :has(),由容器类控制) */
+.amount-focus-wrap {
+ border-color: $amber;
}
.degree-inline {
@@ -796,20 +831,27 @@ export default {
}
.amount-input {
- font-size: $fs-3xl;
+ font-size: $fs-2xl;
font-weight: $fw-black;
color: $text-primary;
flex: 1;
background: transparent;
border: none;
padding: 0;
- height: 80rpx;
- line-height: 80rpx;
+ height: 72rpx;
+ line-height: 72rpx;
+}
+
+.amount-placeholder {
+ color: $text-tertiary;
+ font-size: $fs-base;
+ font-weight: $fw-normal;
}
.amount-unit {
font-size: $fs-md;
color: $text-secondary;
+ flex-shrink: 0;
}
.unit-switcher {
@@ -849,11 +891,13 @@ export default {
}
.degree-input {
- width: 100rpx;
+ width: 120rpx;
text-align: center;
background: $bg-elevated;
border-radius: $radius-md;
+ border: 2rpx solid var(--border-faint, rgba(255, 255, 255, 0.12));
padding: $sp-sm;
+ height: 72rpx;
color: $text-primary;
font-size: $fs-lg;
}