consumerm模块完成度90%,完善消费者和商家端数据库表,商品、聊天、订单数据对接好了supabase,和商家端对接了聊天功能,安卓端编译通过了css样式,剩余几个页面在处理函数规范问题
This commit is contained in:
@@ -6,13 +6,13 @@
|
||||
|
||||
<view class="shop-list" v-if="shops.length > 0">
|
||||
<view class="shop-item" v-for="shop in shops" :key="shop.id" @click="goToShop(shop)">
|
||||
<image :src="shop.shop_logo || '/static/default-shop.png'" class="shop-logo" mode="aspectFill" />
|
||||
<image :src="shop.shop_logo != null ? shop.shop_logo : '/static/default-shop.png'" class="shop-logo" mode="aspectFill" />
|
||||
<view class="shop-info">
|
||||
<text class="shop-name">{{ shop.shop_name }}</text>
|
||||
<text class="shop-desc">{{ shop.description || '暂无介绍' }}</text>
|
||||
<text class="shop-desc">{{ shop.description != null ? shop.description : '暂无介绍' }}</text>
|
||||
<view class="shop-meta">
|
||||
<text class="rating">⭐ {{ shop.rating_avg || 5.0 }}</text>
|
||||
<text class="sales">销量: {{ shop.total_sales || 0 }}</text>
|
||||
<text class="rating shop-meta-text">⭐ {{ shop.rating_avg }}</text>
|
||||
<text class="sales shop-meta-text">销量: {{ shop.total_sales }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<button class="unfollow-btn" @click.stop="unfollow(shop)">已关注</button>
|
||||
@@ -72,8 +72,8 @@ const loadFollowedShops = async () => {
|
||||
shop_name: shopData['shop_name'] as string,
|
||||
shop_logo: shopData['shop_logo'] as string | null,
|
||||
description: shopData['description'] as string | null,
|
||||
rating_avg: (shopData['rating_avg'] || 5.0) as number,
|
||||
total_sales: (shopData['total_sales'] || 0) as number
|
||||
rating_avg: (shopData['rating_avg'] != null) ? (shopData['rating_avg'] as number) : 5.0,
|
||||
total_sales: (shopData['total_sales'] != null) ? (shopData['total_sales'] as number) : 0
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -111,8 +111,9 @@ const goToShop = (shop: FollowedShop) => {
|
||||
// Since shop-detail handles both, passing shop.id (which is ml_shops.id) is fine?
|
||||
// Wait, shop-detail logic: 1. getShopByMerchantId(id) [tries merchant_id then id].
|
||||
// So passing shop.id is safer if merchant_id is not unique or confusing.
|
||||
const targetId = shop.merchant_id != '' ? shop.merchant_id : shop.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/shop-detail?merchantId=${shop.merchant_id || shop.id}`
|
||||
url: `/pages/mall/consumer/shop-detail?merchantId=${targetId}`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ const goHome = () => {
|
||||
.followed-shops-page {
|
||||
padding: 15px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
flex: 1;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 15px;
|
||||
@@ -137,7 +138,6 @@ const goHome = () => {
|
||||
.shop-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.shop-item {
|
||||
background-color: #fff;
|
||||
@@ -146,6 +146,10 @@ const goHome = () => {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.shop-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.shop-logo {
|
||||
width: 50px;
|
||||
@@ -178,7 +182,9 @@ const goHome = () => {
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.shop-meta-text {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.unfollow-btn {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<view v-else class="list">
|
||||
<view class="card" v-for="s in items" :key="s['id']">
|
||||
<view class="row between">
|
||||
<text class="name">{{ s['plan']?.['name'] || '订阅' }}</text>
|
||||
<text class="status" :class="'st-' + (s['status'] || 'active')">{{ statusText(s['status'] as string) }}</text>
|
||||
<text class="name">{{ s['plan']?.['name'] != null ? s['plan']?.['name'] : '订阅' }}</text>
|
||||
<text class="status" :class="'st-' + (s['status'] != null ? s['status'] : 'active')">{{ statusText(s['status'] as string) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">周期</text>
|
||||
@@ -33,7 +33,7 @@
|
||||
<view class="actions">
|
||||
<label class="toggle">
|
||||
<switch :checked="!!s['auto_renew']" @change="e => toggleAutoRenew(s, e.detail.value as boolean)" />
|
||||
<text>自动续费</text>
|
||||
<text class="toggle-text">自动续费</text>
|
||||
</label>
|
||||
<button class="danger" @click="cancelAtPeriodEnd(s)" :disabled="(s['status'] as string) !== 'active'">到期取消</button>
|
||||
</view>
|
||||
@@ -59,7 +59,8 @@ const fmt = (s: string | null): string => {
|
||||
|
||||
const statusText = (st: string): string => {
|
||||
const map: UTSJSONObject = { trial: '试用', active: '生效', past_due: '逾期', canceled: '已取消', expired: '已过期' } as UTSJSONObject
|
||||
return (map[st] as string) || st
|
||||
const val = map[st] as string | null
|
||||
return val != null ? val : st
|
||||
}
|
||||
|
||||
const loadSubs = async () => {
|
||||
@@ -133,14 +134,16 @@ onMounted(loadSubs)
|
||||
<style scoped>
|
||||
.my-subs { padding: 12px; }
|
||||
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.title { font-size: 18px; font-weight: 600; }
|
||||
.title { font-size: 18px; font-weight: 700; }
|
||||
.ghost { background: #fff; border: 1px solid #ddd; color: #333; border-radius: 6px; padding: 6px 10px; }
|
||||
.loading, .empty { padding: 24px; text-align: center; color: #888; }
|
||||
.list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.row { display: flex; gap: 8px; padding: 4px 0; }
|
||||
.list { display: flex; flex-direction: column; }
|
||||
.card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin-bottom: 12px; }
|
||||
.card:last-child { margin-bottom: 0; }
|
||||
.row { display: flex; padding: 4px 0; }
|
||||
.label { margin-right: 8px; }
|
||||
.between { justify-content: space-between; align-items: center; }
|
||||
.name { font-size: 16px; font-weight: 600; }
|
||||
.name { font-size: 16px; font-weight: 700; }
|
||||
.status { font-size: 12px; padding: 2px 8px; border-radius: 999px; background: #eee; color: #333; }
|
||||
.st-trial { background: #e6f7ff; color: #1677ff; }
|
||||
.st-active { background: #f6ffed; color: #52c41a; }
|
||||
@@ -149,6 +152,7 @@ onMounted(loadSubs)
|
||||
.label { color: #666; width: 80px; }
|
||||
.value { color: #111; flex: 1; }
|
||||
.actions { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; }
|
||||
.toggle { display: flex; align-items: center; gap: 6px; }
|
||||
.toggle { display: flex; align-items: center; }
|
||||
.toggle-text { margin-right: 6px; }
|
||||
.danger { background: #f5222d; color: #fff; border-radius: 6px; padding: 6px 10px; }
|
||||
</style>
|
||||
@@ -96,16 +96,17 @@ onMounted(loadPlan)
|
||||
<style scoped>
|
||||
.plan-detail { padding: 12px; }
|
||||
.header { margin-bottom: 8px; }
|
||||
.title { font-size: 18px; font-weight: 600; }
|
||||
.title { font-size: 18px; font-weight: 700; }
|
||||
.card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.name { font-size: 16px; font-weight: 600; }
|
||||
.name { font-size: 16px; font-weight: 700; }
|
||||
.desc { color: #666; margin: 6px 0; }
|
||||
.price-row { display: flex; align-items: baseline; gap: 4px; margin: 8px 0; }
|
||||
.price { font-size: 22px; color: #ff4d4f; font-weight: 700; }
|
||||
.period { color: #999; }
|
||||
.features { margin-top: 8px; }
|
||||
.f-title { font-weight: 600; margin-bottom: 4px; }
|
||||
.f-list { display: flex; flex-direction: column; gap: 2px; color: #444; }
|
||||
.price-row { display: flex; align-items: flex-end; margin: 8px 0; }
|
||||
.price { font-size: 22px; color: #ff4d4f; font-weight: 700; margin-right: 4px; }
|
||||
.period { color: #999; }
|
||||
.features { margin-top: 8px; }
|
||||
.f-title { font-weight: 700; margin-bottom: 4px; }
|
||||
.f-list { display: flex; flex-direction: column; color: #444; }
|
||||
.f-item { margin-bottom: 2px; }
|
||||
.actions { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
.primary { background: #3cc51f; color: #fff; border-radius: 6px; padding: 8px 12px; }
|
||||
.loading, .empty { padding: 24px; text-align: center; color: #888; }
|
||||
|
||||
@@ -92,18 +92,19 @@ onMounted(loadPlans)
|
||||
<style scoped>
|
||||
.sub-plan-list { padding: 12px; }
|
||||
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.title { font-size: 18px; font-weight: 600; }
|
||||
.plan-container { display: flex; flex-direction: column; gap: 12px; }
|
||||
.plan-card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.plan-header { display: flex; align-items: center; justify-content: space-between; }
|
||||
.plan-name { font-size: 16px; font-weight: 600; }
|
||||
.badge { font-size: 12px; color: #fff; background: #3cc51f; border-radius: 999px; padding: 2px 8px; }
|
||||
.plan-desc { color: #666; margin: 6px 0; line-height: 1.5; }
|
||||
.price-row { display: flex; align-items: baseline; gap: 4px; margin: 6px 0; }
|
||||
.price { font-size: 22px; color: #ff4d4f; font-weight: 700; }
|
||||
.period { color: #999; }
|
||||
.feature-list { color: #444; display: flex; flex-direction: column; gap: 2px; margin: 6px 0; }
|
||||
.feature-item { font-size: 12px; color: #555; }
|
||||
.title { font-size: 18px; font-weight: 700; }
|
||||
.plan-container { display: flex; flex-direction: column; }
|
||||
.plan-card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin-bottom: 12px; }
|
||||
.plan-card:last-child { margin-bottom: 0; }
|
||||
.plan-header { display: flex; align-items: center; justify-content: space-between; }
|
||||
.plan-name { font-size: 16px; font-weight: 700; color: #333; }
|
||||
.badge { font-size: 12px; color: #fff; background: #3cc51f; border-radius: 999px; padding: 2px 8px; }
|
||||
.plan-desc { color: #666; margin: 6px 0; line-height: 1.5; }
|
||||
.price-row { display: flex; align-items: flex-end; margin: 6px 0; }
|
||||
.price { font-size: 22px; color: #ff4d4f; font-weight: 700; margin-right: 4px; }
|
||||
.period { color: #999; }
|
||||
.feature-list { color: #444; display: flex; flex-direction: column; margin: 6px 0; }
|
||||
.feature-item { font-size: 12px; color: #555; margin-bottom: 2px; }
|
||||
.actions { display: flex; justify-content: flex-end; margin-top: 8px; }
|
||||
.primary { background: #3cc51f; color: #fff; border-radius: 6px; padding: 8px 12px; }
|
||||
.loading, .empty { padding: 24px; text-align: center; color: #888; }
|
||||
|
||||
@@ -91,7 +91,10 @@ const selPay = (v: number) => { payMethod.value = v }
|
||||
|
||||
// 获取当前用户ID(按现有store实现替换)
|
||||
const getCurrentUserId = (): string => {
|
||||
try { return (uni.getStorageSync('current_user_id') as string) || '' } catch { return '' }
|
||||
try {
|
||||
const u = uni.getStorageSync('current_user_id')
|
||||
return (u != null) ? (u as string) : ''
|
||||
} catch { return '' }
|
||||
}
|
||||
|
||||
const confirmSubscribe = async () => {
|
||||
@@ -135,7 +138,7 @@ const confirmSubscribe = async () => {
|
||||
uni.redirectTo({ url: '/pages/mall/consumer/profile' })
|
||||
}, 600)
|
||||
} else {
|
||||
uni.showToast({ title: ins?.error?.message || '订阅失败', icon: 'none' })
|
||||
uni.showToast({ title: ins?.error?.message ?? '订阅失败', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('订阅失败:', e)
|
||||
@@ -149,15 +152,17 @@ const confirmSubscribe = async () => {
|
||||
<style scoped>
|
||||
.subscribe-checkout { padding: 12px; }
|
||||
.header { margin-bottom: 8px; }
|
||||
.title { font-size: 18px; font-weight: 600; }
|
||||
.title { font-size: 18px; font-weight: 700; }
|
||||
.card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
|
||||
.row:last-child { border-bottom: none; }
|
||||
.label { color: #666; }
|
||||
.value { color: #111; font-weight: 600; }
|
||||
.section-title { margin-top: 12px; font-weight: 600; }
|
||||
.pay-methods { display: flex; flex-direction: column; gap: 8px; padding: 8px 0; }
|
||||
.pay-item { display: flex; align-items: center; gap: 8px; }
|
||||
.value { color: #111; font-weight: 700; }
|
||||
.section-title { margin-top: 12px; font-weight: 700; }
|
||||
.pay-methods { display: flex; flex-direction: column; padding: 8px 0; }
|
||||
.pay-item { display: flex; align-items: center; margin-bottom: 8px; }
|
||||
.pay-item:last-child { margin-bottom: 0; }
|
||||
.pay-icon { margin-right: 8px; }
|
||||
.actions { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
.primary { background: #3cc51f; color: #fff; border-radius: 6px; padding: 8px 12px; }
|
||||
.loading, .empty { padding: 24px; text-align: center; color: #888; }
|
||||
|
||||
Reference in New Issue
Block a user