consumer模块完成度95%,完成部署消费者端(外网可访问consumer.meitizs.com),消费者小程序能正常运行在微信开发者工具上

This commit is contained in:
cyh666666
2026-03-05 16:54:00 +08:00
parent 7f7f723d93
commit 3b0e397714
728 changed files with 1495 additions and 985 deletions

View File

@@ -29,8 +29,8 @@ export const WS_URL: string = 'ws://119.146.131.237:9126/realtime/v1/websocket'
// export const WS_URL: string = 'ws://192.168.1.64:3000/realtime/v1' // export const WS_URL: string = 'ws://192.168.1.64:3000/realtime/v1'
// 路由配置 // 路由配置
export const HOME_REDIRECT: string = '/pages/mall/consumer/index' export const HOME_REDIRECT: string = '/pages/main/index'
export const TABORPAGE: string = '/pages/mall/consumer/index' export const TABORPAGE: string = '/pages/main/index'
// 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向) // 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向)
export const IS_TEST_MODE: boolean = true export const IS_TEST_MODE: boolean = true

View File

@@ -69,7 +69,7 @@
} }
}, },
{ {
"path": "pages/mall/consumer/index", "path": "pages/main/index",
"style": { "style": {
"navigationBarTitleText": "首页", "navigationBarTitleText": "首页",
"navigationStyle": "custom", "navigationStyle": "custom",
@@ -77,14 +77,14 @@
} }
}, },
{ {
"path": "pages/mall/consumer/category", "path": "pages/main/category",
"style": { "style": {
"navigationBarTitleText": "分类", "navigationBarTitleText": "分类",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{ {
"path": "pages/mall/consumer/messages", "path": "pages/main/messages",
"style": { "style": {
"navigationBarTitleText": "消息", "navigationBarTitleText": "消息",
"navigationStyle": "custom", "navigationStyle": "custom",
@@ -92,14 +92,14 @@
} }
}, },
{ {
"path": "pages/mall/consumer/cart", "path": "pages/main/cart",
"style": { "style": {
"navigationBarTitleText": "购物车", "navigationBarTitleText": "购物车",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{ {
"path": "pages/mall/consumer/profile", "path": "pages/main/profile",
"style": { "style": {
"navigationBarTitleText": "我的", "navigationBarTitleText": "我的",
"navigationStyle": "custom" "navigationStyle": "custom"
@@ -912,31 +912,31 @@
"borderStyle": "black", "borderStyle": "black",
"list": [ "list": [
{ {
"pagePath": "pages/mall/consumer/index", "pagePath": "pages/main/index",
"text": "首页", "text": "首页",
"iconPath": "static/tabbar/home.png", "iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home.png" "selectedIconPath": "static/tabbar/home.png"
}, },
{ {
"pagePath": "pages/mall/consumer/category", "pagePath": "pages/main/category",
"text": "分类", "text": "分类",
"iconPath": "static/tabbar/category.png", "iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category.png" "selectedIconPath": "static/tabbar/category.png"
}, },
{ {
"pagePath": "pages/mall/consumer/messages", "pagePath": "pages/main/messages",
"text": "消息", "text": "消息",
"iconPath": "static/tabbar/message.png", "iconPath": "static/tabbar/message.png",
"selectedIconPath": "static/tabbar/message.png" "selectedIconPath": "static/tabbar/message.png"
}, },
{ {
"pagePath": "pages/mall/consumer/cart", "pagePath": "pages/main/cart",
"text": "购物车", "text": "购物车",
"iconPath": "static/tabbar/cart.png", "iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart.png" "selectedIconPath": "static/tabbar/cart.png"
}, },
{ {
"pagePath": "pages/mall/consumer/profile", "pagePath": "pages/main/profile",
"text": "我的", "text": "我的",
"iconPath": "static/tabbar/user.png", "iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user.png" "selectedIconPath": "static/tabbar/user.png"

View File

@@ -1,4 +1,4 @@
<!-- pages/mall/consumer/cart.uvue --> <!-- pages/main/cart.uvue -->
<template> <template>
<view class="cart-page"> <view class="cart-page">
<!-- 智能顶部导航栏 - 与消息页保持一致 --> <!-- 智能顶部导航栏 - 与消息页保持一致 -->
@@ -202,6 +202,24 @@ type CartGroup = {
items: LocalCartItem[] items: LocalCartItem[]
} }
const compareStrings = (a: string, b: string): boolean => {
console.log('[compareStrings] a length:', a.length, 'b length:', b.length)
console.log('[compareStrings] a type:', typeof a, 'b type:', typeof b)
console.log('[compareStrings] a value:', JSON.stringify(a))
console.log('[compareStrings] b value:', JSON.stringify(b))
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
const aCode = a.charCodeAt(i)
const bCode = b.charCodeAt(i)
if (aCode != null && bCode != null && aCode !== bCode) {
console.log('[compareStrings] mismatch at index:', i, 'a:', aCode, 'b:', bCode)
return false
}
}
return true
}
type RecommendProduct = { type RecommendProduct = {
id: string id: string
shopId: string shopId: string
@@ -216,6 +234,7 @@ type RecommendProduct = {
// 响应式数据 // 响应式数据
const cartItems = ref<LocalCartItem[]>([]) const cartItems = ref<LocalCartItem[]>([])
const recommendProducts = ref<RecommendProduct[]>([]) const recommendProducts = ref<RecommendProduct[]>([])
const recommendPage = ref<number>(1)
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
const statusBarHeight = ref(0) const statusBarHeight = ref(0)
const isManageMode = ref(false) const isManageMode = ref(false)
@@ -223,9 +242,11 @@ const updatingItems = ref<Set<string>>(new Set()) // Track items being updated t
// 计算属性 // 计算属性
const cartGroups = computed<CartGroup[]>(() => { const cartGroups = computed<CartGroup[]>(() => {
console.log('[cartGroups] 计算购物车分组, cartItems count:', cartItems.value.length)
const groups = new Map<string, CartGroup>() const groups = new Map<string, CartGroup>()
cartItems.value.forEach((item: LocalCartItem) => { cartItems.value.forEach((item: LocalCartItem) => {
console.log('[cartGroups] item:', item.id, 'shopId:', item.shopId, 'shopName:', item.shopName)
const shopKey = item.shopId const shopKey = item.shopId
if (!groups.has(shopKey)) { if (!groups.has(shopKey)) {
groups.set(shopKey, { groups.set(shopKey, {
@@ -244,6 +265,7 @@ const cartGroups = computed<CartGroup[]>(() => {
const groupArray: CartGroup[] = [] const groupArray: CartGroup[] = []
groups.forEach((value: CartGroup) => { groups.forEach((value: CartGroup) => {
console.log('[cartGroups] group:', value.shopId, 'items count:', value.items.length)
groupArray.push(value) groupArray.push(value)
}) })
return groupArray return groupArray
@@ -266,8 +288,17 @@ const totalPrice = computed(() => {
// 检查店铺是否全选 // 检查店铺是否全选
const isShopSelected = (shopId: string): boolean => { const isShopSelected = (shopId: string): boolean => {
const shopItems = cartItems.value.filter((item: LocalCartItem): boolean => item.shopId === shopId) const shopItems: LocalCartItem[] = []
return shopItems.length > 0 && shopItems.every((item: LocalCartItem): boolean => item.selected) for (let i = 0; i < cartItems.value.length; i++) {
if (compareStrings(cartItems.value[i].shopId, shopId)) {
shopItems.push(cartItems.value[i])
}
}
if (shopItems.length === 0) return false
for (let i = 0; i < shopItems.length; i++) {
if (!shopItems[i].selected) return false
}
return true
} }
const toggleManageMode = () => { const toggleManageMode = () => {
@@ -285,25 +316,49 @@ onMounted(() => {
initPage() initPage()
}) })
// 提取更新列表的辅助函数以减少重复代码
const updateRecommendList = (recommends: Product[]) => {
recommendProducts.value = recommends.map((p: Product): RecommendProduct => {
return {
id: p.id,
shopId: p.merchant_id ?? 'unknown',
shopName: p.shop_name ?? '商城推荐',
name: p.name,
price: p.base_price ?? p.market_price ?? 0,
image: p.main_image_url ?? p.image_url ?? '/static/images/default-product.png',
skuId: '',
merchant_id: p.merchant_id ?? ''
}
})
}
const refreshRecommend = async () => { const refreshRecommend = async () => {
try { try {
// 使用 searchProducts 获取销售额排序的 6 个产 // 递增页码以获取不同的商
const hotResp = await supabaseService.searchProducts('', 1, 6, 'sales') recommendPage.value = recommendPage.value + 1
// 使用 searchProducts 获取不同页码的 6 个产品
const hotResp = await supabaseService.searchProducts('', recommendPage.value, 6, 'sales')
const recommends = hotResp.data const recommends = hotResp.data
// 如果新页码没有数据,重置为第一页再试一次
if (recommends.length === 0 && recommendPage.value > 1) {
recommendPage.value = 1
const firstPageResp = await supabaseService.searchProducts('', 1, 6, 'sales')
const firstRecommends = firstPageResp.data
if (firstRecommends.length > 0) {
updateRecommendList(firstRecommends)
uni.showToast({
title: '已重置推荐',
icon: 'none'
})
}
return
}
if (recommends.length > 0) { if (recommends.length > 0) {
recommendProducts.value = recommends.map((p: Product): RecommendProduct => { updateRecommendList(recommends)
return {
id: p.id,
shopId: p.merchant_id ?? 'unknown',
shopName: p.shop_name ?? '商城推荐',
name: p.name,
price: p.base_price ?? p.market_price ?? 0,
image: p.main_image_url ?? p.image_url ?? '/static/images/default-product.png',
skuId: '',
merchant_id: p.merchant_id ?? ''
}
})
uni.showToast({ uni.showToast({
title: '已更新推荐', title: '已更新推荐',
icon: 'none' icon: 'none'
@@ -409,41 +464,84 @@ const toggleSelect = async (itemId: string) => {
} }
const toggleShopSelect = async (shopId: string) => { const toggleShopSelect = async (shopId: string) => {
// 获取该店铺下所有商品的ID console.log('[toggleShopSelect] shopId:', shopId)
const shopItems = cartItems.value.filter((item: LocalCartItem): boolean => item.shopId === shopId) console.log('[toggleShopSelect] shopId length:', shopId.length)
console.log('[toggleShopSelect] cartItems.value.length:', cartItems.value.length)
// 用 for 循环替代 filter避免安卓端 UTS filter 的问题
const shopItems: LocalCartItem[] = []
for (let i = 0; i < cartItems.value.length; i++) {
const item = cartItems.value[i]
const itemShopId = item.shopId
// 安卓端字符串比较问题:使用 localeCompare 或逐字符比较
const isMatch = compareStrings(itemShopId, shopId)
console.log('[toggleShopSelect] checking item:', item.id, 'item.shopId:', itemShopId, 'match:', isMatch)
if (isMatch) {
shopItems.push(item)
}
}
console.log('[toggleShopSelect] shopItems count:', shopItems.length)
if (shopItems.length === 0) return if (shopItems.length === 0) return
const isAllShopSelected = shopItems.every((item: LocalCartItem): boolean => item.selected) // 用 for 循环替代 every
const newState = !isAllShopSelected let allSelected = true
for (let i = 0; i < shopItems.length; i++) {
if (!shopItems[i].selected) {
allSelected = false
break
}
}
const newState = !allSelected
console.log('[toggleShopSelect] allSelected:', allSelected, 'newState:', newState)
const shopItemIds = shopItems.map((item: LocalCartItem): string => item.id) const shopItemIds: string[] = []
for (let i = 0; i < shopItems.length; i++) {
shopItemIds.push(shopItems[i].id)
}
console.log('[toggleShopSelect] shopItemIds:', shopItemIds)
// 乐观更新本地状态 // 创建全新的数组来触发响应式更新
const oldStates = new Map<string, boolean>() const newCartItems: LocalCartItem[] = []
cartItems.value.forEach(item => { for (let i = 0; i < cartItems.value.length; i++) {
if (item.shopId === shopId) { const item = cartItems.value[i]
oldStates.set(item.id, item.selected) const isMatch = compareStrings(item.shopId, shopId)
item.selected = newState if (isMatch) {
} console.log('[toggleShopSelect] updating item:', item.id, 'to selected:', newState)
}) // 创建新的对象
cartItems.value = [...cartItems.value] const newItem: LocalCartItem = {
id: item.id,
shopId: item.shopId,
shopName: item.shopName,
name: item.name,
price: item.price,
image: item.image,
spec: item.spec,
quantity: item.quantity,
selected: newState,
productId: item.productId,
skuId: item.skuId,
merchantId: item.merchantId
}
newCartItems.push(newItem)
} else {
newCartItems.push(item)
}
}
// 替换整个数组
cartItems.value = newCartItems
// 批量更新到Supabase // 批量更新到Supabase
const success = await supabaseService.batchUpdateCartItemSelection(shopItemIds, newState) const success = await supabaseService.batchUpdateCartItemSelection(shopItemIds, newState)
if (!success) { if (!success) {
console.error('批量更新店铺商品选中状态失败') console.error('批量更新店铺商品选中状态失败')
// 回滚
cartItems.value.forEach(item => {
if (item.shopId === shopId && oldStates.has(item.id)) {
item.selected = oldStates.get(item.id)!
}
})
cartItems.value = [...cartItems.value]
uni.showToast({ uni.showToast({
title: '操作失败', title: '操作失败',
icon: 'none' icon: 'none'
}) })
// 重新加载数据以确保状态一致
loadCartData()
} }
} }
@@ -668,7 +766,7 @@ const navigateToShop = (shopId: string, merchantId: any) => {
} }
const goShopping = () => { const goShopping = () => {
uni.switchTab({ url: '/pages/mall/consumer/index' }) uni.switchTab({ url: '/pages/main/index' })
} }
const navigateToProduct = (product: any) => { const navigateToProduct = (product: any) => {

View File

@@ -655,7 +655,7 @@ async function addToCart(product: Product): Promise<void> {
// 导航函数 // 导航函数
function navigateToSearch(): void { uni.navigateTo({ url: '/pages/mall/consumer/search' }) } function navigateToSearch(): void { uni.navigateTo({ url: '/pages/mall/consumer/search' }) }
function navigateToCart(): void { uni.navigateTo({ url: '/pages/mall/consumer/cart' }) } function navigateToCart(): void { uni.navigateTo({ url: '/pages/main/cart' }) }
function navigateToProduct(product: Product): void { function navigateToProduct(product: Product): void {
const id = (product.id ?? '').toString() const id = (product.id ?? '').toString()
if (id === '') return if (id === '') return

View File

@@ -1,4 +1,4 @@
<!-- pages/mall/consumer/index.uvue --> <!-- pages/main/index.uvue -->
<template> <template>
<view class="medic-home"> <view class="medic-home">
<!-- 智能顶部导航栏 - 添加滚动隐藏效果 --> <!-- 智能顶部导航栏 - 添加滚动隐藏效果 -->
@@ -404,7 +404,7 @@ const onParentCategoryClick = async (category: Category): Promise<void> => {
console.log('[onParentCategoryClick] 没有二级分类,直接跳转到分类页') console.log('[onParentCategoryClick] 没有二级分类,直接跳转到分类页')
uni.setStorageSync('selectedCategory', category.id) uni.setStorageSync('selectedCategory', category.id)
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/category' url: '/pages/main/category'
}) })
} }
} }
@@ -415,10 +415,10 @@ const onSubCategoryClick = (category: Category): void => {
uni.setStorageSync('selectedCategory', category.id) uni.setStorageSync('selectedCategory', category.id)
const timestamp = Date.now() const timestamp = Date.now()
const randomParam = Math.random().toString(36).substring(2, 8) const randomParam = Math.random().toString(36).substring(2, 8)
const url = `/pages/mall/consumer/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}&timestamp=${timestamp}&random=${randomParam}` const url = `/pages/main/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}&timestamp=${timestamp}&random=${randomParam}`
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/category' url: '/pages/main/category'
}) })
} }
@@ -739,10 +739,10 @@ const switchCategory = (category: any) => {
const randomParam = Math.random().toString(36).substring(2, 8) const randomParam = Math.random().toString(36).substring(2, 8)
// 构建带参数的URL直接通过URL传递分类信息 // 构建带参数的URL直接通过URL传递分类信息
const url = `/pages/mall/consumer/category?categoryId=${categoryId}&name=${encodeURIComponent(categoryName)}&timestamp=${timestamp}&random=${randomParam}` const url = `/pages/main/category?categoryId=${categoryId}&name=${encodeURIComponent(categoryName)}&timestamp=${timestamp}&random=${randomParam}`
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/category', url: '/pages/main/category',
success: () => { success: () => {
// 通过 Storage 传递参数已在上面设置 // 通过 Storage 传递参数已在上面设置
console.log('跳转分类页面成功categoryId:', categoryId) console.log('跳转分类页面成功categoryId:', categoryId)

View File

@@ -1064,7 +1064,7 @@ export default {
goShopping() { goShopping() {
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
}, },

View File

@@ -406,7 +406,7 @@ const deleteAddress = () => {
.form-group { .form-group {
background-color: #ffffff; background-color: #ffffff;
border-radius: 12px; border-radius: 12px;
padding: 0 16px; padding: 16px; /* 给整个组增加内边距 */
margin-bottom: 12px; margin-bottom: 12px;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02); box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
} }
@@ -415,46 +415,62 @@ const deleteAddress = () => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
padding: 18px 0; padding: 0 16px;
border-bottom: 1px solid #f9f9f9; min-height: 52px;
background-color: #f8f8f8;
border-radius: 26px; /* 增加大圆角,使其从直角变为圆角 */
margin-bottom: 12px;
border: none;
} }
.form-item:last-child { .form-item:last-child {
border-bottom: none; margin-bottom: 0;
} }
.detail-item { .detail-item {
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
padding: 16px;
border-radius: 16px; /* 详细地址区域也增加圆角 */
} }
.detail-item .label { .detail-item .label {
margin-bottom: 12px; margin-bottom: 8px;
} }
.label { .label {
width: 80px; width: 80px;
font-size: 15px; font-size: 15px;
color: #333; color: #666;
font-weight: 500; font-weight: 400;
} }
.input { .input {
flex: 1; flex: 1;
font-size: 15px; height: 44px; /* 增加高度 */
line-height: 44px;
font-size: 16px;
color: #333; color: #333;
padding: 0 4px;
background-color: transparent; /* 确保输入框背景透明 */
border: none; /* 强制去除安卓原生边框 */
outline: none; /* 强制去除焦点边框 */
} }
.textarea { .textarea {
width: 100%; width: 100%;
height: 60px; height: 80px;
font-size: 15px; font-size: 15px;
line-height: 1.5; line-height: 1.6;
color: #333; color: #333;
padding: 4px 0;
background-color: transparent;
border: none; /* 强制去除安卓原生边框 */
outline: none; /* 强制去除焦点边框 */
} }
.placeholder { .placeholder {
color: #ccc; color: #bbb;
font-size: 15px; font-size: 15px;
} }
@@ -481,10 +497,11 @@ const deleteAddress = () => {
} }
.tag-item { .tag-item {
padding: 6px 18px; padding: 8px 20px; /* 增大点击区域 */
background-color: #f7f7f7; background-color: #f7f7f7;
border-radius: 20px; border-radius: 20px;
margin-right: 12px; margin-right: 12px;
margin-bottom: 8px; /* 增加底部间距 */
border: 1px solid transparent; border: 1px solid transparent;
} }
@@ -494,7 +511,7 @@ const deleteAddress = () => {
} }
.tag-text { .tag-text {
font-size: 13px; font-size: 14px; /* 增大标签文字 */
color: #666; color: #666;
} }
@@ -506,6 +523,7 @@ const deleteAddress = () => {
/* 开关项 */ /* 开关项 */
.switch-item { .switch-item {
justify-content: space-between; justify-content: space-between;
min-height: 72px; /* 增加开关项高度 */
} }
.switch-label-group { .switch-label-group {
@@ -514,9 +532,9 @@ const deleteAddress = () => {
} }
.sub-label { .sub-label {
font-size: 12px; font-size: 13px; /* 增大副标题 */
color: #999; color: #999;
margin-top: 4px; margin-top: 6px;
} }
/* 智能填写 */ /* 智能填写 */

View File

@@ -433,6 +433,8 @@ const sendMessage = async () => {
// 清空输入框 // 清空输入框
inputMessage.value = '' inputMessage.value = ''
// 发送消息时确保收起表情面板
showEmoji.value = false
// 发送到 Supabase // 发送到 Supabase
if (merchantId.value != '') { if (merchantId.value != '') {
@@ -461,12 +463,17 @@ const simulateCustomerReply = async () => {
// 插入表情 // 插入表情
function insertEmoji(emoji: string): void { function insertEmoji(emoji: string): void {
inputMessage.value += emoji inputMessage.value += emoji
showEmoji.value = false // 选中表情后收起表情列表
inputFocus.value = true inputFocus.value = true
} }
// 显示表情选择器 // 显示表情选择器
function showEmojiPicker(): void { function showEmojiPicker(): void {
showEmoji.value = !showEmoji.value showEmoji.value = !showEmoji.value
if (showEmoji.value) {
// 如果打开表情,通常需要收起键盘
uni.hideKeyboard()
}
} }
// 显示图片选择器 // 显示图片选择器

View File

@@ -1,7 +1,7 @@
<!-- 结算页面 --> <!-- 结算页面 -->
<template> <template>
<view class="checkout-page"> <view class="checkout-page">
<scroll-view class="checkout-content" scroll-y> <scroll-view class="checkout-content" direction="vertical">
<!-- 收货地址 --> <!-- 收货地址 -->
<view class="address-section" @click="selectAddress"> <view class="address-section" @click="selectAddress">
<view v-if="selectedAddress" class="address-info"> <view v-if="selectedAddress" class="address-info">
@@ -137,7 +137,7 @@
<text class="popup-close" @click="showAddressPopup = false">×</text> <text class="popup-close" @click="showAddressPopup = false">×</text>
</view> </view>
<scroll-view class="address-list-container" scroll-y="true" :scroll-with-animation="true"> <scroll-view class="address-list-container" direction="vertical" :scroll-with-animation="true">
<!-- 登录提示 --> <!-- 登录提示 -->
<view v-if="isLoggedIn == false" class="login-prompt" @click="goToLogin"> <view v-if="isLoggedIn == false" class="login-prompt" @click="goToLogin">
<text class="login-prompt-icon">🔒</text> <text class="login-prompt-icon">🔒</text>
@@ -215,7 +215,7 @@
</view> </view>
</view> </view>
<scroll-view class="form-content" scroll-y="true"> <scroll-view class="form-content" direction="vertical">
<view class="form-section"> <view class="form-section">
<view class="form-item"> <view class="form-item">
<text class="form-label">收货人</text> <text class="form-label">收货人</text>
@@ -1422,14 +1422,20 @@ const goToLogin = () => {
.checkout-page { .checkout-page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; /* replace height: 100vh */ position: absolute;
background-color: #f5f5f5; top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #f5f5f5;
overflow: hidden;
} }
/* 顶部栏 */ /* 顶部栏 */
.checkout-header { .checkout-header {
background-color: #ffffff; background-color: #ffffff;
padding: 15px; padding: 15px;
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
flex-shrink: 0;
} }
.header-title { .header-title {
@@ -1441,6 +1447,9 @@ const goToLogin = () => {
.checkout-content { .checkout-content {
flex: 1; flex: 1;
width: 100%;
min-height: 0;
background-color: #f5f5f5;
} }
.address-section { .address-section {
@@ -1524,6 +1533,7 @@ const goToLogin = () => {
.address-popup { .address-popup {
background-color: #ffffff; background-color: #ffffff;
width: 100%; width: 100%;
height: 60vh; /* 显式高度保证内部 scroll-view direction="vertical" 生效 */
border-radius: 20px 20px 0 0; border-radius: 20px 20px 0 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1619,13 +1629,13 @@ const goToLogin = () => {
background-color: #f8f8f8; background-color: #f8f8f8;
width: 92%; width: 92%;
max-width: 500px; max-width: 500px;
height: 85vh; height: 600px; /* 改用具体的像素高度Android 端的 scroll-view 计算更稳健 */
border-radius: 16px; border-radius: 16px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
z-index: 10000; z-index: 10001;
} }
.form-header { .form-header {
@@ -1646,24 +1656,14 @@ const goToLogin = () => {
.form-content { .form-content {
flex: 1; flex: 1;
height: 100px; /* 进一步减小初始高度,确保安卓端 flex:1 接管 */ width: 100%;
min-height: 0; /* 在 Android 下scroll-view 如果不给明确的高度且处于 flex 容器,必须通过 flex-grow 撑开 */
flex-grow: 1;
flex-shrink: 1;
background-color: #ffffff;
padding: 12px; padding: 12px;
} }
.address-form-popup {
background-color: #f8f8f8;
width: 92%;
max-width: 500px;
height: 80vh; /* 稍微压低高度确保不在边缘产生冲突 */
border-radius: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
z-index: 10001;
}
.form-section { .form-section {
background-color: #ffffff; background-color: #ffffff;
border-radius: 12px; border-radius: 12px;

View File

@@ -69,7 +69,7 @@ onMounted(() => {
const useCoupon = (coupon: Coupon) => { const useCoupon = (coupon: Coupon) => {
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
} }
</script> </script>

View File

@@ -318,7 +318,7 @@ const addToCart = async (product: FavoriteType) => {
const goShopping = () => { const goShopping = () => {
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
} }

View File

@@ -366,7 +366,7 @@ const loadMore = () => {
const goShopping = () => { const goShopping = () => {
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
} }

View File

@@ -622,7 +622,7 @@ const rePurchase = async () => {
if (successCount > 0) { if (successCount > 0) {
uni.showToast({ title: '已加入购物车' }) uni.showToast({ title: '已加入购物车' })
setTimeout(() => { setTimeout(() => {
uni.switchTab({ url: '/pages/mall/consumer/cart' }) uni.switchTab({ url: '/pages/main/cart' })
}, 1000) }, 1000)
} else { } else {
uni.showToast({ title: '操作失败', icon: 'none' }) uni.showToast({ title: '操作失败', icon: 'none' })

View File

@@ -2,7 +2,7 @@
<template> <template>
<view class="orders-page"> <view class="orders-page">
<!-- 顶部标题栏 --> <!-- 顶部标题栏 -->
<view class="orders-header"> <view class="orders-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="header-search full-width"> <view class="header-search full-width">
<input <input
class="search-input" class="search-input"
@@ -44,7 +44,7 @@
<!-- 订单列表 --> <!-- 订单列表 -->
<scroll-view <scroll-view
scroll-y direction="vertical"
class="orders-content" class="orders-content"
refresher-enabled refresher-enabled
:refresher-triggered="refreshing" :refresher-triggered="refreshing"
@@ -125,29 +125,30 @@
</view> </view>
<view v-if="order.status === 2" class="action-buttons"> <view v-if="order.status === 2" class="action-buttons">
<button class="action-btn remind" @click="remindShipping(order.id)">提醒发货</button> <button class="action-btn remind" @click.stop="remindShipping(order.id)">提醒发货</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button> <button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
</view> </view>
<view v-if="order.status === 3" class="action-buttons"> <view v-if="order.status === 3" class="action-buttons">
<button class="action-btn view" @click="viewLogistics(order.id)">查看物流</button> <button class="action-btn view" @click.stop="viewLogistics(order.id)">查看物流</button>
<button class="action-btn confirm" @click="confirmReceipt(order.id)">确认收货</button> <button class="action-btn confirm" @click.stop="confirmReceipt(order.id)">确认收货</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button> <button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
</view> </view>
<view v-if="order.status === 4" class="action-buttons"> <view v-if="order.status === 4" class="action-buttons">
<button class="action-btn review" @click="goReview(order)">评价</button> <button class="action-btn review" @click.stop="goReview(order)">评价</button>
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button> <button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
<button class="action-btn repurchase" @click="repurchase(order)">再次购买</button> <button class="action-btn repurchase" @click.stop="repurchase(order)">再次购买</button>
</view> </view>
<view v-if="order.status === 5" class="action-buttons"> <view v-if="order.status === 5" class="action-buttons">
<button class="action-btn view" @click="viewOrderDetail(order.id)">查看详情</button> <button class="action-btn view" @click.stop="viewOrderDetail(order.id)">查看详情</button>
</view> </view>
<view v-if="order.status === 6" class="action-buttons"> <view v-if="order.status === 6" class="action-buttons">
<button class="action-btn view" @click="viewOrderDetail(order.id)">查看详情</button> <button class="action-btn view" @click.stop="viewOrderDetail(order.id)">查看详情</button>
<button class="action-btn refund" @click="viewRefundProgress(order.id)">退款进度</button> <button class="action-btn cancel" @click.stop="cancelRefund(order.id)">取消退款</button>
<button class="action-btn refund" @click.stop="viewRefundProgress(order.id)">退款进度</button>
</view> </view>
<view v-if="order.status === 7" class="action-buttons"> <view v-if="order.status === 7" class="action-buttons">
@@ -222,6 +223,7 @@ const hasMore = ref<boolean>(true)
const refreshing = ref<boolean>(false) const refreshing = ref<boolean>(false)
const page = ref<number>(1) const page = ref<number>(1)
const activeTab = ref<string>('all') const activeTab = ref<string>('all')
const statusBarHeight = ref<number>(0)
const searchKeyword = ref<string>('') const searchKeyword = ref<string>('')
// 订单标签页 - 使用 ref 以便整体替换 // 订单标签页 - 使用 ref 以便整体替换
@@ -504,6 +506,10 @@ const loadOrders = async () => {
// 生命周期 // 生命周期
onLoad((options) => { onLoad((options) => {
// 初始化状态栏高度
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
if (options == null) return if (options == null) return
const statusVal = options['status'] const statusVal = options['status']
if (statusVal != null) { if (statusVal != null) {
@@ -518,7 +524,7 @@ onLoad((options) => {
if (type === 'pending') activeTab.value = 'pending' if (type === 'pending') activeTab.value = 'pending'
else if (type === 'shipped') activeTab.value = 'delivering' // 映射到待收货 else if (type === 'shipped') activeTab.value = 'delivering' // 映射到待收货
else if (type === 'review') activeTab.value = 'completed' // 映射到已完成 else if (type === 'review') activeTab.value = 'completed' // 映射到已完成
else if (type === 'refund') activeTab.value = 'all' // 申请售后默认显示全部 else if (type === 'refund') activeTab.value = 'aftersale' // 退款/售后跳转到售后标签页
} }
}) })
@@ -771,15 +777,11 @@ const viewLogistics = (orderId: string) => {
} }
// goReview 必须在 doConfirmReceipt 之前定义,因为 doConfirmReceipt 会调用它 // goReview 必须在 doConfirmReceipt 之前定义,因为 doConfirmReceipt 会调用它
const goReview = (order: any) => { const goReview = (order: OrderItem) => {
const orderObj = order as Record<string, any> const productIds = order.products.map((p: OrderProduct): string => {
const products = orderObj['products'] as any[] return p.id
const productIds = products.map((p: any) => {
const pObj = p as Record<string, any>
const pid = pObj['id']
return pid != null ? pid as string : ''
}).join(',') }).join(',')
const orderId = orderObj['id'] as string const orderId = order.id
uni.navigateTo({ uni.navigateTo({
url: `/pages/mall/consumer/review?orderId=${orderId}&productIds=${productIds}` url: `/pages/mall/consumer/review?orderId=${orderId}&productIds=${productIds}`
}) })
@@ -798,22 +800,15 @@ const doConfirmReceipt = async (orderId: string) => {
}) })
// 更新本地状态 // 更新本地状态
const index = orders.value.findIndex((o: any) => { const index = orders.value.findIndex((o: OrderItem): boolean => o.id === orderId)
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (index !== -1) { if (index !== -1) {
const orderObj = orders.value[index] as Record<string, any> orders.value[index].status = 4
orderObj['status'] = 4
orders.value = [...orders.value] orders.value = [...orders.value]
} }
// 跳转到评价页面 // 跳转到评价页面
setTimeout(() => { setTimeout(() => {
const order = orders.value.find((o: any) => { const order = orders.value.find((o: OrderItem): boolean => o.id === orderId)
const obj = o as Record<string, any>
return obj['id'] === orderId
})
if (order != null) { if (order != null) {
goReview(order) goReview(order)
} }
@@ -845,11 +840,10 @@ const confirmReceipt = (orderId: string) => {
}) })
} }
const repurchase = (order: any) => { const repurchase = (order: OrderItem) => {
const orderObj = order as Record<string, any> const products = order.products
const products = orderObj['products'] as any[]
if (products == null || products.length === 0) { if (products.length === 0) {
uni.showToast({ uni.showToast({
title: '订单无商品', title: '订单无商品',
icon: 'none' icon: 'none'
@@ -864,12 +858,12 @@ const repurchase = (order: any) => {
let successCount = 0 let successCount = 0
for (let i = 0; i < products.length; i++) { for (let i = 0; i < products.length; i++) {
const pObj = products[i] as Record<string, any> const product = products[i]
const productId = pObj['id'] as string const productId = product.id
const merchantId = orderObj['merchant_id'] as string const merchantId = order.merchant_id
if (productId != null && productId !== '') { if (productId != null && productId !== '') {
supabaseService.addToCart(productId, 1, '', merchantId ?? '').then((success) => { supabaseService.addToCart(productId, 1, '', merchantId ?? '').then((success: boolean) => {
completed++ completed++
if (success) successCount++ if (success) successCount++
if (completed === total) { if (completed === total) {
@@ -929,9 +923,8 @@ const viewOrderDetail = (orderId: string) => {
}) })
} }
const onApplyRefund = (order: any) => { const onApplyRefund = (order: OrderItem) => {
const orderObj = order as Record<string, any> const orderId = order.id
const orderId = orderObj['id']
uni.navigateTo({ uni.navigateTo({
url: `/pages/mall/consumer/apply-refund?orderId=${orderId}` url: `/pages/mall/consumer/apply-refund?orderId=${orderId}`
}) })
@@ -943,6 +936,32 @@ const viewRefundProgress = (orderId: string) => {
}) })
} }
const doCancelRefund = async (orderId: string) => {
uni.showLoading({ title: '处理中...' })
const result = await supabaseService.cancelRefund(orderId)
uni.hideLoading()
if (result.success) {
uni.showToast({ title: '已取消退款', icon: 'success' })
loadOrders()
} else {
uni.showToast({ title: result.message, icon: 'none' })
}
}
// 取消退款申请
const cancelRefund = (orderId: string) => {
uni.showModal({
title: '确认取消',
content: '确定要取消退款申请吗?',
success: (res) => {
if (res.confirm) {
doCancelRefund(orderId)
}
}
})
}
// 处理订单操作 // 处理订单操作
const handleOrderAction = (order: OrderItem, action: string) => { const handleOrderAction = (order: OrderItem, action: string) => {
if (action === '取消订单') { if (action === '取消订单') {
@@ -963,6 +982,8 @@ const handleOrderAction = (order: OrderItem, action: string) => {
deleteOrder(order.id) deleteOrder(order.id)
} else if (action === '退款进度') { } else if (action === '退款进度') {
viewRefundProgress(order.id) viewRefundProgress(order.id)
} else if (action === '取消退款') {
cancelRefund(order.id)
} }
} }
@@ -982,7 +1003,7 @@ const showOrderMenu = (order: OrderItem) => {
} else if (status === 5) { } else if (status === 5) {
actions = ['删除订单', '再次购买', '联系卖家'] actions = ['删除订单', '再次购买', '联系卖家']
} else if (status === 6) { } else if (status === 6) {
actions = ['退款进度', '联系卖家'] actions = ['取消退款', '退款进度', '联系卖家']
} else if (status === 7) { } else if (status === 7) {
actions = ['再次购买', '联系卖家'] actions = ['再次购买', '联系卖家']
} }
@@ -1001,59 +1022,75 @@ const navigateToSearch = () => {
uni.navigateTo({ url: '/pages/mall/consumer/search' }) uni.navigateTo({ url: '/pages/mall/consumer/search' })
} }
const navigateToProduct = (product: any) => { const navigateToProduct = (product: OrderProduct) => {
const productObj = product as Record<string, any> const productId = product.id
const productId = productObj['id']
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${productId}` }) uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${productId}` })
} }
const goShopping = () => { const goShopping = () => {
uni.switchTab({ url: '/pages/mall/consumer/index' }) uni.switchTab({ url: '/pages/main/index' })
} }
</script> </script>
<style> <style>
.orders-page { .orders-page {
position: absolute; display: flex;
flex-direction: column;
/* 采用相对于窗口的 100% 方案 */
width: 100%;
height: 100%;
/* App/小程序通过 fixed 解决根容器撑满问题H5/电脑端建议使用 flex 1 */
/* #ifdef APP-PLUS || MP-WEIXIN */
position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
display: flex; /* #endif */
flex-direction: column; /* #ifdef H5 */
position: relative;
/* #endif */
background-color: #f5f5f5; background-color: #f5f5f5;
overflow: hidden; overflow: hidden;
} }
/* 头部 */ /* 头部:确保显式可见且不被遮挡 */
.orders-header { .orders-header {
background-color: white; background-color: #ffffff;
padding: 15px; padding: 10px 15px;
display: flex; display: flex;
flex-direction: row;
align-items: center; align-items: center;
justify-content: center; width: 100%;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eeeeee;
z-index: 10; z-index: 999;
position: relative;
box-sizing: border-box;
flex-shrink: 0; flex-shrink: 0;
} }
.header-search.full-width { .header-search.full-width {
display: flex; display: flex;
flex-direction: row;
align-items: center; align-items: center;
position: relative; position: relative;
width: 100%; width: 100%;
flex: 1;
} }
.search-input { .search-input {
flex: 1; flex: 1;
height: 36px; height: 36px;
border: 1px solid #ddd; line-height: normal;
border: 1px solid #dddddd;
border-radius: 18px; border-radius: 18px;
padding: 0 40px 0 16px; padding: 0 40px 0 16px;
font-size: 14px; font-size: 14px;
background-color: #f5f5f5; background-color: #f5f5f5;
color: #333; color: #333333;
width: 100%; width: 100%;
display: flex;
align-items: center;
} }
.search-input::placeholder { .search-input::placeholder {
@@ -1086,7 +1123,7 @@ const goShopping = () => {
/* cursor: pointer; removed */ /* cursor: pointer; removed */
} }
/* 标签页 */ /* 标签页容器:确保在安卓下层级正确且不随内容滚动 */
.order-tabs-fixed-container { .order-tabs-fixed-container {
background-color: #ffffff; background-color: #ffffff;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
@@ -1094,26 +1131,25 @@ const goShopping = () => {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
width: 100%; width: 100%;
z-index: 10; z-index: 100 !important;
height: 50px;
flex-shrink: 0; flex-shrink: 0;
position: relative;
} }
/* 安卓端滚动修复 */ /* 安卓端滚动修复:关键容器 */
.orders-content { .orders-content {
flex: 1; flex: 1;
width: 100%; width: 100%;
height: 0; /* 关键:强制 flex: 1 生效 */ /* 极致方案:不再设置 height: 0改用 flex: 1 填满,并显式设置 scroll-y 的表现 */
min-height: 0;
flex-shrink: 1;
background-color: #f5f5f5;
} }
.orders-header { /* 顶部内容区域必须显式不可伸缩,防止撑占滚动空间 */
background-color: white; .orders-header, .order-tabs-fixed-container {
padding: 15px;
display: flex;
align-items: center;
justify-content: center;
border-bottom: 1px solid #eee;
flex-shrink: 0; flex-shrink: 0;
width: 100%;
} }
.tab-item-fixed { .tab-item-fixed {
@@ -1456,6 +1492,16 @@ const goShopping = () => {
border-color: #34c759; border-color: #34c759;
} }
.action-btn.refund {
color: #666;
border-color: #ccc;
}
.action-btn.cancel {
color: #ff6b6b;
border-color: #ff6b6b;
}
.action-btn.review { .action-btn.review {
color: #ff9500; color: #ff9500;
border-color: #ff9500; border-color: #ff9500;

View File

@@ -95,7 +95,7 @@ const viewOrder = () => {
const goHome = () => { const goHome = () => {
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
} }
</script> </script>

View File

@@ -1,7 +1,7 @@
<!-- 支付页面 --> <!-- 支付页面 -->
<template> <template>
<view class="payment-page"> <view class="payment-page">
<view class="payment-content"> <scroll-view class="payment-content" direction="vertical">
<!-- 价格明细 --> <!-- 价格明细 -->
<view class="price-detail-section"> <view class="price-detail-section">
<text class="section-title">价格明细</text> <text class="section-title">价格明细</text>
@@ -71,7 +71,7 @@
</view> </view>
<text class="forgot-password" @click="forgotPassword">忘记密码?</text> <text class="forgot-password" @click="forgotPassword">忘记密码?</text>
</view> </view>
</view> </scroll-view>
<!-- 底部支付按钮 --> <!-- 底部支付按钮 -->
<view class="payment-bottom"> <view class="payment-bottom">
@@ -614,8 +614,15 @@ onUnmounted(() => {
.payment-page { .payment-page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: #f5f5f5; background-color: #f5f5f5;
overflow: hidden;
} }
.payment-header { .payment-header {

View File

@@ -832,7 +832,7 @@ export default {
}, },
goToCart() { goToCart() {
uni.switchTab({ url: '/pages/mall/consumer/cart' }) uni.switchTab({ url: '/pages/main/cart' })
}, },
decreaseQuantity() { decreaseQuantity() {

View File

@@ -107,7 +107,7 @@ onMounted(() => {
const usePacket = (item: RedPacket) => { const usePacket = (item: RedPacket) => {
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
} }

View File

@@ -1,10 +1,13 @@
<!-- 评价页面 --> <!-- 评价页面 -->
<template> <template>
<view class="review-page"> <view class="review-page">
<!-- 顶部栏 --> <!-- 顶部栏:已移除“发表评价”文字 -->
<view class="review-header"> <view class="review-header">
<text class="back-btn" @click="goBack"></text> <view class="header-back" @click="goBack">
<text class="header-title">评价商品</text> <image class="back-icon" src="/static/icons/back.png" mode="aspectFit"></image>
</view>
<view class="header-title-placeholder"></view>
<view class="header-right"></view>
</view> </view>
<scroll-view class="review-content" direction="vertical"> <scroll-view class="review-content" direction="vertical">
@@ -145,7 +148,6 @@
<script setup lang="uts"> <script setup lang="uts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import supa from '@/components/supadb/aksupainstance.uts'
import { supabaseService } from '@/utils/supabaseService.uts' import { supabaseService } from '@/utils/supabaseService.uts'
type OrderType = { type OrderType = {
@@ -195,72 +197,58 @@ const isSubmitting = ref<boolean>(false)
const loadOrderData = async (): Promise<void> => { const loadOrderData = async (): Promise<void> => {
try { try {
const orderRes = await supa console.log('[loadOrderData] 开始加载订单数据, orderId:', orderId.value)
.from('ml_orders')
.select('*')
.eq('id', orderId.value)
.single()
.execute()
if (orderRes.error != null) {
console.error('加载订单失败:', orderRes.error)
return
}
if (orderRes.data != null) {
const orderData = orderRes.data as UTSJSONObject
order.value = {
id: orderData.getString('id') ?? '',
order_no: orderData.getString('order_no') ?? '',
created_at: orderData.getString('created_at') ?? '',
merchant_id: orderData.getString('merchant_id') ?? ''
} as OrderType
}
const itemsRes = await supa
.from('ml_order_items')
.select(`
*,
product:product_id(images)
`)
.eq('order_id', orderId.value)
.execute()
if (itemsRes.error != null) {
console.error('加载订单商品失败:', itemsRes.error)
return
}
const rawData = itemsRes.data
let itemsArray: Array<any> = []
if (rawData != null) {
itemsArray = rawData as Array<any>
}
const processedItems: Array<OrderItemType> = [] // 使用 supabaseService 获取订单详情
for (let i: number = 0; i < itemsArray.length; i++) { const orderDetailRaw = await supabaseService.getOrderDetail(orderId.value)
const item = itemsArray[i] as UTSJSONObject console.log('[loadOrderData] orderDetailRaw:', JSON.stringify(orderDetailRaw))
const productObjRaw = item.get('product')
const productObj = (productObjRaw != null) ? (productObjRaw as UTSJSONObject) : null if (orderDetailRaw == null) {
const imagesArrRaw = (productObj != null) ? productObj.get('images') : null console.error('加载订单失败: 未找到订单')
const imagesArr = (imagesArrRaw != null) ? (imagesArrRaw as Array<string>) : [] uni.showToast({ title: '订单不存在', icon: 'none' })
const firstImage = (imagesArr.length > 0) ? imagesArr[0] : '/static/default-product.png' return
const skuSpecRaw = item.get('sku_specifications')
const skuSpec = (skuSpecRaw != null) ? (skuSpecRaw as any) : null
const processedItem: OrderItemType = {
id: (item.getNumber('id') ?? 0) as number,
order_id: (item.getNumber('order_id') ?? 0) as number,
product_id: (item.getNumber('product_id') ?? 0) as number,
product_name: item.getString('product_name') ?? '',
price: (item.getNumber('price') ?? 0) as number,
quantity: (item.getNumber('quantity') ?? 1) as number,
sku_specifications: skuSpec,
product_image: firstImage
}
processedItems.push(processedItem)
} }
orderItems.value = processedItems
// 转换为 UTSJSONObject
const orderDetail = JSON.parse(JSON.stringify(orderDetailRaw)) as UTSJSONObject
// 解析订单基本信息
order.value = {
id: orderDetail.getString('id') ?? '',
order_no: orderDetail.getString('order_no') ?? '',
created_at: orderDetail.getString('created_at') ?? '',
merchant_id: orderDetail.getString('merchant_id') ?? ''
} as OrderType
// 解析订单商品
const itemsRaw = orderDetail.get('ml_order_items')
console.log('[loadOrderData] itemsRaw:', JSON.stringify(itemsRaw))
if (itemsRaw != null) {
const itemsList = itemsRaw as any[]
const processedItems: Array<OrderItemType> = []
for (let i: number = 0; i < itemsList.length; i++) {
const itemStr = JSON.stringify(itemsList[i])
const item = JSON.parse(itemStr) as UTSJSONObject
const skuSpec = item.get('sku_specifications')
processedItems.push({
id: (item.getNumber('id') ?? 0) as number,
order_id: (item.getNumber('order_id') ?? 0) as number,
product_id: (item.getNumber('product_id') ?? 0) as number,
product_name: item.getString('product_name') ?? '',
price: (item.getNumber('price') ?? 0) as number,
quantity: (item.getNumber('quantity') ?? 1) as number,
sku_specifications: skuSpec,
product_image: item.getString('product_image') ?? item.getString('image_url') ?? '/static/default-product.png'
} as OrderItemType)
}
orderItems.value = processedItems
console.log('[loadOrderData] processedItems count:', processedItems.length)
}
// 初始化评价数据
const count = orderItems.value.length const count = orderItems.value.length
const newRatings: Array<number> = [] const newRatings: Array<number> = []
const newContents: Array<string> = [] const newContents: Array<string> = []
@@ -274,23 +262,25 @@ const loadOrderData = async (): Promise<void> => {
contents.value = newContents contents.value = newContents
images.value = newImages images.value = newImages
const orderObj = order.value as UTSJSONObject // 获取商家信息
const merchantId = orderObj.getString('merchant_id') const orderObj = order.value
if (merchantId != null && merchantId !== '') { if (orderObj != null) {
const merchantRes = await supa const merchantId = orderObj.merchant_id
.from('ml_shops') if (merchantId != '') {
.select('id, shop_name, rating') const shopInfo = await supabaseService.getShopByMerchantId(merchantId)
.eq('id', merchantId) if (shopInfo != null) {
.single() merchant.value = {
.execute() id: shopInfo.id,
shop_name: shopInfo.shop_name,
if (merchantRes.error == null && merchantRes.data != null) { rating: shopInfo.rating_avg ?? 5
merchant.value = merchantRes.data as MerchantType } as MerchantType
}
} }
} }
} catch (err) { } catch (err) {
console.error('加载订单数据异常:', err) console.error('加载订单数据异常:', err)
uni.showToast({ title: '加载失败', icon: 'none' })
} }
} }
@@ -322,10 +312,19 @@ const formatTime = (timeStr?: string): string => {
const getSpecText = (specs: any | null): string => { const getSpecText = (specs: any | null): string => {
if (specs == null) return '' if (specs == null) return ''
if (specs instanceof UTSJSONObject) { if (typeof specs === 'string') return specs as string
return '规格信息'
try {
const specObj = JSON.parse(JSON.stringify(specs)) as UTSJSONObject
const jsonStr = JSON.stringify(specObj)
if (jsonStr == '{}' || jsonStr == 'null') return ''
// 简单解析:直接返回 JSON 字符串(去除大括号)
const cleanStr = jsonStr.replace(/^\{|\}$/g, '').replace(/"/g, '').replace(/:/g, ': ').replace(/,/g, '; ')
return cleanStr
} catch (e) {
return ''
} }
return specs as string
} }
// 获取评分文本 // 获取评分文本
@@ -548,23 +547,37 @@ const goBack = (): void => {
.review-header { .review-header {
background-color: #ffffff; background-color: #ffffff;
padding: 15px; padding: 10px 15px;
height: 44px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
position: sticky;
top: 0;
z-index: 100;
}
.header-back {
width: 44px;
height: 44px;
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid #e5e5e5; justify-content: flex-start;
} }
.back-btn { .back-icon {
font-size: 24px; width: 20px;
color: #333333; height: 20px;
padding: 5px;
margin-right: 15px;
} }
.header-title { .header-title-placeholder {
font-size: 18px; flex: 1;
font-weight: bold; }
color: #333333;
.header-right {
width: 44px;
} }
.review-content { .review-content {
@@ -632,39 +645,46 @@ const goBack = (): void => {
.rating-section { .rating-section {
display: flex; display: flex;
flex-direction: row;
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: 20px;
padding: 5px 0;
} }
.rating-label { .rating-label {
font-size: 14px; font-size: 15px;
color: #333333; color: #333333;
margin-right: 15px; font-weight: 500;
margin-right: 20px;
} }
.rating-stars { .rating-stars {
display: flex; display: flex;
/* gap: 10px; removed */ flex-direction: row;
} align-items: center;
margin-right: 15px;
.rating-stars.small {
/* gap: 5px; removed */
} }
.star-icon { .star-icon {
font-size: 24px; font-size: 26px;
color: #cccccc; margin-right: 8px;
margin-right: 10px; color: #e0e0e0;
transition: transform 0.1s ease;
} }
.star-icon.active { .star-icon.active {
color: #ff5000; color: #ff5000;
transform: scale(1.1);
} }
.rating-text { .rating-text {
margin-left: 15px;
font-size: 14px; font-size: 14px;
color: #666666; color: #999999;
margin-left: 5px;
}
.rating-stars.small {
/* gap: 5px; removed */
} }
.content-section { .content-section {
@@ -783,28 +803,49 @@ const goBack = (): void => {
.merchant-section { .merchant-section {
background-color: #ffffff; background-color: #ffffff;
padding: 15px; padding: 20px 15px;
margin-bottom: 10px; margin-top: 15px;
border-radius: 12px;
} }
.section-title { .section-title {
/* display: block; removed */
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: 600;
color: #333333; color: #333333;
margin-bottom: 15px; margin-bottom: 20px;
padding-left: 10px;
border-left: 4px solid #ff5000;
} }
.merchant-rating { .merchant-rating {
display: flex; display: flex;
flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: 20px;
padding: 0 5px;
} }
.rating-item { .rating-item {
font-size: 14px; font-size: 14px;
color: #333333; color: #666666;
flex: 1;
}
.merchant-rating .rating-stars.small {
display: flex;
flex-direction: row;
align-items: center;
}
.merchant-rating .rating-stars.small .star-icon {
font-size: 20px;
margin-right: 5px;
color: #e0e0e0;
}
.merchant-rating .rating-stars.small .star-icon.active {
color: #ff5000;
} }
.tips-section { .tips-section {

View File

@@ -821,7 +821,7 @@ const goBack = () => {
} else { } else {
// 如果只有一页(由于深链接或重定向),返回首页 // 如果只有一页(由于深链接或重定向),返回首页
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/index' url: '/pages/main/index'
}) })
} }
} }

View File

@@ -212,7 +212,7 @@ onBackPress((options) => {
// 无论是什么触发的返回系统返回键或导航栏返回按钮都跳转到profile // 无论是什么触发的返回系统返回键或导航栏返回按钮都跳转到profile
// 注意onBackPress 只能在 page 中使用component 中无效 // 注意onBackPress 只能在 page 中使用component 中无效
uni.switchTab({ uni.switchTab({
url: '/pages/mall/consumer/profile' url: '/pages/main/profile'
}) })
// 返回 true 表示阻止默认返回行为 // 返回 true 表示阻止默认返回行为
return true return true

View File

@@ -115,7 +115,7 @@ const goToShop = (shop: FollowedShop): void => {
} }
const goHome = (): void => { const goHome = (): void => {
uni.switchTab({ url: '/pages/mall/consumer/index' }) uni.switchTab({ url: '/pages/main/index' })
} }
onMounted(() => { onMounted(() => {

View File

@@ -135,7 +135,7 @@ const confirmSubscribe = async () => {
if (ins != null && ins.error == null) { if (ins != null && ins.error == null) {
uni.showToast({ title: '订阅成功', icon: 'success' }) uni.showToast({ title: '订阅成功', icon: 'success' })
setTimeout(() => { setTimeout(() => {
uni.redirectTo({ url: '/pages/mall/consumer/profile' }) uni.redirectTo({ url: '/pages/main/profile' })
}, 600) }, 600)
} else { } else {
uni.showToast({ title: ins?.error?.message ?? '订阅失败', icon: 'none' }) uni.showToast({ title: ins?.error?.message ?? '订阅失败', icon: 'none' })

View File

@@ -47,7 +47,7 @@
try { try {
const sessionInfo = supa.getSession() const sessionInfo = supa.getSession()
if (sessionInfo != null && sessionInfo.user != null) { if (sessionInfo != null && sessionInfo.user != null) {
uni.reLaunch({ url: '/pages/mall/consumer/index' }) uni.reLaunch({ url: '/pages/main/index' })
return return
} }
} catch (e) { } catch (e) {

View File

@@ -182,12 +182,12 @@ const checkLoginStatus = (): void => {
const opts = currentPage.options as UTSJSONObject const opts = currentPage.options as UTSJSONObject
const redirect = opts.getString('redirect') const redirect = opts.getString('redirect')
if (redirect != null && redirect != '') { if (redirect != null && redirect != '') {
uni.reLaunch({ url: `/pages/mall/consumer/index` }) uni.reLaunch({ url: `/pages/main/index` })
} else { } else {
uni.reLaunch({ url: '/pages/mall/consumer/index' }) uni.reLaunch({ url: '/pages/main/index' })
} }
} else { } else {
uni.reLaunch({ url: '/pages/mall/consumer/index' }) uni.reLaunch({ url: '/pages/main/index' })
} }
} }
} catch (e) { } catch (e) {
@@ -291,7 +291,7 @@ const handleLogin = async () => {
uni.showToast({ title: '管理员登录成功', icon: 'success' }) uni.showToast({ title: '管理员登录成功', icon: 'success' })
setTimeout(() => { setTimeout(() => {
uni.reLaunch({ url: '/pages/mall/consumer/index' }) uni.reLaunch({ url: '/pages/main/index' })
}, 500) }, 500)
return return
} }
@@ -360,7 +360,7 @@ const handleLogin = async () => {
uni.showToast({ title: '登录成功', icon: 'success' }) uni.showToast({ title: '登录成功', icon: 'success' })
setTimeout(() => { setTimeout(() => {
uni.reLaunch({ url: '/pages/mall/consumer/index' }) uni.reLaunch({ url: '/pages/main/index' })
}, 500) }, 500)
} catch (err) { } catch (err) {
console.error('登录错误:', err) console.error('登录错误:', err)

25
project.config.json Normal file
View File

@@ -0,0 +1,25 @@
{
"setting": {
"es6": true,
"postcss": true,
"minified": true,
"uglifyFileName": false,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useCompilerPlugins": false,
"minifyWXML": true
},
"compileType": "miniprogram",
"simulatorPluginLibVersion": {},
"packOptions": {
"ignore": [],
"include": []
},
"appid": "wxe98d61426b6456c1",
"editorSetting": {}
}

View File

@@ -0,0 +1,14 @@
{
"libVersion": "3.14.2",
"projectname": "mall",
"setting": {
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"compileHotReLoad": true
}
}

View File

@@ -194,11 +194,12 @@ export class AkReq {
resolve(result); resolve(result);
}, },
fail: (err) => { fail: (err) => {
const errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;
const result = AkReq.createResponse<any>( const result = AkReq.createResponse<any>(
err.errCode, errStatus,
err.data ?? {},
{} as UTSJSONObject, {} as UTSJSONObject,
new UniError('uni-request', err.errCode, err.errMsg ?? 'request fail') {} as UTSJSONObject,
new UniError('uni-request', errStatus, err.errMsg ?? 'request fail')
); );
resolve(result); resolve(result);
} }
@@ -302,11 +303,12 @@ export class AkReq {
resolve(result); resolve(result);
}, },
fail: (err) => { fail: (err) => {
const errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;
const result = AkReq.createResponse<any>( const result = AkReq.createResponse<any>(
err.errCode, errStatus,
err.data ?? {},
{} as UTSJSONObject, {} as UTSJSONObject,
new UniError('uni-upload', err.errCode, err.errMsg ?? 'upload fail') {} as UTSJSONObject,
new UniError('uni-upload', errStatus, err.errMsg ?? 'upload fail')
); );
resolve(result); resolve(result);
} }

Some files were not shown because too many files have changed in this diff Show More