consumer模块完成度95%,安卓端大部分页面能正常获取数据,页面样式显示基本正常,逐渐完善;消费者端的积分、余额、评价、优惠券等小模块正在完善
75
App.uvue
@@ -1,13 +1,88 @@
|
||||
<script lang="uts">
|
||||
import { setIsLoggedIn, setUserProfile, getCurrentUser } from '@/utils/store.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
// 自动登录凭据(开发测试用)
|
||||
const AUTO_LOGIN_EMAIL = 'test@mall.com'
|
||||
const AUTO_LOGIN_PASSWORD = 'Hf2152111'
|
||||
|
||||
export default {
|
||||
onLaunch: function () {
|
||||
console.log('App Launch')
|
||||
|
||||
// 尝试自动登录并跳转(使用 Promise 链)
|
||||
this.tryAutoLogin()
|
||||
},
|
||||
onShow: function () {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function () {
|
||||
console.log('App Hide')
|
||||
},
|
||||
methods: {
|
||||
tryAutoLogin: function(): void {
|
||||
// 检查是否已有有效会话
|
||||
const session = supa.getSession()
|
||||
if (session.user != null) {
|
||||
console.log('已有有效会话,跳过自动登录')
|
||||
setIsLoggedIn(true)
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
return
|
||||
}
|
||||
|
||||
// 检查本地存储的登录状态
|
||||
const savedUserId = uni.getStorageSync('user_id')
|
||||
if (savedUserId != null && savedUserId != '') {
|
||||
console.log('本地存储中有用户ID,尝试恢复会话')
|
||||
getCurrentUser().then((profile) => {
|
||||
if (profile != null) {
|
||||
console.log('会话恢复成功')
|
||||
setIsLoggedIn(true)
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
return
|
||||
}
|
||||
// 恢复失败,执行自动登录
|
||||
this.doAutoLogin()
|
||||
}).catch(() => {
|
||||
console.log('会话恢复失败,尝试自动登录')
|
||||
this.doAutoLogin()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行自动登录
|
||||
this.doAutoLogin()
|
||||
},
|
||||
doAutoLogin: function(): void {
|
||||
console.log('开始自动登录...')
|
||||
supa.signIn(AUTO_LOGIN_EMAIL, AUTO_LOGIN_PASSWORD).then((result) => {
|
||||
if (result.user != null) {
|
||||
console.log('自动登录成功')
|
||||
setIsLoggedIn(true)
|
||||
|
||||
// 保存用户ID到本地存储
|
||||
const uid = result.user.getString('id')
|
||||
if (uid != null) {
|
||||
uni.setStorageSync('user_id', uid)
|
||||
console.log('用户ID已保存:', uid)
|
||||
}
|
||||
|
||||
// 获取用户资料
|
||||
getCurrentUser().then(() => {
|
||||
console.log('获取用户资料成功')
|
||||
}).catch((e) => {
|
||||
console.log('获取用户资料失败(忽略)')
|
||||
})
|
||||
|
||||
// 直接跳转到首页
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
} else {
|
||||
console.log('自动登录失败,用户需要手动登录')
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.error('自动登录异常:', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
27
pages.json
@@ -87,19 +87,22 @@
|
||||
"path": "pages/mall/consumer/messages",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/cart",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车"
|
||||
"navigationBarTitleText": "购物车",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -911,32 +914,32 @@
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home-active.png"
|
||||
"iconPath": "static/tabbar/home.svg",
|
||||
"selectedIconPath": "static/tabbar/home-active.svg"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/category",
|
||||
"text": "分类",
|
||||
"iconPath": "static/tabbar/category.png",
|
||||
"selectedIconPath": "static/tabbar/category-active.png"
|
||||
"iconPath": "static/tabbar/category.svg",
|
||||
"selectedIconPath": "static/tabbar/category-active.svg"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/messages",
|
||||
"text": "消息",
|
||||
"iconPath": "static/tabbar/messages.png",
|
||||
"selectedIconPath": "static/tabbar/messages-active.png"
|
||||
"iconPath": "static/tabbar/messages.svg",
|
||||
"selectedIconPath": "static/tabbar/messages-active.svg"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/cart",
|
||||
"text": "购物车",
|
||||
"iconPath": "static/tabbar/cart.png",
|
||||
"selectedIconPath": "static/tabbar/cart-active.png"
|
||||
"iconPath": "static/tabbar/cart.svg",
|
||||
"selectedIconPath": "static/tabbar/cart-active.svg"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/profile",
|
||||
"text": "我的",
|
||||
"iconPath": "static/tabbar/profile.png",
|
||||
"selectedIconPath": "static/tabbar/profile-active.png"
|
||||
"iconPath": "static/tabbar/profile.svg",
|
||||
"selectedIconPath": "static/tabbar/profile-active.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -59,8 +59,10 @@ const loadAddresses = async () => {
|
||||
const supabaseAddresses = await supabaseService.getAddresses()
|
||||
|
||||
// 转换数据格式以匹配前端界面
|
||||
const transformedAddresses = supabaseAddresses.map((item: SupabaseUserAddress): Address => {
|
||||
return {
|
||||
const transformedAddresses: Address[] = []
|
||||
for (let i = 0; i < supabaseAddresses.length; i++) {
|
||||
const item = supabaseAddresses[i]
|
||||
const addr: Address = {
|
||||
id: item.id,
|
||||
name: item.recipient_name,
|
||||
phone: item.phone,
|
||||
@@ -69,9 +71,10 @@ const loadAddresses = async () => {
|
||||
district: item.district,
|
||||
detail: item.detail_address,
|
||||
isDefault: item.is_default,
|
||||
label: '' // Supabase表没有label字段
|
||||
label: ''
|
||||
} as Address
|
||||
})
|
||||
transformedAddresses.push(addr)
|
||||
}
|
||||
|
||||
addresses.value = transformedAddresses
|
||||
|
||||
|
||||
@@ -14,11 +14,17 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 导航栏占位符 - 已移除 -->
|
||||
<!-- <view class="navbar-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view> -->
|
||||
<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->
|
||||
<view class="navbar-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
|
||||
<!-- 购物车内容 -->
|
||||
<scroll-view scroll-y class="cart-content" :style="{ paddingTop: (statusBarHeight + 10) + 'px' }">
|
||||
<scroll-view
|
||||
:scroll-y="true"
|
||||
class="cart-content"
|
||||
:show-scrollbar="false"
|
||||
:enhanced="true"
|
||||
:bounces="true"
|
||||
>
|
||||
<!-- 空购物车 -->
|
||||
<view v-if="!loading && cartItems.length === 0" class="empty-cart">
|
||||
<text class="empty-icon">🛒</text>
|
||||
@@ -726,28 +732,28 @@ const goToCheckout = () => {
|
||||
|
||||
<style>
|
||||
.cart-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1; /* 使用 Flex 撑满 */
|
||||
height: 100%; /* 兼容性考虑,部分环境需要 */
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* 防止整页滚动 */
|
||||
}
|
||||
|
||||
/* 智能导航栏 */
|
||||
.smart-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.smart-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #4CAF50;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
padding: 0 16px;
|
||||
@@ -809,7 +815,9 @@ const goToCheckout = () => {
|
||||
/* 内容区 */
|
||||
.cart-content {
|
||||
flex: 1;
|
||||
height: 0; /* 配合 flex: 1 实现自适应滚动 */
|
||||
/* 必须设置 height: 0 或 overflow: hidden 可以在 flex 容器中正确收缩 */
|
||||
height: 0px;
|
||||
width: 100%;
|
||||
padding-bottom: 60px; /* 为底部结算栏留出空间 */
|
||||
}
|
||||
|
||||
|
||||
1269
pages/mall/consumer/category copy 2.uvue
Normal file
1246
pages/mall/consumer/category copy.uvue
Normal file
@@ -25,19 +25,24 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 导航栏占位 - 修复:只使用44px高度,因为search-bar的paddingTop已处理statusBarHeight -->
|
||||
<view class="navbar-placeholder" :style="{ height: '44px' }"></view>
|
||||
<!-- 导航栏占位 - 需要包含statusBarHeight + 搜索框高度44px -->
|
||||
<view class="navbar-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
|
||||
<!-- 分类内容区 -->
|
||||
<view class="category-content">
|
||||
<!-- 左侧一级分类 -->
|
||||
<scroll-view scroll-y class="primary-category">
|
||||
<scroll-view
|
||||
:scroll-y="true"
|
||||
class="primary-category"
|
||||
:scroll-top="scrollTop"
|
||||
:scroll-with-animation="true"
|
||||
>
|
||||
<view
|
||||
v-for="item in primaryCategories"
|
||||
:key="item.id"
|
||||
:class="['primary-item', { active: activePrimary === item.id }]"
|
||||
:class="['primary-item', { active: isPrimaryActive(item.id) }]"
|
||||
@click="selectPrimaryCategory(item.id)"
|
||||
:style="{ backgroundColor: activePrimary === item.id ? item.color : 'transparent' }"
|
||||
:style="{ backgroundColor: getPrimaryItemBgColor(item) }"
|
||||
>
|
||||
<text class="primary-icon">{{ item.icon }}</text>
|
||||
<text class="primary-name">{{ item.name }}</text>
|
||||
@@ -46,7 +51,7 @@
|
||||
|
||||
<!-- 右侧商品列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
:scroll-y="true"
|
||||
class="product-content"
|
||||
@scrolltolower="loadMore"
|
||||
:lower-threshold="50"
|
||||
@@ -57,6 +62,21 @@
|
||||
<text class="category-desc">{{ currentCategoryDesc }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 二级分类 -->
|
||||
<view v-if="subCategories.length > 0" class="sub-category-section">
|
||||
<view class="sub-category-list">
|
||||
<view
|
||||
v-for="sub in subCategories"
|
||||
:key="sub.id"
|
||||
:class="['sub-category-item', { active: isSubActive(sub.id) }]"
|
||||
@click="selectSubCategory(sub.id)"
|
||||
>
|
||||
<text class="sub-category-icon">{{ sub.icon }}</text>
|
||||
<text class="sub-category-name">{{ sub.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品网格 -->
|
||||
<view v-if="productList.length > 0" class="product-grid">
|
||||
<view
|
||||
@@ -98,6 +118,7 @@
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import supabaseService from '@/utils/supabaseService.uts'
|
||||
import type { Product } from '@/utils/supabaseService.uts'
|
||||
|
||||
@@ -111,15 +132,20 @@ type LocalCategory = {
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
const headerHeight = ref(44) // 默认头部高度
|
||||
const headerHeight = ref(44)
|
||||
const primaryCategories = ref<LocalCategory[]>([])
|
||||
const subCategories = ref<LocalCategory[]>([]) // 二级分类列表
|
||||
const productList = ref<Product[]>([])
|
||||
const activePrimary = ref<string>('')
|
||||
const activeSubCategory = ref<string>('') // 当前选中的二级分类
|
||||
const selectedParentId = ref<string>('') // 当前选中的一级分类ID(用于高亮显示)
|
||||
const cartCount = ref(3)
|
||||
const hasMore = ref(true)
|
||||
const hasLoadedFromParams = ref(false) // 标记是否已通过参数加载
|
||||
const hasLoadedFromParams = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const loading = ref(false)
|
||||
const scrollTop = ref(0)
|
||||
const pendingCategoryId = ref('') // 待处理的分类ID(从其他页面跳转过来时暂存)
|
||||
|
||||
// 获取当前分类信息
|
||||
const currentCategoryName = ref('')
|
||||
@@ -147,7 +173,7 @@ async function loadProducts(): Promise<void> {
|
||||
page: currentPage.value
|
||||
})
|
||||
|
||||
if (currentPage.value === 1) {
|
||||
if (currentPage.value == 1) {
|
||||
productList.value = response.data
|
||||
} else {
|
||||
productList.value.push(...response.data)
|
||||
@@ -155,75 +181,303 @@ async function loadProducts(): Promise<void> {
|
||||
|
||||
hasMore.value = response.hasmore
|
||||
|
||||
// 更新当前分类信息
|
||||
const category = primaryCategories.value.find((cat: LocalCategory): boolean => cat.id === activePrimary.value)
|
||||
if (category != null) {
|
||||
currentCategoryName.value = category.name
|
||||
currentCategoryDesc.value = category.description
|
||||
// 更新当前分类信息 - 先在一级分类中查找,再在二级分类中查找
|
||||
let foundCat: LocalCategory | null = null
|
||||
for (let i = 0; i < primaryCategories.value.length; i++) {
|
||||
if (primaryCategories.value[i].id == activePrimary.value) {
|
||||
foundCat = primaryCategories.value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (foundCat == null) {
|
||||
for (let i = 0; i < subCategories.value.length; i++) {
|
||||
if (subCategories.value[i].id == activePrimary.value) {
|
||||
foundCat = subCategories.value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (foundCat != null) {
|
||||
currentCategoryName.value = foundCat.name
|
||||
currentCategoryDesc.value = foundCat.description
|
||||
}
|
||||
|
||||
console.log('商品列表加载完成,当前总数量:', productList.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载商品数据失败:', error)
|
||||
if (currentPage.value === 1) {
|
||||
if (currentPage.value == 1) {
|
||||
productList.value = []
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载二级分类
|
||||
async function loadSubCategories(parentId: string): Promise<void> {
|
||||
console.log('加载二级分类,父级ID:', parentId)
|
||||
try {
|
||||
const subCats = await supabaseService.getSubCategories(parentId)
|
||||
console.log('获取到二级分类数量:', subCats.length)
|
||||
|
||||
const categories: LocalCategory[] = []
|
||||
for (let i = 0; i < subCats.length; i++) {
|
||||
const cat = subCats[i]
|
||||
categories.push({
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
icon: cat.icon,
|
||||
description: cat.description,
|
||||
color: cat.color
|
||||
})
|
||||
}
|
||||
subCategories.value = categories
|
||||
} catch (e) {
|
||||
console.error('加载二级分类失败:', e)
|
||||
subCategories.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 判断一级分类是否选中
|
||||
function isPrimaryActive(categoryId: string): boolean {
|
||||
return selectedParentId.value == categoryId
|
||||
}
|
||||
|
||||
// 判断二级分类是否选中
|
||||
function isSubActive(subCategoryId: string): boolean {
|
||||
return activeSubCategory.value == subCategoryId || activePrimary.value == subCategoryId
|
||||
}
|
||||
|
||||
// 获取一级分类的背景色
|
||||
function getPrimaryItemBgColor(item: LocalCategory): string {
|
||||
if (isPrimaryActive(item.id)) {
|
||||
return item.color
|
||||
}
|
||||
return 'transparent'
|
||||
}
|
||||
|
||||
// 选择二级分类
|
||||
async function selectSubCategory(subCategoryId: string): Promise<void> {
|
||||
console.log('选择二级分类:', subCategoryId)
|
||||
activeSubCategory.value = subCategoryId
|
||||
|
||||
// 使用二级分类ID加载商品
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
activePrimary.value = subCategoryId // 临时设置为二级分类ID用于加载商品
|
||||
await loadProducts()
|
||||
}
|
||||
|
||||
// 选择一级分类 - 必须在 loadCategories 之前定义
|
||||
// originalCategoryId: 可能是一级分类ID,也可能是二级分类ID
|
||||
async function selectPrimaryCategory(originalCategoryId: string): Promise<void> {
|
||||
console.log('=== selectPrimaryCategory函数开始执行 ===')
|
||||
console.log('传入的categoryId:', originalCategoryId)
|
||||
|
||||
if (originalCategoryId == '') {
|
||||
console.error('categoryId为空,尝试使用第一个分类')
|
||||
if (primaryCategories.value.length > 0) {
|
||||
originalCategoryId = primaryCategories.value[0].id
|
||||
} else {
|
||||
console.error('没有可用的分类')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查传入的是否是一级分类ID
|
||||
let targetParentId = originalCategoryId
|
||||
let targetSubId = ''
|
||||
console.log('当前一级分类列表长度:', primaryCategories.value.length)
|
||||
let foundInPrimary: LocalCategory | null = null
|
||||
for (let i = 0; i < primaryCategories.value.length; i++) {
|
||||
if (primaryCategories.value[i].id == originalCategoryId) {
|
||||
foundInPrimary = primaryCategories.value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
console.log('在一级分类中查找结果:', foundInPrimary != null)
|
||||
|
||||
if (foundInPrimary == null) {
|
||||
// 传入的可能是二级分类ID,需要查找其父级分类
|
||||
console.log('传入的ID不在一级分类中,可能是二级分类ID,尝试查找父级分类')
|
||||
|
||||
// 从服务器获取分类信息以确定父级
|
||||
try {
|
||||
const categoryInfo = await supabaseService.getCategoryById(originalCategoryId)
|
||||
if (categoryInfo != null && categoryInfo.parent_id != null && categoryInfo.parent_id != '') {
|
||||
console.log('找到父级分类ID:', categoryInfo.parent_id)
|
||||
|
||||
// 检查父级分类ID是否在一级分类列表中
|
||||
console.log('查找父级分类ID:', categoryInfo.parent_id)
|
||||
let parentInPrimary: LocalCategory | null = null
|
||||
for (let i = 0; i < primaryCategories.value.length; i++) {
|
||||
if (primaryCategories.value[i].id == categoryInfo.parent_id) {
|
||||
parentInPrimary = primaryCategories.value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
console.log('父级分类查找结果:', parentInPrimary != null)
|
||||
if (parentInPrimary != null) {
|
||||
console.log('父级分类在列表中找到:', parentInPrimary.name)
|
||||
targetParentId = categoryInfo.parent_id!
|
||||
targetSubId = originalCategoryId // 记住要选中的二级分类
|
||||
} else {
|
||||
console.log('父级分类不在列表中,使用第一个分类')
|
||||
// 打印当前列表中的所有分类ID
|
||||
for (let i = 0; i < primaryCategories.value.length; i++) {
|
||||
console.log('列表中的分类:', primaryCategories.value[i].id, primaryCategories.value[i].name)
|
||||
}
|
||||
if (primaryCategories.value.length > 0) {
|
||||
targetParentId = primaryCategories.value[0].id
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('未找到父级分类,使用第一个分类')
|
||||
if (primaryCategories.value.length > 0) {
|
||||
targetParentId = primaryCategories.value[0].id
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取分类信息失败:', e)
|
||||
if (primaryCategories.value.length > 0) {
|
||||
targetParentId = primaryCategories.value[0].id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('最终选中的一级分类ID:', targetParentId)
|
||||
console.log('需要选中的二级分类ID:', targetSubId)
|
||||
|
||||
// 设置一级分类高亮
|
||||
selectedParentId.value = targetParentId
|
||||
activePrimary.value = targetParentId
|
||||
|
||||
// 加载二级分类
|
||||
await loadSubCategories(targetParentId)
|
||||
|
||||
// 如果有要选中的二级分类
|
||||
if (targetSubId != '') {
|
||||
activeSubCategory.value = targetSubId
|
||||
} else {
|
||||
// 如果没有指定二级分类,但有二级分类列表,默认选中第一个
|
||||
if (subCategories.value.length > 0) {
|
||||
activeSubCategory.value = subCategories.value[0].id
|
||||
targetSubId = subCategories.value[0].id
|
||||
console.log('默认选中第一个二级分类:', subCategories.value[0].name)
|
||||
} else {
|
||||
activeSubCategory.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 自动滚动到选中位置
|
||||
let foundIndex = -1
|
||||
for (let i = 0; i < primaryCategories.value.length; i++) {
|
||||
if (primaryCategories.value[i].id == targetParentId) {
|
||||
foundIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (foundIndex != -1) {
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
|
||||
let itemHeight = 70
|
||||
if (systemInfo.windowWidth > 1025) {
|
||||
itemHeight = 80
|
||||
}
|
||||
|
||||
const scrollViewHeight = systemInfo.windowHeight - systemInfo.statusBarHeight - 44
|
||||
const targetScrollTop = (foundIndex * itemHeight) - (scrollViewHeight / 2) + (itemHeight / 2)
|
||||
scrollTop.value = Math.max(0, targetScrollTop)
|
||||
console.log(`滚动左侧菜单: index=${foundIndex}, target=${scrollTop.value}`)
|
||||
}
|
||||
|
||||
// 查找分类信息
|
||||
let foundCategory: LocalCategory | null = null
|
||||
for (let i = 0; i < primaryCategories.value.length; i++) {
|
||||
if (primaryCategories.value[i].id == targetParentId) {
|
||||
foundCategory = primaryCategories.value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (foundCategory != null) {
|
||||
currentCategoryName.value = foundCategory.name
|
||||
currentCategoryDesc.value = foundCategory.description
|
||||
} else {
|
||||
console.log('分类信息未找到,使用第一个分类的信息')
|
||||
if (primaryCategories.value.length > 0) {
|
||||
const firstCategory = primaryCategories.value[0]
|
||||
currentCategoryName.value = firstCategory.name
|
||||
currentCategoryDesc.value = firstCategory.description
|
||||
}
|
||||
}
|
||||
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
|
||||
// 如果有选中的二级分类,使用二级分类ID加载商品;否则使用一级分类ID
|
||||
const categoryIdForProducts = (targetSubId != '') ? targetSubId : targetParentId
|
||||
activePrimary.value = categoryIdForProducts // 临时设置为要加载的分类ID
|
||||
await loadProducts()
|
||||
}
|
||||
|
||||
async function loadCategories(): Promise<void> {
|
||||
try {
|
||||
const categoriesData = await supabaseService.getCategories()
|
||||
console.log('加载分类数据成功,数量:', categoriesData.length)
|
||||
// 只获取一级分类(parent_id 为 null 的分类)
|
||||
const categoriesData = await supabaseService.getParentCategories()
|
||||
console.log('加载一级分类数据成功,数量:', categoriesData.length)
|
||||
|
||||
// 映射数据并添加默认颜色,防止选中时背景透明导致文字看不清
|
||||
// 过滤掉医药健康相关分类
|
||||
const categories: LocalCategory[] = []
|
||||
const rawList = categoriesData as any[]
|
||||
for (let i = 0; i < rawList.length; i++) {
|
||||
const raw = rawList[i]
|
||||
const catObj = (raw instanceof UTSJSONObject) ? (raw as UTSJSONObject) : (JSON.parse(JSON.stringify(raw)) as UTSJSONObject)
|
||||
const name = catObj.getString('name') ?? ''
|
||||
for (let i = 0; i < categoriesData.length; i++) {
|
||||
const cat = categoriesData[i]
|
||||
const name = cat.name
|
||||
console.log('一级分类:', cat.id, name)
|
||||
if (name.includes('医药') || name.includes('健康')) {
|
||||
console.log('过滤掉分类:', name)
|
||||
continue
|
||||
}
|
||||
const id = catObj.getString('id') ?? ''
|
||||
const description = catObj.getString('description') ?? ''
|
||||
const icon = catObj.getString('icon') ?? catObj.getString('icon_url') ?? '📦'
|
||||
const color = catObj.getString('color') ?? '#4CAF50'
|
||||
categories.push({
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
description,
|
||||
color
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
icon: cat.icon,
|
||||
description: cat.description,
|
||||
color: cat.color
|
||||
})
|
||||
}
|
||||
|
||||
console.log('最终一级分类列表数量:', categories.length)
|
||||
|
||||
if (categories.length > 0) {
|
||||
primaryCategories.value = categories
|
||||
// 如果没有通过参数设置分类,则设置默认选中一个分类
|
||||
if (activePrimary.value == '') {
|
||||
// 优先查找"厨具"相关的分类作为默认
|
||||
const defaultCategory = categories.find((c: LocalCategory): boolean => c.name.includes('厨具')) ?? categories[0]
|
||||
|
||||
activePrimary.value = defaultCategory.id
|
||||
console.log('设置默认分类为:', defaultCategory.name, 'ID:', defaultCategory.id)
|
||||
currentCategoryName.value = defaultCategory.name
|
||||
currentCategoryDesc.value = defaultCategory.description
|
||||
} else {
|
||||
// 如果已经选中了分类(可能来自Storage),更新显示信息
|
||||
const current = categories.find((c: LocalCategory): boolean => c.id == activePrimary.value)
|
||||
if (current != null) {
|
||||
currentCategoryName.value = current.name
|
||||
currentCategoryDesc.value = current.description
|
||||
// 如果此时没有商品列表(且没有正在加载),可能需要加载
|
||||
if (productList.value.length === 0 && !loading.value) {
|
||||
loadProducts()
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有待处理的分类ID(从其他页面跳转过来时暂存)
|
||||
if (pendingCategoryId.value != '') {
|
||||
console.log('发现待处理的分类ID:', pendingCategoryId.value)
|
||||
// 直接调用 selectPrimaryCategory,它会处理一级或二级分类ID
|
||||
const idToSelect = pendingCategoryId.value
|
||||
pendingCategoryId.value = '' // 清除暂存
|
||||
selectPrimaryCategory(idToSelect)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有预设的分类ID
|
||||
if (activePrimary.value != '') {
|
||||
console.log('有预设的分类ID:', activePrimary.value)
|
||||
const target = categories.find((c: LocalCategory): boolean => c.id == activePrimary.value)
|
||||
if (target != null) {
|
||||
console.log('找到目标分类,执行选中:', target.name)
|
||||
selectPrimaryCategory(activePrimary.value)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 默认选中第一个分类或"厨具"分类
|
||||
const defaultCategory = categories.find((c: LocalCategory): boolean => c.name.includes('厨具')) ?? categories[0]
|
||||
if (defaultCategory != null) {
|
||||
console.log('设置默认分类:', defaultCategory.name)
|
||||
selectPrimaryCategory(defaultCategory.id)
|
||||
}
|
||||
} else {
|
||||
console.warn('从Supabase获取的分类数据为空')
|
||||
@@ -241,71 +495,6 @@ function loadMore(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 选择一级分类
|
||||
async function selectPrimaryCategory(categoryId: string): Promise<void> {
|
||||
console.log('=== selectPrimaryCategory函数开始执行 ===')
|
||||
console.log('传入的categoryId:', categoryId)
|
||||
console.log('当前时间:', Date.now())
|
||||
|
||||
// 验证categoryId是否有效
|
||||
if (categoryId == '') {
|
||||
console.error('categoryId为空,尝试使用第一个分类')
|
||||
if (primaryCategories.value.length > 0) {
|
||||
categoryId = primaryCategories.value[0].id
|
||||
} else {
|
||||
console.error('没有可用的分类')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log('验证后的categoryId:', categoryId)
|
||||
console.log('当前activePrimary的值:', activePrimary.value)
|
||||
|
||||
// 更新活动分类
|
||||
activePrimary.value = categoryId
|
||||
console.log('更新后的activePrimary:', activePrimary.value)
|
||||
|
||||
// 更新当前分类信息
|
||||
const category = primaryCategories.value.find((cat: LocalCategory): boolean => cat.id === categoryId)
|
||||
if (category != null) {
|
||||
currentCategoryName.value = category.name
|
||||
currentCategoryDesc.value = category.description
|
||||
console.log('✅ 找到分类:', category.name, '描述:', category.description)
|
||||
} else {
|
||||
console.error('❌ 未找到分类ID:', categoryId, ',使用第一个分类')
|
||||
// 如果找不到对应的分类,使用第一个分类
|
||||
if (primaryCategories.value.length > 0) {
|
||||
const firstCategory = primaryCategories.value[0]
|
||||
currentCategoryName.value = firstCategory.name
|
||||
currentCategoryDesc.value = firstCategory.description
|
||||
activePrimary.value = firstCategory.id
|
||||
categoryId = firstCategory.id
|
||||
console.log('使用默认分类:', firstCategory.name)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('准备加载商品数据...')
|
||||
|
||||
// 重置分页并加载
|
||||
currentPage.value = 1
|
||||
hasMore.value = true
|
||||
await loadProducts()
|
||||
|
||||
console.log('✅ 加载商品数据成功')
|
||||
console.log('分类:', categoryId)
|
||||
console.log('商品数量:', productList.value.length)
|
||||
console.log('商品列表:', productList.value)
|
||||
|
||||
// 验证数据是否已正确更新
|
||||
console.log('数据更新验证:')
|
||||
console.log('activePrimary:', activePrimary.value)
|
||||
console.log('currentCategoryName:', currentCategoryName.value)
|
||||
console.log('currentCategoryDesc:', currentCategoryDesc.value)
|
||||
console.log('productList长度:', productList.value.length)
|
||||
|
||||
console.log('=== selectPrimaryCategory函数执行完成 ===')
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadCategories().then(() => {
|
||||
@@ -317,12 +506,42 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// 页面加载时处理参数 - 这是处理分类切换的主要入口
|
||||
// 页面显示时检查是否有参数传递过来
|
||||
onShow(() => {
|
||||
console.log('=== category页面onShow被调用 ===')
|
||||
|
||||
// 检查是否有存储的分类选择
|
||||
const savedCategoryId = uni.getStorageSync('selectedCategory')
|
||||
console.log('onShow检查Storage:', savedCategoryId)
|
||||
|
||||
if (savedCategoryId != null && savedCategoryId != '') {
|
||||
const targetId = savedCategoryId as string
|
||||
console.log('onShow发现存储的分类ID:', targetId)
|
||||
|
||||
// 清除存储,避免下次进入默认选中
|
||||
uni.removeStorageSync('selectedCategory')
|
||||
|
||||
// 确保分类数据已加载
|
||||
if (primaryCategories.value.length > 0) {
|
||||
// 如果当前未选中或选中的不是目标分类,则切换
|
||||
if (activePrimary.value != targetId) {
|
||||
console.log('onShow执行切换分类:', targetId)
|
||||
selectPrimaryCategory(targetId)
|
||||
} else {
|
||||
console.log('当前已是目标分类:', targetId)
|
||||
}
|
||||
} else {
|
||||
// 如果分类数据未加载,暂存ID,等待loadCategories完成后处理
|
||||
console.log('分类数据尚未加载,暂存ID等待加载')
|
||||
pendingCategoryId.value = targetId
|
||||
}
|
||||
}
|
||||
})
|
||||
// 页面加载时处理参数 - 这是处理分类切换的主要入口
|
||||
onLoad((options: any) => {
|
||||
console.log('=== category页面onLoad被调用 ===')
|
||||
console.log('页面加载时间:', Date.now())
|
||||
console.log('传入的options参数:', options)
|
||||
console.log('当前活动分类:', activePrimary.value)
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight
|
||||
console.log('=== category页面onLoad被调用 ===')
|
||||
|
||||
let categoryId = ''
|
||||
let categoryName = ''
|
||||
@@ -381,72 +600,6 @@ onLoad((options: any) => {
|
||||
console.log('=== category页面onLoad执行完成 ===')
|
||||
})
|
||||
|
||||
// 页面显示时也检查参数,确保从其他页面返回时能正确显示
|
||||
onShow(() => {
|
||||
console.log('=== category页面onShow被调用 ===')
|
||||
console.log('页面显示时间:', Date.now())
|
||||
console.log('当前活动分类:', activePrimary.value)
|
||||
|
||||
// 1. 优先检查 Storage 中的参数 (由首页传入)
|
||||
const storageCategoryId = (uni.getStorageSync('selectedCategory') as string) ?? ''
|
||||
if (storageCategoryId !== '') {
|
||||
console.log('✅ onShow中找到Storage分类参数:', storageCategoryId)
|
||||
hasLoadedFromParams.value = true
|
||||
// 清除Storage,防止下次误读
|
||||
uni.removeStorageSync('selectedCategory')
|
||||
|
||||
if (activePrimary.value !== storageCategoryId) {
|
||||
selectPrimaryCategory(storageCategoryId)
|
||||
}
|
||||
// 如果分类还没加载完,这里设置了ID,等loadCategories完成后会自动匹配信息
|
||||
return
|
||||
}
|
||||
|
||||
// 在onShow中,我们也需要检查是否有新的参数
|
||||
// 因为当从主页再次点击分类跳转过来时,可能不会触发onLoad
|
||||
// 而是触发onShow
|
||||
|
||||
// 获取当前页面实例和参数
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const rawPageOptions = currentPage.options ?? {}
|
||||
console.log('onShow中获取参数:', rawPageOptions)
|
||||
const pageOptObj = (rawPageOptions instanceof UTSJSONObject) ? (rawPageOptions as UTSJSONObject) : (JSON.parse(JSON.stringify(rawPageOptions)) as UTSJSONObject)
|
||||
|
||||
// 检查是否有分类参数
|
||||
const pageCategoryId = pageOptObj.getString('categoryId') ?? ''
|
||||
if (pageCategoryId !== '') {
|
||||
hasLoadedFromParams.value = true
|
||||
const categoryId = pageCategoryId
|
||||
const categoryName = pageOptObj.getString('name') ?? ''
|
||||
|
||||
console.log('✅ onShow中找到分类参数:', categoryId, categoryName)
|
||||
console.log('URL中的时间戳参数:', pageOptObj.getString('timestamp') ?? '')
|
||||
console.log('URL中的随机参数:', pageOptObj.getString('random') ?? '')
|
||||
|
||||
// 检查是否需要更新分类
|
||||
if (activePrimary.value !== categoryId) {
|
||||
console.log('当前分类:', activePrimary.value, '与目标分类:', categoryId, '不同,需要更新')
|
||||
console.log('准备调用selectPrimaryCategory函数...')
|
||||
selectPrimaryCategory(categoryId)
|
||||
} else {
|
||||
console.log('当前分类已经是目标分类,但可能用户想要刷新页面')
|
||||
console.log('当前分类:', activePrimary.value, '目标分类:', categoryId)
|
||||
// 即使分类相同,也重新加载数据,确保数据是最新的
|
||||
// 添加一个小的延迟,确保页面完全显示后再更新数据
|
||||
setTimeout(() => {
|
||||
selectPrimaryCategory(categoryId)
|
||||
}, 100)
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ onShow中未找到分类参数')
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== category页面onShow执行完成 ===')
|
||||
})
|
||||
|
||||
|
||||
// 添加到购物车
|
||||
async function addToCart(product: Product): Promise<void> {
|
||||
@@ -574,7 +727,7 @@ function onScan(): void {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
background-color: #4CAF50;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
}
|
||||
@@ -714,32 +867,31 @@ function onScan(): void {
|
||||
|
||||
/* 左侧一级分类 */
|
||||
.primary-category {
|
||||
width: 120px;
|
||||
height: 100%; /* 占满父容器高度 */
|
||||
margin-right: 20px; /* gap replacement */
|
||||
width: 63px;
|
||||
height: 100%;
|
||||
margin-right: 6px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 12px 0;
|
||||
border-radius: 8px;
|
||||
padding: 6px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
display: flex;
|
||||
flex-direction: column; /* 图标和文字垂直排列 */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px 8px;
|
||||
margin: 4px 8px;
|
||||
padding: 12px 4px;
|
||||
margin: 4px 6px;
|
||||
border-radius: 8px;
|
||||
/* cursor: pointer; removed for uniapp-x support */
|
||||
transition: all 0.2s ease;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.primary-item:hover {
|
||||
transform: translateY(-2px); /* 悬停时向上浮动 */
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.primary-item.active {
|
||||
@@ -748,24 +900,22 @@ function onScan(): void {
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 6px;
|
||||
margin-right: 0; /* 移除右边距 */
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
margin-right: 0;
|
||||
text-align: center;
|
||||
/* display: block; removed for uniapp-x support */
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
/* display: block; removed for uniapp-x support */
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
/* 右侧内容区 */
|
||||
.product-content {
|
||||
flex: 1;
|
||||
height: 100%; /* 占满父容器高度 */
|
||||
padding: 0; /* 移除内边距,交给内部元素 */
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
@@ -789,6 +939,56 @@ function onScan(): void {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 二级分类 */
|
||||
.sub-category-section {
|
||||
padding: 8px 8px;
|
||||
background: white;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sub-category-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sub-category-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 4px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.sub-category-item.active {
|
||||
background: #4CAF50;
|
||||
}
|
||||
|
||||
.sub-category-item.active .sub-category-name {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sub-category-icon {
|
||||
font-size: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.sub-category-name {
|
||||
font-size: 11px;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 商品网格 */
|
||||
.product-grid {
|
||||
display: flex;
|
||||
@@ -954,52 +1154,47 @@ function onScan(): void {
|
||||
}
|
||||
|
||||
.category-content {
|
||||
padding: 0 8px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
width: 80px; /* 减小宽度 */
|
||||
/* display: flex; 移除flex布局,保持默认 */
|
||||
/* flex-wrap: wrap; 移除换行 */
|
||||
padding: 8px 0;
|
||||
margin-right: 10px; /* Gap replacement */
|
||||
width: 56px;
|
||||
padding: 4px 0;
|
||||
margin-right: 4px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
/* width: calc(25% - 8px); 移除百分比宽度 */
|
||||
width: auto; /* 恢复自动宽度 */
|
||||
margin: 4px;
|
||||
padding: 8px 4px;
|
||||
/* text-align: center; 已经在通用样式中设置 */
|
||||
margin: 2px 2px;
|
||||
padding: 4px 2px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 4px;
|
||||
font-size: 20px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 11px;
|
||||
font-size: 9px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
/* grid-template-columns: repeat(2, 1fr); REMOVED */
|
||||
/* gap: 8px; REMOVED */
|
||||
padding: 0 4px 20px 4px; /* 增加底部内边距 */
|
||||
padding: 0 4px 20px 4px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
width: 48%; /* 2 columns for mobile */
|
||||
width: 48%;
|
||||
margin: 1%;
|
||||
}
|
||||
|
||||
/* 手机端商品卡片极简模式 - 仿照主页样式 */
|
||||
.product-spec,
|
||||
.manufacturer,
|
||||
.original-price,
|
||||
.sales-info,
|
||||
.product-badge { /* 分类页也隐藏角标,保持整洁 */
|
||||
.product-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1008,7 +1203,7 @@ function onScan(): void {
|
||||
}
|
||||
|
||||
.product-image {
|
||||
height: 100px; /* 由于分类页右侧空间更窄,图片高度设得更小一点 */
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
@@ -1018,10 +1213,7 @@ function onScan(): void {
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* display: -webkit-box; REMOVED for support */
|
||||
/* -webkit-line-clamp: 2; REMOVED for support */
|
||||
/* -webkit-box-orient: vertical; REMOVED for support */
|
||||
lines: 2; /* UTS text truncation */
|
||||
lines: 2;
|
||||
}
|
||||
|
||||
.price-section {
|
||||
@@ -1040,30 +1232,52 @@ function onScan(): void {
|
||||
}
|
||||
|
||||
.product-meta {
|
||||
display: none; /* 隐藏整个元数据行 */
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 0 12px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 8px 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
padding: 12px 4px 0 4px;
|
||||
padding: 8px 4px 0 4px;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.category-desc {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中屏手机/小平板 (415px-768px) */
|
||||
/* 中屏手机 (415px-768px) */
|
||||
@media screen and (min-width: 415px) and (max-width: 768px) {
|
||||
.search-container {
|
||||
padding: 0 16px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
width: 62px;
|
||||
padding: 6px 0;
|
||||
margin-right: 6px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
margin: 2px 2px;
|
||||
padding: 5px 2px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
font-size: 18px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 10px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
width: 46%;
|
||||
margin: 2%;
|
||||
@@ -1077,6 +1291,26 @@ function onScan(): void {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
width: 100px;
|
||||
padding: 10px 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
margin: 4px 4px;
|
||||
padding: 10px 6px;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
font-size: 22px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
width: 30%;
|
||||
margin: 1.5%;
|
||||
@@ -1097,7 +1331,7 @@ function onScan(): void {
|
||||
.primary-category {
|
||||
width: 160px;
|
||||
padding: 16px 0;
|
||||
margin-right: 30px; /* Gap replacement */
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
@@ -1132,14 +1366,9 @@ function onScan(): void {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
/* grid-template-columns: repeat(4, 1fr); REMOVED */
|
||||
/* gap: 24px; REMOVED */
|
||||
}
|
||||
|
||||
.product-card {
|
||||
border-radius: 14px;
|
||||
width: 22%; /* 4 columns */
|
||||
width: 22%;
|
||||
margin: 1.5%;
|
||||
}
|
||||
|
||||
@@ -1164,12 +1393,11 @@ function onScan(): void {
|
||||
@media screen and (min-width: 1400px) {
|
||||
.category-content {
|
||||
max-width: 1600px;
|
||||
/* gap: 40px; REMOVED */
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
margin-right: 40px; /* Gap replacement */
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
@@ -1209,14 +1437,9 @@ function onScan(): void {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
/* grid-template-columns: repeat(5, 1fr); REMOVED */
|
||||
/* gap: 28px; REMOVED */
|
||||
}
|
||||
|
||||
.product-card {
|
||||
border-radius: 16px;
|
||||
width: 17%; /* 5 columns */
|
||||
width: 17%;
|
||||
margin: 1.5%;
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,8 @@ function getCurrentTime(): string {
|
||||
|
||||
function setupRealtimeSubscription(): void {
|
||||
console.log('开始建立聊天实时订阅...')
|
||||
console.log('当前用户ID:', currentUserId.value, '商家ID:', merchantId.value)
|
||||
|
||||
const filter = ({
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
@@ -173,41 +175,80 @@ function setupRealtimeSubscription(): void {
|
||||
|
||||
realtimeChannel = supa.channel('public:ml_chat_messages')
|
||||
.on('postgres_changes', filter, (payload: any) => {
|
||||
console.log('=== 收到实时订阅回调 ===')
|
||||
const payloadObj = (payload instanceof UTSJSONObject) ? (payload as UTSJSONObject) : (JSON.parse(JSON.stringify(payload ?? {})) as UTSJSONObject)
|
||||
const newMsgAny = payloadObj.get('new')
|
||||
if (newMsgAny == null) return
|
||||
if (newMsgAny == null) {
|
||||
console.log('newMsgAny 为空,跳过')
|
||||
return
|
||||
}
|
||||
const newMsg = (newMsgAny instanceof UTSJSONObject) ? (newMsgAny as UTSJSONObject) : (JSON.parse(JSON.stringify(newMsgAny)) as UTSJSONObject)
|
||||
console.log('收到新消息:', newMsg)
|
||||
|
||||
const senderId = newMsg.getString('sender_id') ?? ''
|
||||
const receiverId = newMsg.getString('receiver_id') ?? ''
|
||||
const msgId = newMsg.getString('id') ?? ''
|
||||
const content = newMsg.getString('content') ?? ''
|
||||
|
||||
console.log('=== 消息详情 ===')
|
||||
console.log('消息ID:', msgId)
|
||||
console.log('发送者ID:', senderId)
|
||||
console.log('接收者ID:', receiverId)
|
||||
console.log('当前用户ID:', currentUserId.value)
|
||||
console.log('商家ID:', merchantId.value)
|
||||
console.log('消息内容:', content)
|
||||
|
||||
if (senderId === currentUserId.value) {
|
||||
// 检查消息是否已经在列表中(避免重复)
|
||||
for (let i = 0; i < messages.value.length; i++) {
|
||||
if (messages.value[i].id == msgId) {
|
||||
console.log('消息已存在,跳过')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 判断消息类型
|
||||
const isMyMessage = (senderId == currentUserId.value)
|
||||
const isForMe = (receiverId == currentUserId.value)
|
||||
const isRelatedToCurrentChat = (senderId == merchantId.value || receiverId == merchantId.value)
|
||||
|
||||
console.log('=== 条件判断 ===')
|
||||
console.log('isMyMessage:', isMyMessage)
|
||||
console.log('isForMe:', isForMe)
|
||||
console.log('isRelatedToCurrentChat:', isRelatedToCurrentChat)
|
||||
|
||||
// 如果消息与当前聊天无关,跳过
|
||||
if (!isRelatedToCurrentChat) {
|
||||
console.log('消息与当前聊天无关,跳过')
|
||||
return
|
||||
}
|
||||
|
||||
if (receiverId === currentUserId.value) {
|
||||
if (merchantId.value != '' && senderId !== merchantId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是自己发送的消息,或者是发给自己的消息,都显示
|
||||
if (isMyMessage || isForMe) {
|
||||
const createdAt = newMsg.getString('created_at') ?? new Date().toISOString()
|
||||
const date = new Date(createdAt)
|
||||
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
|
||||
const incomingMsg: UiChatMessage = {
|
||||
id: newMsg.getString('id') ?? Date.now().toString(),
|
||||
type: 'received',
|
||||
content: newMsg.getString('content') ?? '',
|
||||
id: msgId,
|
||||
type: isMyMessage ? 'sent' : 'received',
|
||||
content: content,
|
||||
time: timeStr
|
||||
}
|
||||
|
||||
console.log('=== 添加新消息到列表 ===')
|
||||
console.log('消息类型:', incomingMsg.type)
|
||||
console.log('消息内容:', incomingMsg.content)
|
||||
messages.value.push(incomingMsg)
|
||||
scrollToBottom()
|
||||
} else {
|
||||
console.log('条件不满足,不添加消息')
|
||||
}
|
||||
})
|
||||
.subscribe((status: string, err: any | null) => {
|
||||
console.log('订阅状态:', status)
|
||||
if (err != null) {
|
||||
console.log('订阅错误:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,21 +262,27 @@ async function loadChatHistory(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
messages.value = rawMsgs.reverse().map((m: ChatMessage): UiChatMessage => {
|
||||
// 使用 for 循环替代 map
|
||||
const uiMessages: UiChatMessage[] = []
|
||||
for (let i = rawMsgs.length - 1; i >= 0; i--) {
|
||||
const m = rawMsgs[i]
|
||||
const date = new Date(m.created_at ?? new Date().toISOString())
|
||||
const timeStr = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
|
||||
const sender = m.sender_id ?? ''
|
||||
const msgType = (currentUserId.value != '' && sender == currentUserId.value) ? 'sent' : 'received'
|
||||
const rawId = (m.id ?? '').toString()
|
||||
const msgId = rawId !== '' ? rawId : Date.now().toString()
|
||||
return {
|
||||
const msgId = rawId != '' ? rawId : Date.now().toString() + i.toString()
|
||||
|
||||
const uiMsg: UiChatMessage = {
|
||||
id: msgId,
|
||||
type: msgType,
|
||||
content: m.content ?? '',
|
||||
time: timeStr
|
||||
}
|
||||
})
|
||||
uiMessages.push(uiMsg)
|
||||
}
|
||||
messages.value = uiMessages
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottom()
|
||||
@@ -288,22 +335,13 @@ const sendMessage = async () => {
|
||||
const content = inputMessage.value.trim()
|
||||
if (content == '') return
|
||||
|
||||
// 添加发送的消息 (乐观更新)
|
||||
const newMessage: UiChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
type: 'sent',
|
||||
content: content,
|
||||
time: getCurrentTime()
|
||||
}
|
||||
|
||||
messages.value.push(newMessage)
|
||||
// 清空输入框
|
||||
inputMessage.value = ''
|
||||
|
||||
// 滚动到底部
|
||||
scrollToBottom()
|
||||
|
||||
// 发送到 Supabase
|
||||
if (merchantId.value != '') {
|
||||
// 不使用乐观更新,等待实时订阅推送
|
||||
// 这样可以确保多端同步
|
||||
const success = await supabaseService.sendMessage(merchantId.value, content)
|
||||
if (!success) {
|
||||
uni.showToast({
|
||||
|
||||
@@ -39,18 +39,22 @@ const loadCoupons = async () => {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
try {
|
||||
const userCoupons = await supabaseService.getUserCoupons(1)
|
||||
coupons.value = userCoupons.map((item: UserCoupon): Coupon => {
|
||||
const couponList: Coupon[] = []
|
||||
for (let i = 0; i < userCoupons.length; i++) {
|
||||
const item = userCoupons[i]
|
||||
const amountVal = item.amount ?? 0
|
||||
const expiryVal = (item.expire_at != null && item.expire_at !== '')
|
||||
? item.expire_at.substring(0, 10)
|
||||
: '长期有效'
|
||||
return {
|
||||
const coupon: Coupon = {
|
||||
id: item.id,
|
||||
title: (item.template_name != null && item.template_name !== '') ? item.template_name : '优惠券',
|
||||
amount: `¥${amountVal}`,
|
||||
expiry: expiryVal
|
||||
} as Coupon
|
||||
})
|
||||
couponList.push(coupon)
|
||||
}
|
||||
coupons.value = couponList
|
||||
} catch (e) {
|
||||
console.error('加载优惠券失败', e)
|
||||
coupons.value = []
|
||||
|
||||
235
pages/mall/consumer/doc/SMART_RECOMMENDATION.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# 智能推荐系统文档
|
||||
|
||||
## 一、系统概述
|
||||
|
||||
智能推荐系统基于用户行为数据,为用户提供个性化的商品推荐。系统综合分析用户的搜索历史、浏览历史,结合全站热销商品数据,生成智能推荐列表。
|
||||
|
||||
## 二、推荐逻辑架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 智能推荐系统 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ 用户搜索历史 │ │ 用户浏览历史 │ │ 全站热销商品 │ │
|
||||
│ │ (权重高) │ │ (权重中) │ │ (权重低) │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ 关键词匹配 │ │ 分类匹配 │ │ 销量排序 │ │
|
||||
│ │ 相关商品 │ │ 相似商品 │ │ 热门商品 │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
│ │ │ │ │
|
||||
│ └────────────────┼────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ 去重 & 合并结果 │ │
|
||||
│ └──────────┬──────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ 返回推荐商品列表 │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 三、权重分配策略
|
||||
|
||||
### 3.1 数据源权重
|
||||
|
||||
| 数据源 | 权重 | 说明 |
|
||||
|--------|------|------|
|
||||
| 用户搜索历史 | 50% | 用户主动搜索的关键词最能反映购买意向 |
|
||||
| 用户浏览历史 | 30% | 用户浏览过的商品分类反映兴趣偏好 |
|
||||
| 热销商品 | 20% | 全站热销商品作为兜底和补充 |
|
||||
|
||||
### 3.2 推荐优先级
|
||||
|
||||
```
|
||||
优先级 1: 用户搜索历史匹配的商品(最多占推荐列表的50%)
|
||||
优先级 2: 用户浏览过的分类下的商品(最多占推荐列表的30%)
|
||||
优先级 3: 全站热销商品(填充剩余位置)
|
||||
```
|
||||
|
||||
## 四、核心算法详解
|
||||
|
||||
### 4.1 智能推荐主流程
|
||||
|
||||
```typescript
|
||||
async getSmartRecommendations(limit: number): Promise<Product[]> {
|
||||
const products: Product[] = []
|
||||
const addedIds = new Set<string>() // 用于去重
|
||||
|
||||
// 步骤1: 根据用户搜索历史推荐(权重最高)
|
||||
const searchHistory = await getUserSearchHistory(5)
|
||||
if (searchHistory.length > 0) {
|
||||
const keywordProducts = await searchProductsByKeywords(searchHistory, limit)
|
||||
// 添加到结果列表,去重
|
||||
}
|
||||
|
||||
// 步骤2: 根据用户浏览历史推荐(权重中)
|
||||
if (products.length < limit) {
|
||||
const browseCategories = await getUserBrowseCategories(3)
|
||||
if (browseCategories.length > 0) {
|
||||
const categoryProducts = await getProductsByCategories(browseCategories, limit - products.length)
|
||||
// 添加到结果列表,去重
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤3: 补充热销商品(权重低)
|
||||
if (products.length < limit) {
|
||||
const hotProducts = await getHotProducts(limit - products.length + 5)
|
||||
// 添加到结果列表,去重
|
||||
}
|
||||
|
||||
return products.slice(0, limit)
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 搜索历史匹配算法
|
||||
|
||||
```typescript
|
||||
// 根据用户搜索关键词匹配商品
|
||||
async searchProductsByKeywords(keywords: string[], limit: number): Promise<Product[]> {
|
||||
// 1. 获取商品数据
|
||||
// 2. 遍历商品,检查名称和描述是否包含关键词
|
||||
// 3. 匹配成功的商品加入推荐列表
|
||||
|
||||
for (product in products) {
|
||||
for (keyword in keywords) {
|
||||
if (product.name.contains(keyword) || product.description.contains(keyword)) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 浏览历史分类匹配算法
|
||||
|
||||
```typescript
|
||||
// 根据用户浏览过的商品分类推荐
|
||||
async getProductsByCategories(categoryIds: string[], limit: number): Promise<Product[]> {
|
||||
// 1. 获取用户浏览过的商品分类ID列表
|
||||
// 2. 查询这些分类下的商品
|
||||
// 3. 按销量排序返回
|
||||
}
|
||||
```
|
||||
|
||||
## 五、热搜词系统
|
||||
|
||||
### 5.1 热搜词计算
|
||||
|
||||
```typescript
|
||||
async getHotKeywords(limit: number): Promise<string[]> {
|
||||
// 1. 获取最近100条搜索记录
|
||||
// 2. 统计每个关键词的出现频率
|
||||
// 3. 按频率降序排序
|
||||
// 4. 返回前N个高频关键词
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 热搜词展示规则
|
||||
|
||||
| 排名 | 样式 | 说明 |
|
||||
|------|------|------|
|
||||
| 1-3名 | 红色背景 | 热度最高,突出显示 |
|
||||
| 4-10名 | 灰色背景 | 普通热度 |
|
||||
|
||||
## 六、用户行为记录
|
||||
|
||||
### 6.1 搜索行为记录
|
||||
|
||||
```typescript
|
||||
// 在用户执行搜索时调用
|
||||
async recordSearch(keyword: string, resultCount: number): Promise<void> {
|
||||
// 记录字段:
|
||||
// - user_id: 用户ID(可选,支持匿名)
|
||||
// - keyword: 搜索关键词
|
||||
// - result_count: 搜索结果数量
|
||||
// - created_at: 搜索时间
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 浏览行为记录
|
||||
|
||||
```typescript
|
||||
// 在用户查看商品详情时调用
|
||||
async recordBrowse(productId: string, duration: number): Promise<void> {
|
||||
// 记录字段:
|
||||
// - user_id: 用户ID
|
||||
// - product_id: 商品ID
|
||||
// - browse_duration: 浏览时长(秒)
|
||||
// - created_at/updated_at: 时间戳
|
||||
}
|
||||
```
|
||||
|
||||
## 七、数据表结构
|
||||
|
||||
### 7.1 搜索历史表 (ml_search_history)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| user_id | UUID | 用户ID(可空) |
|
||||
| keyword | VARCHAR(200) | 搜索关键词 |
|
||||
| result_count | INTEGER | 搜索结果数量 |
|
||||
| created_at | TIMESTAMP | 搜索时间 |
|
||||
|
||||
### 7.2 浏览历史表 (ml_browse_history)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | UUID | 主键 |
|
||||
| user_id | UUID | 用户ID |
|
||||
| product_id | UUID | 商品ID |
|
||||
| browse_duration | INTEGER | 浏览时长(秒) |
|
||||
| created_at | TIMESTAMP | 首次浏览时间 |
|
||||
| updated_at | TIMESTAMP | 最近浏览时间 |
|
||||
|
||||
## 八、API接口列表
|
||||
|
||||
| API | 方法 | 说明 |
|
||||
|-----|------|------|
|
||||
| getSmartRecommendations | GET | 获取智能推荐商品 |
|
||||
| getHotKeywords | GET | 获取热搜词列表 |
|
||||
| getUserSearchHistory | GET | 获取用户搜索历史 |
|
||||
| getUserBrowseCategories | GET | 获取用户浏览分类 |
|
||||
| recordSearch | POST | 记录搜索行为 |
|
||||
| recordBrowse | POST | 记录浏览行为 |
|
||||
|
||||
## 九、性能优化建议
|
||||
|
||||
### 9.1 缓存策略
|
||||
|
||||
- 热搜词列表:缓存5分钟,减少数据库查询
|
||||
- 用户搜索历史:缓存10分钟
|
||||
- 推荐结果:缓存3分钟
|
||||
|
||||
### 9.2 数据量控制
|
||||
|
||||
- 搜索历史:每个用户最多保留100条
|
||||
- 浏览历史:每个用户最多保留50条
|
||||
- 热搜词统计:只统计最近30天的数据
|
||||
|
||||
## 十、扩展方向
|
||||
|
||||
### 10.1 短期优化
|
||||
|
||||
1. **时间衰减因子**:近期行为权重更高
|
||||
2. **购买行为加权**:已购买商品的相关商品权重提升
|
||||
3. **收藏行为加权**:收藏商品的相关商品权重提升
|
||||
|
||||
### 10.2 长期规划
|
||||
|
||||
1. **协同过滤**:基于相似用户的行为推荐
|
||||
2. **商品相似度**:基于商品属性计算相似度
|
||||
3. **机器学习**:使用推荐算法模型
|
||||
|
||||
---
|
||||
|
||||
*文档版本:1.0*
|
||||
*最后更新:2024年*
|
||||
3364
pages/mall/consumer/doc/UTS_ANDROID_GUIDE.md
Normal file
2334
pages/mall/consumer/doc/uts_final.txt
Normal file
2286
pages/mall/consumer/doc/uts_utf8.txt
Normal file
@@ -66,7 +66,9 @@ const addToCart = async (product: Product) => {
|
||||
const loadFavorites = async () => {
|
||||
const res = await supabaseService.getFavorites()
|
||||
|
||||
favorites.value = res.map((item: any): Product => {
|
||||
const productList: Product[] = []
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item = res[i]
|
||||
let prod: any | null = null
|
||||
let itemObj: UTSJSONObject | null = null
|
||||
|
||||
@@ -117,7 +119,7 @@ const loadFavorites = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const product: Product = {
|
||||
id: id,
|
||||
name: name,
|
||||
price: price,
|
||||
@@ -126,7 +128,9 @@ const loadFavorites = async () => {
|
||||
main_image_url: image,
|
||||
sale_count: sales
|
||||
} as Product
|
||||
})
|
||||
productList.push(product)
|
||||
}
|
||||
favorites.value = productList
|
||||
}
|
||||
|
||||
const goShopping = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="footprint-content" scroll-y @scrolltolower="loadMore">
|
||||
<scroll-view class="footprint-content" :scroll-y="true" @scrolltolower="loadMore">
|
||||
<view v-if="footprints.length === 0 && !isLoading" class="empty-footprints">
|
||||
<text class="empty-icon">👣</text>
|
||||
<text class="empty-text">暂无浏览记录</text>
|
||||
@@ -501,6 +501,7 @@ onMounted(() => {
|
||||
|
||||
.footprint-content {
|
||||
flex: 1;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.empty-footprints {
|
||||
|
||||
2666
pages/mall/consumer/index copy.uvue
Normal file
@@ -174,7 +174,7 @@
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 热销药品专区 -->
|
||||
<!-- 热销药品专区 -->
|
||||
<view class="hot-products">
|
||||
<view class="section-header">
|
||||
<view class="title-section">
|
||||
@@ -285,10 +285,12 @@ const hasMore = ref(true)
|
||||
const activeSort = ref('recommend') // 默认展示智能推荐
|
||||
const activeFilter = ref('recommend')
|
||||
const currentPage = ref(1)
|
||||
const priceAscending = ref(true) // 价格排序方向:true=升序,false=降序
|
||||
|
||||
// 数据源
|
||||
const hotProducts = ref<Product[]>([])
|
||||
const recommendedProducts = ref<Product[]>([])
|
||||
const hotKeywords = ref<string[]>([])
|
||||
|
||||
// 屏幕尺寸检测
|
||||
const isMobile = ref(false)
|
||||
@@ -367,7 +369,10 @@ const loadCategories = async (): Promise<void> => {
|
||||
// 获取二级分类数据
|
||||
const loadSubCategories = async (parentId: string): Promise<void> => {
|
||||
try {
|
||||
console.log('[loadSubCategories] 开始加载二级分类, parentId:', parentId)
|
||||
const subData = await supabaseService.getSubCategories(parentId)
|
||||
console.log('[loadSubCategories] 获取到二级分类数量:', subData.length)
|
||||
console.log('[loadSubCategories] 二级分类数据:', JSON.stringify(subData))
|
||||
subCategories.value = subData
|
||||
} catch (error) {
|
||||
console.error('加载子分类数据失败:', error)
|
||||
@@ -377,18 +382,31 @@ const loadSubCategories = async (parentId: string): Promise<void> => {
|
||||
|
||||
// 点击一级分类
|
||||
const onParentCategoryClick = async (category: Category): Promise<void> => {
|
||||
// 如果已经选中,则切换显示/隐藏二级分类
|
||||
if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) {
|
||||
console.log('[onParentCategoryClick] 点击一级分类:', category.name, 'id:', category.id)
|
||||
|
||||
// 如果已经选中,则切换显示/隐藏二级分类
|
||||
if (selectedParentCategory.value != null && selectedParentCategory.value.id === category.id) {
|
||||
console.log('[onParentCategoryClick] 切换显示状态')
|
||||
showSubCategories.value = !showSubCategories.value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 选中新的分类
|
||||
selectedParentCategory.value = category
|
||||
showSubCategories.value = true
|
||||
// 选中新的分类
|
||||
selectedParentCategory.value = category
|
||||
showSubCategories.value = true
|
||||
console.log('[onParentCategoryClick] showSubCategories 设置为 true')
|
||||
|
||||
// 加载二级分类
|
||||
await loadSubCategories(category.id)
|
||||
// 加载二级分类
|
||||
await loadSubCategories(category.id)
|
||||
|
||||
// 如果没有二级分类,直接跳转到分类页
|
||||
if (subCategories.value.length == 0) {
|
||||
console.log('[onParentCategoryClick] 没有二级分类,直接跳转到分类页')
|
||||
uni.setStorageSync('selectedCategory', category.id)
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/category'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 点击二级分类
|
||||
@@ -453,32 +471,28 @@ const doLoadHotProducts = async (targetLimit: number, resolve: (value: void) =>
|
||||
|
||||
switch (activeSort.value) {
|
||||
case 'sales':
|
||||
console.log('调用 getHotProducts')
|
||||
products = await supabaseService.getHotProducts(limit)
|
||||
console.log('调用 getProductsBySales')
|
||||
products = await supabaseService.getProductsBySales(limit)
|
||||
break
|
||||
case 'price':
|
||||
console.log('调用 getProductsByPrice')
|
||||
// 按价格升序(从低到高)
|
||||
products = await supabaseService.getProductsByPrice(limit, true)
|
||||
console.log('调用 getProductsByPrice, 升序:', priceAscending.value)
|
||||
products = await supabaseService.getProductsByPrice(limit, priceAscending.value)
|
||||
break
|
||||
case 'new':
|
||||
console.log('调用 getProductsByNewest')
|
||||
// 按创建时间,最新的在前
|
||||
products = await supabaseService.getProductsByNewest(limit)
|
||||
break
|
||||
case 'recommend':
|
||||
console.log('调用 getRecommendedProducts')
|
||||
// 推荐商品(带badge的商品)
|
||||
products = await supabaseService.getRecommendedProducts(limit)
|
||||
console.log('调用 getSmartRecommendations')
|
||||
products = await supabaseService.getSmartRecommendations(limit)
|
||||
break
|
||||
case 'discount':
|
||||
console.log('调用 getDiscountProducts')
|
||||
// 特价商品(badge为'特价')
|
||||
products = await supabaseService.getDiscountProducts(limit)
|
||||
break
|
||||
default:
|
||||
console.log('调用默认 getHotProducts')
|
||||
products = await supabaseService.getHotProducts(limit)
|
||||
console.log('调用默认 getProductsBySales')
|
||||
products = await supabaseService.getProductsBySales(limit)
|
||||
}
|
||||
|
||||
console.log('加载到的商品数量:', products.length)
|
||||
@@ -516,6 +530,25 @@ function loadRecommendedProducts(limit: number): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
// 加载热搜词
|
||||
const loadHotKeywords = async (): Promise<void> => {
|
||||
try {
|
||||
const keywords = await supabaseService.getHotKeywords(10)
|
||||
hotKeywords.value = keywords
|
||||
console.log('加载热搜词:', keywords.length, '个')
|
||||
} catch (error) {
|
||||
console.error('加载热搜词失败:', error)
|
||||
hotKeywords.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 点击热搜词进行搜索
|
||||
const searchByKeyword = (keyword: string): void => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(keyword)}`
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
// 首先确保用户资料已加载
|
||||
@@ -527,6 +560,7 @@ const initData = async () => {
|
||||
}
|
||||
await loadCategories()
|
||||
await loadBrands()
|
||||
await loadHotKeywords()
|
||||
await loadHotProducts(defaultLoadLimit)
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
}
|
||||
@@ -719,7 +753,17 @@ const switchBrand = (brand: Brand) => {
|
||||
|
||||
// 切换排序
|
||||
const switchSort = (sortId: string) => {
|
||||
activeSort.value = sortId
|
||||
// 如果点击的是价格排序,切换升序/降序
|
||||
if (sortId === 'price' && activeSort.value === 'price') {
|
||||
priceAscending.value = !priceAscending.value
|
||||
console.log('切换价格排序方向,升序:', priceAscending.value)
|
||||
} else {
|
||||
// 切换到其他排序时,重置价格排序为升序
|
||||
if (sortId !== 'price') {
|
||||
priceAscending.value = true
|
||||
}
|
||||
activeSort.value = sortId
|
||||
}
|
||||
hasMore.value = true // 重置加载更多状态
|
||||
// 重新加载热销商品,排序由 Supabase 服务处理
|
||||
loadHotProducts(defaultLoadLimit)
|
||||
@@ -776,26 +820,45 @@ const loadMore = async () => {
|
||||
try {
|
||||
// 获取当前热销商品的数量
|
||||
const currentCount = hotProducts.value.length
|
||||
const nextLimit = currentCount + 6
|
||||
const nextPage = Math.floor(currentCount / 6) + 1
|
||||
const additionalLimit = 6
|
||||
|
||||
console.log('开始加载更多,当前数量:', currentCount, '目标数量:', nextLimit)
|
||||
console.log('开始加载更多,当前数量:', currentCount, '页码:', nextPage)
|
||||
|
||||
// 加载更多热销商品
|
||||
await loadHotProducts(nextLimit)
|
||||
// 加载更多商品
|
||||
let newProducts: Product[] = []
|
||||
switch (activeSort.value) {
|
||||
case 'sales':
|
||||
newProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit)
|
||||
break
|
||||
case 'price':
|
||||
newProducts = await supabaseService.getProductsByPrice(currentCount + additionalLimit, priceAscending.value)
|
||||
break
|
||||
case 'new':
|
||||
newProducts = await supabaseService.getProductsByNewest(currentCount + additionalLimit)
|
||||
break
|
||||
case 'recommend':
|
||||
newProducts = await supabaseService.getSmartRecommendations(currentCount + additionalLimit)
|
||||
break
|
||||
case 'discount':
|
||||
newProducts = await supabaseService.getDiscountProducts(currentCount + additionalLimit)
|
||||
break
|
||||
default:
|
||||
newProducts = await supabaseService.getProductsBySales(currentCount + additionalLimit)
|
||||
}
|
||||
|
||||
console.log('加载到的新商品数量:', newProducts.length)
|
||||
|
||||
// 检查是否还有更多数据
|
||||
if (hotProducts.value.length === currentCount) {
|
||||
if (newProducts.length <= currentCount) {
|
||||
hasMore.value = false
|
||||
uni.showToast({
|
||||
title: '没有更多了',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
// 还有数据,或者是刚加载了一批
|
||||
/* uni.showToast({
|
||||
title: '加载完成',
|
||||
icon: 'success'
|
||||
}) */
|
||||
// 更新商品列表
|
||||
hotProducts.value = newProducts
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载更多失败:', error)
|
||||
@@ -963,14 +1026,10 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
background-color: #4CAF50;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15); /* 调整为与分类页一致 */
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
/* will-change: transform, opacity; removed for uniapp-x support */
|
||||
/* pointer-events: auto; */
|
||||
/* backface-visibility: hidden; */
|
||||
/* -webkit-backface-visibility: hidden; */
|
||||
}
|
||||
|
||||
/* 导航栏搜索框容器内边距调整 */
|
||||
@@ -1320,14 +1379,12 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
||||
|
||||
.card-name {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
text-align: center;
|
||||
lines: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
@@ -1524,6 +1581,59 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 热搜词区域 */
|
||||
.hot-keywords-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.keywords-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.keyword-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.keyword-item:hover {
|
||||
background: #fff0f0;
|
||||
}
|
||||
|
||||
.keyword-rank {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
background: #eee;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.keyword-rank.top-three {
|
||||
background: #ff4757;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.keyword-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 热销药品 */
|
||||
.hot-products {
|
||||
background: white;
|
||||
@@ -2076,45 +2186,43 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
||||
}
|
||||
|
||||
.category-grid {
|
||||
/* grid-template-columns: repeat(5, 1fr); removed for uniapp-x support */
|
||||
/* gap: 8px; removed */
|
||||
margin: 0 -1%;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.category-grid .category-card {
|
||||
width: 18%; /* 5 cols : 20 - 2 */
|
||||
margin: 0 1% 8px 1%;
|
||||
padding: 8px 0;
|
||||
background: transparent; /* 移除卡片背景 */
|
||||
box-shadow: none; /* 移除阴影 */
|
||||
border: none; /* 移除边框 */
|
||||
width: 18%;
|
||||
margin: 0 1% 6px 1%;
|
||||
padding: 4px 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: none; /* 移动端移除悬停效果 */
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 44px; /* 减小图标尺寸 */
|
||||
height: 44px;
|
||||
border-radius: 22px;
|
||||
margin-bottom: 6px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.card-icon-text {
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
font-size: 11px; /* 减小文字大小 */
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
display: none; /* 手机端隐藏描述文字,保持界面整洁 */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.services-grid .service-card {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 导航栏占位符 -->
|
||||
<view class="navbar-placeholder" :style="{ height: (statusBarHeight + 10) + 'px' }"></view>
|
||||
<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->
|
||||
<view class="navbar-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
|
||||
<!-- 消息分类标签 - 固定在顶部,方便随时切换 -->
|
||||
<view class="tabs-container">
|
||||
@@ -350,9 +350,15 @@ const loadMessages = async () => {
|
||||
promoMessages.length = 0
|
||||
|
||||
// 1. 获取通知 (系统、订单、优惠)
|
||||
console.log('[loadMessages] 开始获取通知...')
|
||||
const notes = await supabaseService.getUserNotifications()
|
||||
console.log('[loadMessages] 获取到通知数量:', notes.length)
|
||||
|
||||
notes.forEach((note: Notification) => {
|
||||
// 使用 for 循环替代 forEach
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
const note = notes[i]
|
||||
console.log('[loadMessages] 通知类型:', note.type, '标题:', note.title)
|
||||
|
||||
const item: MessageItem = {
|
||||
id: note.id,
|
||||
title: note.title,
|
||||
@@ -361,7 +367,7 @@ const loadMessages = async () => {
|
||||
read: note.is_read,
|
||||
type: note.type,
|
||||
avatar: note.icon_url,
|
||||
important: note.type === 'system',
|
||||
important: note.type == 'system',
|
||||
coupon: '点击查看',
|
||||
expiry: '',
|
||||
claimed: false,
|
||||
@@ -378,15 +384,17 @@ const loadMessages = async () => {
|
||||
active: false
|
||||
}
|
||||
|
||||
if (note.type === 'system') {
|
||||
if (note.type == 'system') {
|
||||
systemMessages.push(item)
|
||||
} else if (note.type === 'order') {
|
||||
} else if (note.type == 'order') {
|
||||
orderMessages.push(item)
|
||||
} else if (note.type === 'promotion') {
|
||||
} else if (note.type == 'promotion') {
|
||||
item.type = 'promo'
|
||||
promoMessages.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log('[loadMessages] 系统消息:', systemMessages.length, '订单消息:', orderMessages.length, '优惠消息:', promoMessages.length)
|
||||
|
||||
// 2. 获取客服消息 (Chat)
|
||||
const rooms = await supabaseService.getChatRooms()
|
||||
@@ -641,15 +649,13 @@ const onRefresh = () => {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
background-color: #4CAF50;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
/* height: 50px; 移除固定高度,由内容决定 */
|
||||
display: flex;
|
||||
flex-direction: row; /* 显式设置行方向 */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0; /* 防止被压缩 */
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
|
||||
@@ -279,21 +279,63 @@ const loadOrderDetail = async () => {
|
||||
try {
|
||||
const data = await supabaseService.getOrderDetail(orderId.value)
|
||||
if (data != null) {
|
||||
const dataObj = data as Record<string, any>
|
||||
// 使用 JSON.parse(JSON.stringify()) 转换数据
|
||||
const dataObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject
|
||||
order.value = data as OrderType
|
||||
const items = dataObj['ml_order_items']
|
||||
orderItems.value = items != null ? (items as OrderItemType[]) : []
|
||||
deliveryAddress.value = dataObj['shipping_address'] as AddressType
|
||||
|
||||
// 解析订单商品
|
||||
const itemsRaw = dataObj.get('ml_order_items')
|
||||
if (itemsRaw != null && Array.isArray(itemsRaw)) {
|
||||
const items = itemsRaw as any[]
|
||||
orderItems.value = []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||||
|
||||
const orderItem: OrderItemType = {
|
||||
id: (itemObj.get('id') ?? '') as string,
|
||||
product_id: (itemObj.get('product_id') ?? '') as string,
|
||||
product_name: (itemObj.get('product_name') ?? '') as string,
|
||||
price: (itemObj.get('price') ?? 0) as number,
|
||||
quantity: (itemObj.get('quantity') ?? 1) as number,
|
||||
image_url: (itemObj.get('image_url') ?? '') as string,
|
||||
specifications: (itemObj.get('specifications') ?? '') as string
|
||||
}
|
||||
orderItems.value.push(orderItem)
|
||||
}
|
||||
}
|
||||
|
||||
// 解析收货地址
|
||||
const addressRaw = dataObj.get('shipping_address')
|
||||
if (addressRaw != null) {
|
||||
const addressObj = JSON.parse(JSON.stringify(addressRaw)) as UTSJSONObject
|
||||
const province = (addressObj.get('province') ?? '') as string
|
||||
const city = (addressObj.get('city') ?? '') as string
|
||||
const district = (addressObj.get('district') ?? '') as string
|
||||
const detail = (addressObj.get('detail') ?? '') as string
|
||||
deliveryAddress.value = {
|
||||
name: (addressObj.get('name') ?? '') as string,
|
||||
phone: (addressObj.get('phone') ?? '') as string,
|
||||
province: province,
|
||||
city: city,
|
||||
district: district,
|
||||
detail: detail,
|
||||
address: province + city + district + detail
|
||||
} as AddressType
|
||||
}
|
||||
|
||||
// 获取店铺信息
|
||||
const merchantId = dataObj['merchant_id'] as string
|
||||
const merchantId = dataObj.get('merchant_id') as string
|
||||
if (merchantId != null && merchantId != '') {
|
||||
loadShopInfo(merchantId)
|
||||
}
|
||||
|
||||
console.log('订单详情加载成功,商品数量:', orderItems.value.length)
|
||||
} else {
|
||||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载订单详情失败:', e)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
|
||||
@@ -379,90 +379,53 @@ export default {
|
||||
async loadProductDetail(productId: string, options: any = {}) {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
try {
|
||||
const dbProductResponse = await supabaseService.getProductById(productId)
|
||||
let dbProduct: UTSJSONObject | null = null
|
||||
if (Array.isArray(dbProductResponse)) {
|
||||
const arr = dbProductResponse as any[]
|
||||
if (arr.length > 0) {
|
||||
dbProduct = arr[0] as UTSJSONObject
|
||||
}
|
||||
} else if (dbProductResponse != null) {
|
||||
dbProduct = dbProductResponse as UTSJSONObject
|
||||
}
|
||||
const dbProduct = await supabaseService.getProductById(productId)
|
||||
|
||||
if (dbProduct != null) {
|
||||
// Map DB product to local product
|
||||
const dbObj = dbProduct as UTSJSONObject
|
||||
// 使用 getProductById 返回的 Product 对象
|
||||
this.product = {
|
||||
id: dbProduct['id'] as string,
|
||||
merchant_id: (dbProduct['merchant_id'] ?? dbProduct['shop_id'] ?? '') as string,
|
||||
category_id: (dbProduct['category_id'] ?? '') as string,
|
||||
name: dbProduct['name'] as string,
|
||||
description: (dbProduct['description'] ?? '') as string,
|
||||
images: [] as string[],
|
||||
price: (dbProduct['base_price'] ?? dbProduct['price'] ?? 0) as number,
|
||||
original_price: (dbProduct['market_price'] ?? dbProduct['original_price'] ?? 0) as number,
|
||||
stock: (dbProduct['available_stock'] ?? dbProduct['total_stock'] ?? dbProduct['stock'] ?? 0) as number,
|
||||
sales: (dbProduct['sale_count'] ?? dbProduct['sales'] ?? 0) as number,
|
||||
status: dbProduct['status'] != null ? dbProduct['status'] as number : 1,
|
||||
created_at: (dbProduct['created_at'] ?? new Date().toISOString()) as string,
|
||||
// Attributes
|
||||
specification: dbProduct['specification'] as string | null,
|
||||
usage: dbProduct['usage'] as string | null,
|
||||
side_effects: dbProduct['side_effects'] as string | null,
|
||||
precautions: dbProduct['precautions'] as string | null,
|
||||
expiry_date: dbProduct['expiry_date'] as string | null,
|
||||
storage_conditions: dbProduct['storage_conditions'] as string | null,
|
||||
approval_number: dbProduct['approval_number'] as string | null,
|
||||
id: dbProduct.id,
|
||||
merchant_id: dbProduct.merchant_id ?? '',
|
||||
category_id: dbProduct.category_id ?? '',
|
||||
name: dbProduct.name,
|
||||
description: dbProduct.description ?? '',
|
||||
images: dbProduct.images ?? [] as string[],
|
||||
price: dbProduct.price ?? dbProduct.base_price ?? 0,
|
||||
original_price: dbProduct.original_price ?? dbProduct.market_price ?? 0,
|
||||
stock: dbProduct.stock ?? dbProduct.total_stock ?? 0,
|
||||
sales: dbProduct.sale_count ?? 0,
|
||||
status: dbProduct.status ?? 1,
|
||||
created_at: dbProduct.created_at ?? new Date().toISOString(),
|
||||
specification: dbProduct.specification ?? null,
|
||||
usage: dbProduct.usage ?? null,
|
||||
side_effects: dbProduct.side_effects ?? null,
|
||||
precautions: dbProduct.precautions ?? null,
|
||||
expiry_date: dbProduct.expiry_date ?? null,
|
||||
storage_conditions: dbProduct.storage_conditions ?? null,
|
||||
approval_number: dbProduct.approval_number ?? null,
|
||||
tags: [] as string[]
|
||||
} as ProductType
|
||||
|
||||
// Handle Images
|
||||
if (dbProduct['image_urls'] != null) {
|
||||
|
||||
// 解析 tags
|
||||
if (dbProduct.tags != null && dbProduct.tags != '') {
|
||||
try {
|
||||
const imageUrls = dbProduct['image_urls']
|
||||
const parsed = typeof imageUrls === 'string' ? JSON.parse(imageUrls) : imageUrls
|
||||
if (Array.isArray(parsed)) {
|
||||
this.product.images = (parsed as any[]).map((i: any): string => i as string)
|
||||
}
|
||||
} catch (e) { console.error('Error parsing image_urls', e) }
|
||||
}
|
||||
// Fallback to main_image_url if no images found
|
||||
if (this.product.images.length === 0 && dbProduct['main_image_url'] != null) {
|
||||
this.product.images.push(dbProduct['main_image_url'] as string)
|
||||
}
|
||||
// Fallback to 'image' field (legacy)
|
||||
if (this.product.images.length === 0 && dbProduct['image'] != null) {
|
||||
this.product.images.push(dbProduct['image'] as string)
|
||||
}
|
||||
// Final fallback
|
||||
if (this.product.images.length === 0) {
|
||||
this.product.images.push('/static/default-product.png')
|
||||
}
|
||||
|
||||
// Handle Tags
|
||||
if (dbProduct['tags'] != null) {
|
||||
try {
|
||||
const tagsData = dbProduct['tags']
|
||||
const parsedTags = typeof tagsData === 'string' ? JSON.parse(tagsData) : tagsData
|
||||
const parsedTags = JSON.parse(dbProduct.tags)
|
||||
if (Array.isArray(parsedTags)) {
|
||||
this.product.tags = (parsedTags as any[]).map((t: any): string => t as string)
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
// Handle Images - 使用 main_image_url 作为后备
|
||||
if (this.product.images.length == 0 && dbProduct.main_image_url != null && dbProduct.main_image_url != '') {
|
||||
this.product.images.push(dbProduct.main_image_url)
|
||||
}
|
||||
// Final fallback
|
||||
if (this.product.images.length == 0) {
|
||||
this.product.images.push('/static/default-product.png')
|
||||
}
|
||||
|
||||
// Handle JSON attributes if present
|
||||
const attributes = dbProduct['attributes']
|
||||
if (attributes != null && typeof attributes === 'string') {
|
||||
try {
|
||||
const attrs = JSON.parse(attributes) as UTSJSONObject | null
|
||||
if (attrs != null) {
|
||||
// Merge attributes into product if they match keys
|
||||
if (attrs['specification'] != null) this.product.specification = attrs['specification'] as string
|
||||
if (attrs['usage'] != null) this.product.usage = attrs['usage'] as string
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
console.log('商品详情加载成功:', this.product.name, '库存:', this.product.stock, '销量:', this.product.sales)
|
||||
} else {
|
||||
throw new Error('No product found')
|
||||
}
|
||||
@@ -471,12 +434,21 @@ export default {
|
||||
// Fallback to options if available
|
||||
this.product.id = productId
|
||||
const opts = options as UTSJSONObject
|
||||
const nameOpt = opts['name'] as string | null
|
||||
this.product.name = (nameOpt != null && nameOpt !== '') ? decodeURIComponent(nameOpt) ?? '未知商品' : '未知商品'
|
||||
const priceOpt = opts['price'] as string | null
|
||||
this.product.price = (priceOpt != null && priceOpt !== '') ? parseFloat(priceOpt) : 0
|
||||
const imageOpt = opts['image'] as string | null
|
||||
const decodedImage = (imageOpt != null && imageOpt !== '') ? decodeURIComponent(imageOpt) : null
|
||||
const nameOpt = opts['name']
|
||||
this.product.name = (nameOpt != null && nameOpt != '') ? decodeURIComponent(nameOpt as string) ?? '未知商品' : '未知商品'
|
||||
|
||||
// price 可能是 string 或 number 类型
|
||||
const priceOpt = opts['price']
|
||||
if (typeof priceOpt == 'number') {
|
||||
this.product.price = priceOpt as number
|
||||
} else if (typeof priceOpt == 'string') {
|
||||
this.product.price = parseFloat(priceOpt as string)
|
||||
} else {
|
||||
this.product.price = 0
|
||||
}
|
||||
|
||||
const imageOpt = opts['image']
|
||||
const decodedImage = (imageOpt != null && imageOpt != '') ? decodeURIComponent(imageOpt as string) : null
|
||||
this.product.images = decodedImage != null ? [decodedImage] : ['/static/default-product.png']
|
||||
}
|
||||
|
||||
@@ -499,22 +471,23 @@ export default {
|
||||
try {
|
||||
const shopResponse = await supabaseService.getShopByMerchantId(merchantId)
|
||||
if (shopResponse != null) {
|
||||
const shop = shopResponse as UTSJSONObject
|
||||
// 直接使用 Shop 对象的属性
|
||||
this.merchant = {
|
||||
id: shop['id'] as string,
|
||||
user_id: shop['merchant_id'] as string,
|
||||
shop_name: shop['shop_name'] as string,
|
||||
shop_logo: (shop['shop_logo'] ?? '/static/default-shop.png') as string,
|
||||
shop_banner: (shop['shop_banner'] ?? '/static/default-banner.png') as string,
|
||||
shop_description: (shop['description'] ?? '') as string,
|
||||
contact_name: (shop['contact_name'] ?? '店主') as string,
|
||||
contact_phone: (shop['contact_phone'] ?? '') as string,
|
||||
id: shopResponse.id,
|
||||
user_id: shopResponse.merchant_id,
|
||||
shop_name: shopResponse.shop_name,
|
||||
shop_logo: shopResponse.shop_logo ?? '/static/default-shop.png',
|
||||
shop_banner: shopResponse.shop_banner ?? '/static/default-banner.png',
|
||||
shop_description: shopResponse.description ?? '',
|
||||
contact_name: shopResponse.contact_name ?? '店主',
|
||||
contact_phone: shopResponse.contact_phone ?? '',
|
||||
shop_status: 1,
|
||||
rating: (shop['rating_avg'] ?? 5.0) as number,
|
||||
total_sales: (shop['total_sales'] ?? 0) as number,
|
||||
created_at: (shop['created_at'] ?? new Date().toISOString()) as string
|
||||
rating: shopResponse.rating_avg ?? 5.0,
|
||||
total_sales: shopResponse.total_sales ?? 0,
|
||||
created_at: shopResponse.created_at ?? new Date().toISOString()
|
||||
} as MerchantType
|
||||
realMerchantLoaded = true
|
||||
console.log('店铺信息加载成功:', this.merchant.shop_name)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Load shop failed', e)
|
||||
@@ -555,33 +528,30 @@ export default {
|
||||
const skus = await supabaseService.getProductSkus(productId)
|
||||
if (skus.length > 0) {
|
||||
console.log('加载到商品SKU:', skus.length)
|
||||
this.productSkus = skus.map((skuData): ProductSkuType => {
|
||||
const sku = skuData as UTSJSONObject
|
||||
this.productSkus = []
|
||||
for (let i = 0; i < skus.length; i++) {
|
||||
const skuData = skus[i]
|
||||
// 解析 specifications JSON 字符串
|
||||
let specs: UTSJSONObject = {}
|
||||
const specsData = sku['specifications']
|
||||
if (specsData != null) {
|
||||
if (skuData.specifications != null && skuData.specifications != '') {
|
||||
try {
|
||||
if (typeof specsData === 'string') {
|
||||
specs = JSON.parse(specsData) as UTSJSONObject
|
||||
} else {
|
||||
// 假设已经是对象
|
||||
specs = specsData as UTSJSONObject
|
||||
}
|
||||
specs = JSON.parse(skuData.specifications) as UTSJSONObject
|
||||
} catch(e) {
|
||||
console.error('解析SKU规格失败', e)
|
||||
console.error('解析SKU规格失败', e)
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: sku['id'] as string,
|
||||
product_id: sku['product_id'] as string,
|
||||
sku_code: sku['sku_code'] as string,
|
||||
specifications: specs,
|
||||
price: sku['price'] as number,
|
||||
stock: sku['stock'] != null ? sku['stock'] as number : 0,
|
||||
image_url: sku['image_url'] != null ? sku['image_url'] as string : '',
|
||||
status: sku['status'] != null ? sku['status'] as number : 1
|
||||
} as ProductSkuType
|
||||
})
|
||||
}
|
||||
const sku: ProductSkuType = {
|
||||
id: skuData.id,
|
||||
product_id: skuData.product_id,
|
||||
sku_code: skuData.sku_code,
|
||||
specifications: specs,
|
||||
price: skuData.price,
|
||||
stock: skuData.stock ?? 0,
|
||||
image_url: skuData.image_url ?? '',
|
||||
status: skuData.status ?? 1
|
||||
}
|
||||
this.productSkus.push(sku)
|
||||
}
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -594,17 +564,54 @@ export default {
|
||||
if (this.product.merchant_id == '') return
|
||||
// Safety check for cached service definition
|
||||
try {
|
||||
// @ts-ignore
|
||||
const couponData = await supabaseService.fetchShopCoupons(this.product.merchant_id)
|
||||
this.coupons = couponData as Array<CouponTemplateType>
|
||||
} catch (e) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
const couponData2 = await supabaseService.getAvailableCoupons(this.product.merchant_id)
|
||||
this.coupons = couponData2 as Array<CouponTemplateType>
|
||||
} catch (e2) {
|
||||
console.warn('SupabaseService coupon methods not available:', e2)
|
||||
// 解析优惠券数据
|
||||
this.coupons = []
|
||||
if (couponData != null && couponData.length > 0) {
|
||||
for (let i = 0; i < couponData.length; i++) {
|
||||
const item = couponData[i]
|
||||
const couponObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||||
|
||||
const getSafeString = (key: string): string => {
|
||||
const val = couponObj.get(key)
|
||||
if (val == null) return ''
|
||||
if (typeof val == 'string') return val
|
||||
return ''
|
||||
}
|
||||
|
||||
const getSafeNumber = (key: string): number => {
|
||||
const val = couponObj.get(key)
|
||||
if (val == null) return 0
|
||||
if (typeof val == 'number') return val
|
||||
return 0
|
||||
}
|
||||
|
||||
const coupon: CouponTemplateType = {
|
||||
id: getSafeString('id'),
|
||||
name: getSafeString('name'),
|
||||
description: getSafeString('description'),
|
||||
coupon_type: getSafeNumber('coupon_type'),
|
||||
discount_type: getSafeNumber('discount_type'),
|
||||
discount_value: getSafeNumber('discount_value'),
|
||||
min_order_amount: getSafeNumber('min_order_amount'),
|
||||
max_discount_amount: getSafeNumber('max_discount_amount'),
|
||||
total_quantity: getSafeNumber('total_quantity'),
|
||||
per_user_limit: getSafeNumber('per_user_limit'),
|
||||
usage_limit: getSafeNumber('usage_limit'),
|
||||
merchant_id: getSafeString('merchant_id'),
|
||||
category_ids: [] as string[],
|
||||
product_ids: [] as string[],
|
||||
user_type_limit: getSafeNumber('user_type_limit'),
|
||||
start_time: getSafeString('start_time'),
|
||||
end_time: getSafeString('end_time'),
|
||||
status: getSafeNumber('status'),
|
||||
created_at: getSafeString('created_at')
|
||||
}
|
||||
this.coupons.push(coupon)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('SupabaseService coupon methods not available:', e)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -801,9 +808,16 @@ export default {
|
||||
},
|
||||
|
||||
goToShop() {
|
||||
if (this.merchant.user_id != null && this.merchant.user_id !== '') {
|
||||
const merchantId = this.merchant.id ?? this.product.merchant_id ?? ''
|
||||
if (merchantId != '') {
|
||||
console.log('进店点击,merchantId:', merchantId)
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}`
|
||||
url: `/pages/mall/consumer/shop-detail?merchantId=${merchantId}`
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '店铺信息加载中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="profile-scroll-content" direction="vertical" style="flex:1; height: 0; width: 100%;">
|
||||
<!-- 导航栏占位符 - 恢复 -->
|
||||
<view :style="{ height: (statusBarHeight + 10) + 'px' }"></view>
|
||||
<scroll-view class="profile-scroll-content" :scroll-y="true">
|
||||
<!-- 导航栏占位符 - 需要包含statusBarHeight + 导航栏高度44px -->
|
||||
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
|
||||
<!-- 我的服务 (移到订单上方) -->
|
||||
<view class="my-services" style="margin-top: 10px;">
|
||||
@@ -249,7 +249,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UserType, OrderType } from '@/types/mall-types.uts'
|
||||
import { UserType } from '@/types/mall-types.uts'
|
||||
import supabaseService from '@/utils/supabaseService.uts'
|
||||
|
||||
type UserStatsType = {
|
||||
@@ -283,6 +283,15 @@ type StatsPeriodType = {
|
||||
label: string
|
||||
}
|
||||
|
||||
type OrderItemType = {
|
||||
id: string
|
||||
order_no: string
|
||||
status: number
|
||||
actual_amount: number
|
||||
created_at: string
|
||||
ml_order_items: any | null
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -313,7 +322,7 @@ export default {
|
||||
coupons: 0,
|
||||
favorites: 0
|
||||
} as ServiceCountsType,
|
||||
recentOrders: [] as Array<OrderType>,
|
||||
recentOrders: [] as Array<OrderItemType>,
|
||||
statsPeriods: [
|
||||
{ key: 'month', label: '本月' },
|
||||
{ key: 'quarter', label: '本季度' },
|
||||
@@ -328,8 +337,8 @@ export default {
|
||||
save_amount: 0
|
||||
} as ConsumptionStatsType,
|
||||
statusBarHeight: 0,
|
||||
currentOrderTab: 'all' as string, // 当前选中的订单Tab
|
||||
allOrders: [] as Array<OrderType> // 存储所有订单数据
|
||||
currentOrderTab: 'all' as string,
|
||||
allOrders: [] as Array<OrderItemType>
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
@@ -348,8 +357,8 @@ export default {
|
||||
uni.$off('orderUpdated', this.handleOrderUpdated)
|
||||
},
|
||||
computed: {
|
||||
filteredOrders(): Array<OrderType> {
|
||||
const result: Array<OrderType> = []
|
||||
filteredOrders(): Array<OrderItemType> {
|
||||
const result: Array<OrderItemType> = []
|
||||
if (this.currentOrderTab === 'all') {
|
||||
for (let i: number = 0; i < this.allOrders.length; i++) {
|
||||
result.push(this.allOrders[i])
|
||||
@@ -377,29 +386,37 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载订单数据
|
||||
async loadOrders() {
|
||||
try {
|
||||
const orders = await supabaseService.getOrders()
|
||||
|
||||
const mappedOrders: Array<OrderType> = []
|
||||
const mappedOrders: Array<OrderItemType> = []
|
||||
for (let i: number = 0; i < orders.length; i++) {
|
||||
const o = orders[i] as UTSJSONObject
|
||||
const orderObj = new UTSJSONObject()
|
||||
const rawItem = orders[i]
|
||||
const o = JSON.parse(JSON.stringify(rawItem)) as UTSJSONObject
|
||||
|
||||
const keys = UTSJSONObject.keys(o)
|
||||
for (let j: number = 0; j < keys.length; j++) {
|
||||
const key = keys[j]
|
||||
orderObj.set(key, o.get(key))
|
||||
let status = o.getNumber('status')
|
||||
if (status == null) {
|
||||
const orderStatus = o.getNumber('order_status')
|
||||
status = orderStatus != null ? orderStatus : 0
|
||||
}
|
||||
|
||||
if (o.getNumber('status') == null && o.getNumber('order_status') != null) {
|
||||
orderObj.set('status', o.getNumber('order_status'))
|
||||
let actualAmount = o.getNumber('actual_amount')
|
||||
if (actualAmount == null) {
|
||||
const totalAmount = o.getNumber('total_amount')
|
||||
actualAmount = totalAmount != null ? totalAmount : 0
|
||||
}
|
||||
if (o.getNumber('actual_amount') == null && o.getNumber('total_amount') != null) {
|
||||
orderObj.set('actual_amount', o.getNumber('total_amount'))
|
||||
|
||||
const orderItem: OrderItemType = {
|
||||
id: o.getString('id') ?? '',
|
||||
order_no: o.getString('order_no') ?? '',
|
||||
status: status,
|
||||
actual_amount: actualAmount,
|
||||
created_at: o.getString('created_at') ?? '',
|
||||
ml_order_items: o.get('ml_order_items')
|
||||
}
|
||||
mappedOrders.push(orderObj as OrderType)
|
||||
|
||||
mappedOrders.push(orderItem)
|
||||
}
|
||||
|
||||
for (let i: number = 0; i < mappedOrders.length; i++) {
|
||||
@@ -418,7 +435,7 @@ export default {
|
||||
|
||||
this.allOrders = mappedOrders
|
||||
|
||||
const recentList: Array<OrderType> = []
|
||||
const recentList: Array<OrderItemType> = []
|
||||
const limit = mappedOrders.length < 5 ? mappedOrders.length : 5
|
||||
for (let i: number = 0; i < limit; i++) {
|
||||
recentList.push(mappedOrders[i])
|
||||
@@ -627,30 +644,28 @@ export default {
|
||||
return 'error'
|
||||
},
|
||||
|
||||
getOrderMainImage(order: any): string {
|
||||
const orderObj = order as UTSJSONObject
|
||||
const itemsRaw = orderObj.get('ml_order_items')
|
||||
getOrderMainImage(order: OrderItemType): string {
|
||||
const itemsRaw = order.ml_order_items
|
||||
if (itemsRaw == null) return '/static/product1.jpg'
|
||||
const items = itemsRaw as any[]
|
||||
if (items.length > 0) {
|
||||
const firstItem = items[0] as UTSJSONObject
|
||||
const imgUrl = firstItem.getString('image_url') ?? ''
|
||||
const prodImg = firstItem.getString('product_image') ?? ''
|
||||
const img = imgUrl !== '' ? imgUrl : prodImg
|
||||
if (img !== '') return img
|
||||
const firstItem = items[0] as Record<string, any>
|
||||
const imgUrl = firstItem['image_url'] as string
|
||||
const prodImg = firstItem['product_image'] as string
|
||||
const img = (imgUrl != null && imgUrl !== '') ? imgUrl : prodImg
|
||||
if (img != null && img !== '') return img
|
||||
}
|
||||
return '/static/product1.jpg'
|
||||
},
|
||||
|
||||
getOrderTitle(order: any): string {
|
||||
const orderObj = order as UTSJSONObject
|
||||
const itemsRaw = orderObj.get('ml_order_items')
|
||||
getOrderTitle(order: OrderItemType): string {
|
||||
const itemsRaw = order.ml_order_items
|
||||
if (itemsRaw == null) return '精选商品'
|
||||
const items = itemsRaw as any[]
|
||||
if (items.length > 0) {
|
||||
const firstItem = items[0] as UTSJSONObject
|
||||
const pName = firstItem.getString('product_name') ?? ''
|
||||
const name = pName !== '' ? pName : '商品'
|
||||
const firstItem = items[0] as Record<string, any>
|
||||
const pName = firstItem['product_name'] as string
|
||||
const name = (pName != null && pName !== '') ? pName : '商品'
|
||||
|
||||
if (items.length > 1) {
|
||||
return `${name} 等${items.length}件商品`
|
||||
@@ -712,19 +727,19 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
viewOrderDetail(order: OrderType) {
|
||||
viewOrderDetail(order: OrderItemType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/order-detail?orderId=${order.id}`
|
||||
})
|
||||
},
|
||||
|
||||
payOrder(order: OrderType) {
|
||||
payOrder(order: OrderItemType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?orderId=${order.id}`
|
||||
})
|
||||
},
|
||||
|
||||
confirmReceive(order: OrderType) {
|
||||
confirmReceive(order: OrderItemType) {
|
||||
uni.showModal({
|
||||
title: '确认收货',
|
||||
content: '确认已收到商品吗?',
|
||||
@@ -740,7 +755,7 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
reviewOrder(order: OrderType) {
|
||||
reviewOrder(order: OrderItemType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/review?orderId=${order.id}`
|
||||
})
|
||||
@@ -855,6 +870,12 @@ export default {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-scroll-content {
|
||||
flex: 1;
|
||||
}
|
||||
/* 智能顶部导航栏 */
|
||||
.smart-navbar {
|
||||
@@ -862,13 +883,12 @@ export default {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
background-color: #4CAF50;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
|
||||
@@ -284,7 +284,7 @@ const handleLogin = async () => {
|
||||
|
||||
uni.showToast({ title: '管理员登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/category' })
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
@@ -353,7 +353,7 @@ const handleLogin = async () => {
|
||||
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/category' })
|
||||
}, 500)
|
||||
} catch (err) {
|
||||
console.error('登录错误:', err)
|
||||
|
||||
1
static/tabbar/cart-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">🛒</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/cart.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">🛒</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/category-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">🔠</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/category.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">🔠</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/home-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">🏠</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/home.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">🏠</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/messages-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">💬</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/messages.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">💬</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/profile-active.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">👤</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |
1
static/tabbar/profile.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><text x="12" y="18" font-size="20" text-anchor="middle">👤</text></svg>
|
||||
|
After Width: | Height: | Size: 156 B |