consumer模块完成度95%,完成部署消费者端(外网可访问consumer.meitizs.com),消费者小程序能正常运行在微信开发者工具上
This commit is contained in:
@@ -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 HOME_REDIRECT: string = '/pages/mall/consumer/index'
|
||||
export const TABORPAGE: string = '/pages/mall/consumer/index'
|
||||
export const HOME_REDIRECT: string = '/pages/main/index'
|
||||
export const TABORPAGE: string = '/pages/main/index'
|
||||
|
||||
// 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向)
|
||||
export const IS_TEST_MODE: boolean = true
|
||||
20
pages.json
20
pages.json
@@ -69,7 +69,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/index",
|
||||
"path": "pages/main/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationStyle": "custom",
|
||||
@@ -77,14 +77,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/category",
|
||||
"path": "pages/main/category",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分类",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/messages",
|
||||
"path": "pages/main/messages",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"navigationStyle": "custom",
|
||||
@@ -92,14 +92,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/cart",
|
||||
"path": "pages/main/cart",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/profile",
|
||||
"path": "pages/main/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationStyle": "custom"
|
||||
@@ -912,31 +912,31 @@
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/index",
|
||||
"pagePath": "pages/main/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/category",
|
||||
"pagePath": "pages/main/category",
|
||||
"text": "分类",
|
||||
"iconPath": "static/tabbar/category.png",
|
||||
"selectedIconPath": "static/tabbar/category.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/messages",
|
||||
"pagePath": "pages/main/messages",
|
||||
"text": "消息",
|
||||
"iconPath": "static/tabbar/message.png",
|
||||
"selectedIconPath": "static/tabbar/message.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/cart",
|
||||
"pagePath": "pages/main/cart",
|
||||
"text": "购物车",
|
||||
"iconPath": "static/tabbar/cart.png",
|
||||
"selectedIconPath": "static/tabbar/cart.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/consumer/profile",
|
||||
"pagePath": "pages/main/profile",
|
||||
"text": "我的",
|
||||
"iconPath": "static/tabbar/user.png",
|
||||
"selectedIconPath": "static/tabbar/user.png"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- pages/mall/consumer/cart.uvue -->
|
||||
<!-- pages/main/cart.uvue -->
|
||||
<template>
|
||||
<view class="cart-page">
|
||||
<!-- 智能顶部导航栏 - 与消息页保持一致 -->
|
||||
@@ -202,6 +202,24 @@ type CartGroup = {
|
||||
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 = {
|
||||
id: string
|
||||
shopId: string
|
||||
@@ -216,6 +234,7 @@ type RecommendProduct = {
|
||||
// 响应式数据
|
||||
const cartItems = ref<LocalCartItem[]>([])
|
||||
const recommendProducts = ref<RecommendProduct[]>([])
|
||||
const recommendPage = ref<number>(1)
|
||||
const loading = ref<boolean>(false)
|
||||
const statusBarHeight = ref(0)
|
||||
const isManageMode = ref(false)
|
||||
@@ -223,9 +242,11 @@ const updatingItems = ref<Set<string>>(new Set()) // Track items being updated t
|
||||
|
||||
// 计算属性
|
||||
const cartGroups = computed<CartGroup[]>(() => {
|
||||
console.log('[cartGroups] 计算购物车分组, cartItems count:', cartItems.value.length)
|
||||
const groups = new Map<string, CartGroup>()
|
||||
|
||||
cartItems.value.forEach((item: LocalCartItem) => {
|
||||
console.log('[cartGroups] item:', item.id, 'shopId:', item.shopId, 'shopName:', item.shopName)
|
||||
const shopKey = item.shopId
|
||||
if (!groups.has(shopKey)) {
|
||||
groups.set(shopKey, {
|
||||
@@ -244,6 +265,7 @@ const cartGroups = computed<CartGroup[]>(() => {
|
||||
|
||||
const groupArray: CartGroup[] = []
|
||||
groups.forEach((value: CartGroup) => {
|
||||
console.log('[cartGroups] group:', value.shopId, 'items count:', value.items.length)
|
||||
groupArray.push(value)
|
||||
})
|
||||
return groupArray
|
||||
@@ -266,8 +288,17 @@ const totalPrice = computed(() => {
|
||||
|
||||
// 检查店铺是否全选
|
||||
const isShopSelected = (shopId: string): boolean => {
|
||||
const shopItems = cartItems.value.filter((item: LocalCartItem): boolean => item.shopId === shopId)
|
||||
return shopItems.length > 0 && shopItems.every((item: LocalCartItem): boolean => item.selected)
|
||||
const shopItems: LocalCartItem[] = []
|
||||
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 = () => {
|
||||
@@ -285,25 +316,49 @@ onMounted(() => {
|
||||
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 () => {
|
||||
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
|
||||
|
||||
// 如果新页码没有数据,重置为第一页再试一次
|
||||
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) {
|
||||
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 ?? ''
|
||||
}
|
||||
})
|
||||
updateRecommendList(recommends)
|
||||
uni.showToast({
|
||||
title: '已更新推荐',
|
||||
icon: 'none'
|
||||
@@ -409,41 +464,84 @@ const toggleSelect = async (itemId: string) => {
|
||||
}
|
||||
|
||||
const toggleShopSelect = async (shopId: string) => {
|
||||
// 获取该店铺下所有商品的ID
|
||||
const shopItems = cartItems.value.filter((item: LocalCartItem): boolean => item.shopId === shopId)
|
||||
console.log('[toggleShopSelect] 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
|
||||
|
||||
const isAllShopSelected = shopItems.every((item: LocalCartItem): boolean => item.selected)
|
||||
const newState = !isAllShopSelected
|
||||
// 用 for 循环替代 every
|
||||
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>()
|
||||
cartItems.value.forEach(item => {
|
||||
if (item.shopId === shopId) {
|
||||
oldStates.set(item.id, item.selected)
|
||||
item.selected = newState
|
||||
}
|
||||
})
|
||||
cartItems.value = [...cartItems.value]
|
||||
// 创建全新的数组来触发响应式更新
|
||||
const newCartItems: LocalCartItem[] = []
|
||||
for (let i = 0; i < cartItems.value.length; i++) {
|
||||
const item = cartItems.value[i]
|
||||
const isMatch = compareStrings(item.shopId, shopId)
|
||||
if (isMatch) {
|
||||
console.log('[toggleShopSelect] updating item:', item.id, 'to selected:', newState)
|
||||
// 创建新的对象
|
||||
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
|
||||
const success = await supabaseService.batchUpdateCartItemSelection(shopItemIds, newState)
|
||||
|
||||
if (!success) {
|
||||
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({
|
||||
title: '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
// 重新加载数据以确保状态一致
|
||||
loadCartData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +766,7 @@ const navigateToShop = (shopId: string, merchantId: any) => {
|
||||
}
|
||||
|
||||
const goShopping = () => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
uni.switchTab({ url: '/pages/main/index' })
|
||||
}
|
||||
|
||||
const navigateToProduct = (product: any) => {
|
||||
@@ -655,7 +655,7 @@ async function addToCart(product: Product): Promise<void> {
|
||||
|
||||
// 导航函数
|
||||
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 {
|
||||
const id = (product.id ?? '').toString()
|
||||
if (id === '') return
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- pages/mall/consumer/index.uvue -->
|
||||
<!-- pages/main/index.uvue -->
|
||||
<template>
|
||||
<view class="medic-home">
|
||||
<!-- 智能顶部导航栏 - 添加滚动隐藏效果 -->
|
||||
@@ -404,7 +404,7 @@ const onParentCategoryClick = async (category: Category): Promise<void> => {
|
||||
console.log('[onParentCategoryClick] 没有二级分类,直接跳转到分类页')
|
||||
uni.setStorageSync('selectedCategory', category.id)
|
||||
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)
|
||||
const timestamp = Date.now()
|
||||
const randomParam = Math.random().toString(36).substring(2, 8)
|
||||
const url = `/pages/mall/consumer/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}×tamp=${timestamp}&random=${randomParam}`
|
||||
const url = `/pages/main/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}×tamp=${timestamp}&random=${randomParam}`
|
||||
|
||||
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)
|
||||
|
||||
// 构建带参数的URL,直接通过URL传递分类信息
|
||||
const url = `/pages/mall/consumer/category?categoryId=${categoryId}&name=${encodeURIComponent(categoryName)}×tamp=${timestamp}&random=${randomParam}`
|
||||
const url = `/pages/main/category?categoryId=${categoryId}&name=${encodeURIComponent(categoryName)}×tamp=${timestamp}&random=${randomParam}`
|
||||
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/category',
|
||||
url: '/pages/main/category',
|
||||
success: () => {
|
||||
// 通过 Storage 传递参数已在上面设置
|
||||
console.log('跳转分类页面成功,categoryId:', categoryId)
|
||||
@@ -1064,7 +1064,7 @@ export default {
|
||||
|
||||
goShopping() {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
},
|
||||
|
||||
@@ -406,7 +406,7 @@ const deleteAddress = () => {
|
||||
.form-group {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 0 16px;
|
||||
padding: 16px; /* 给整个组增加内边距 */
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
@@ -415,46 +415,62 @@ const deleteAddress = () => {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
padding: 0 16px;
|
||||
min-height: 52px;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 26px; /* 增加大圆角,使其从直角变为圆角 */
|
||||
margin-bottom: 12px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
border-radius: 16px; /* 详细地址区域也增加圆角 */
|
||||
}
|
||||
|
||||
.detail-item .label {
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 80px;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 15px;
|
||||
height: 44px; /* 增加高度 */
|
||||
line-height: 44px;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
padding: 0 4px;
|
||||
background-color: transparent; /* 确保输入框背景透明 */
|
||||
border: none; /* 强制去除安卓原生边框 */
|
||||
outline: none; /* 强制去除焦点边框 */
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
height: 80px;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
padding: 4px 0;
|
||||
background-color: transparent;
|
||||
border: none; /* 强制去除安卓原生边框 */
|
||||
outline: none; /* 强制去除焦点边框 */
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #ccc;
|
||||
color: #bbb;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@@ -481,10 +497,11 @@ const deleteAddress = () => {
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
padding: 6px 18px;
|
||||
padding: 8px 20px; /* 增大点击区域 */
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 20px;
|
||||
margin-right: 12px;
|
||||
margin-bottom: 8px; /* 增加底部间距 */
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
@@ -494,7 +511,7 @@ const deleteAddress = () => {
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 13px;
|
||||
font-size: 14px; /* 增大标签文字 */
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@@ -506,6 +523,7 @@ const deleteAddress = () => {
|
||||
/* 开关项 */
|
||||
.switch-item {
|
||||
justify-content: space-between;
|
||||
min-height: 72px; /* 增加开关项高度 */
|
||||
}
|
||||
|
||||
.switch-label-group {
|
||||
@@ -514,9 +532,9 @@ const deleteAddress = () => {
|
||||
}
|
||||
|
||||
.sub-label {
|
||||
font-size: 12px;
|
||||
font-size: 13px; /* 增大副标题 */
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* 智能填写 */
|
||||
|
||||
@@ -433,6 +433,8 @@ const sendMessage = async () => {
|
||||
|
||||
// 清空输入框
|
||||
inputMessage.value = ''
|
||||
// 发送消息时确保收起表情面板
|
||||
showEmoji.value = false
|
||||
|
||||
// 发送到 Supabase
|
||||
if (merchantId.value != '') {
|
||||
@@ -461,12 +463,17 @@ const simulateCustomerReply = async () => {
|
||||
// 插入表情
|
||||
function insertEmoji(emoji: string): void {
|
||||
inputMessage.value += emoji
|
||||
showEmoji.value = false // 选中表情后收起表情列表
|
||||
inputFocus.value = true
|
||||
}
|
||||
|
||||
// 显示表情选择器
|
||||
function showEmojiPicker(): void {
|
||||
showEmoji.value = !showEmoji.value
|
||||
if (showEmoji.value) {
|
||||
// 如果打开表情,通常需要收起键盘
|
||||
uni.hideKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示图片选择器
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 结算页面 -->
|
||||
<template>
|
||||
<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 v-if="selectedAddress" class="address-info">
|
||||
@@ -137,7 +137,7 @@
|
||||
<text class="popup-close" @click="showAddressPopup = false">×</text>
|
||||
</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">
|
||||
<text class="login-prompt-icon">🔒</text>
|
||||
@@ -215,7 +215,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="form-content" scroll-y="true">
|
||||
<scroll-view class="form-content" direction="vertical">
|
||||
<view class="form-section">
|
||||
<view class="form-item">
|
||||
<text class="form-label">收货人</text>
|
||||
@@ -1422,14 +1422,20 @@ const goToLogin = () => {
|
||||
.checkout-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1; /* replace height: 100vh */
|
||||
background-color: #f5f5f5;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 顶部栏 */
|
||||
.checkout-header {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@@ -1441,6 +1447,9 @@ const goToLogin = () => {
|
||||
|
||||
.checkout-content {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.address-section {
|
||||
@@ -1524,6 +1533,7 @@ const goToLogin = () => {
|
||||
.address-popup {
|
||||
background-color: #ffffff;
|
||||
width: 100%;
|
||||
height: 60vh; /* 显式高度保证内部 scroll-view direction="vertical" 生效 */
|
||||
border-radius: 20px 20px 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1619,13 +1629,13 @@ const goToLogin = () => {
|
||||
background-color: #f8f8f8;
|
||||
width: 92%;
|
||||
max-width: 500px;
|
||||
height: 85vh;
|
||||
height: 600px; /* 改用具体的像素高度,Android 端的 scroll-view 计算更稳健 */
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
@@ -1646,24 +1656,14 @@ const goToLogin = () => {
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
height: 100px; /* 进一步减小初始高度,确保安卓端 flex:1 接管 */
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
/* 在 Android 下,scroll-view 如果不给明确的高度且处于 flex 容器,必须通过 flex-grow 撑开 */
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
background-color: #ffffff;
|
||||
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 {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
|
||||
@@ -69,7 +69,7 @@ onMounted(() => {
|
||||
|
||||
const useCoupon = (coupon: Coupon) => {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -318,7 +318,7 @@ const addToCart = async (product: FavoriteType) => {
|
||||
|
||||
const goShopping = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ const loadMore = () => {
|
||||
|
||||
const goShopping = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -622,7 +622,7 @@ const rePurchase = async () => {
|
||||
if (successCount > 0) {
|
||||
uni.showToast({ title: '已加入购物车' })
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/cart' })
|
||||
uni.switchTab({ url: '/pages/main/cart' })
|
||||
}, 1000)
|
||||
} else {
|
||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<view class="orders-page">
|
||||
<!-- 顶部标题栏 -->
|
||||
<view class="orders-header">
|
||||
<view class="orders-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="header-search full-width">
|
||||
<input
|
||||
class="search-input"
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
direction="vertical"
|
||||
class="orders-content"
|
||||
refresher-enabled
|
||||
:refresher-triggered="refreshing"
|
||||
@@ -125,29 +125,30 @@
|
||||
</view>
|
||||
|
||||
<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>
|
||||
</view>
|
||||
|
||||
<view v-if="order.status === 3" class="action-buttons">
|
||||
<button class="action-btn view" @click="viewLogistics(order.id)">查看物流</button>
|
||||
<button class="action-btn confirm" @click="confirmReceipt(order.id)">确认收货</button>
|
||||
<button class="action-btn view" @click.stop="viewLogistics(order.id)">查看物流</button>
|
||||
<button class="action-btn confirm" @click.stop="confirmReceipt(order.id)">确认收货</button>
|
||||
<button class="action-btn refund" @click.stop="onApplyRefund(order)">申请售后</button>
|
||||
</view>
|
||||
|
||||
<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 repurchase" @click="repurchase(order)">再次购买</button>
|
||||
<button class="action-btn repurchase" @click.stop="repurchase(order)">再次购买</button>
|
||||
</view>
|
||||
|
||||
<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 v-if="order.status === 6" class="action-buttons">
|
||||
<button class="action-btn view" @click="viewOrderDetail(order.id)">查看详情</button>
|
||||
<button class="action-btn refund" @click="viewRefundProgress(order.id)">退款进度</button>
|
||||
<button class="action-btn view" @click.stop="viewOrderDetail(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 v-if="order.status === 7" class="action-buttons">
|
||||
@@ -222,6 +223,7 @@ const hasMore = ref<boolean>(true)
|
||||
const refreshing = ref<boolean>(false)
|
||||
const page = ref<number>(1)
|
||||
const activeTab = ref<string>('all')
|
||||
const statusBarHeight = ref<number>(0)
|
||||
const searchKeyword = ref<string>('')
|
||||
|
||||
// 订单标签页 - 使用 ref 以便整体替换
|
||||
@@ -504,6 +506,10 @@ const loadOrders = async () => {
|
||||
|
||||
// 生命周期
|
||||
onLoad((options) => {
|
||||
// 初始化状态栏高度
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
|
||||
|
||||
if (options == null) return
|
||||
const statusVal = options['status']
|
||||
if (statusVal != null) {
|
||||
@@ -518,7 +524,7 @@ onLoad((options) => {
|
||||
if (type === 'pending') activeTab.value = 'pending'
|
||||
else if (type === 'shipped') activeTab.value = 'delivering' // 映射到待收货
|
||||
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 会调用它
|
||||
const goReview = (order: any) => {
|
||||
const orderObj = order as Record<string, any>
|
||||
const products = orderObj['products'] as any[]
|
||||
const productIds = products.map((p: any) => {
|
||||
const pObj = p as Record<string, any>
|
||||
const pid = pObj['id']
|
||||
return pid != null ? pid as string : ''
|
||||
const goReview = (order: OrderItem) => {
|
||||
const productIds = order.products.map((p: OrderProduct): string => {
|
||||
return p.id
|
||||
}).join(',')
|
||||
const orderId = orderObj['id'] as string
|
||||
const orderId = order.id
|
||||
uni.navigateTo({
|
||||
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 obj = o as Record<string, any>
|
||||
return obj['id'] === orderId
|
||||
})
|
||||
const index = orders.value.findIndex((o: OrderItem): boolean => o.id === orderId)
|
||||
if (index !== -1) {
|
||||
const orderObj = orders.value[index] as Record<string, any>
|
||||
orderObj['status'] = 4
|
||||
orders.value[index].status = 4
|
||||
orders.value = [...orders.value]
|
||||
}
|
||||
|
||||
// 跳转到评价页面
|
||||
setTimeout(() => {
|
||||
const order = orders.value.find((o: any) => {
|
||||
const obj = o as Record<string, any>
|
||||
return obj['id'] === orderId
|
||||
})
|
||||
const order = orders.value.find((o: OrderItem): boolean => o.id === orderId)
|
||||
if (order != null) {
|
||||
goReview(order)
|
||||
}
|
||||
@@ -845,11 +840,10 @@ const confirmReceipt = (orderId: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
const repurchase = (order: any) => {
|
||||
const orderObj = order as Record<string, any>
|
||||
const products = orderObj['products'] as any[]
|
||||
const repurchase = (order: OrderItem) => {
|
||||
const products = order.products
|
||||
|
||||
if (products == null || products.length === 0) {
|
||||
if (products.length === 0) {
|
||||
uni.showToast({
|
||||
title: '订单无商品',
|
||||
icon: 'none'
|
||||
@@ -864,12 +858,12 @@ const repurchase = (order: any) => {
|
||||
let successCount = 0
|
||||
|
||||
for (let i = 0; i < products.length; i++) {
|
||||
const pObj = products[i] as Record<string, any>
|
||||
const productId = pObj['id'] as string
|
||||
const merchantId = orderObj['merchant_id'] as string
|
||||
const product = products[i]
|
||||
const productId = product.id
|
||||
const merchantId = order.merchant_id
|
||||
|
||||
if (productId != null && productId !== '') {
|
||||
supabaseService.addToCart(productId, 1, '', merchantId ?? '').then((success) => {
|
||||
supabaseService.addToCart(productId, 1, '', merchantId ?? '').then((success: boolean) => {
|
||||
completed++
|
||||
if (success) successCount++
|
||||
if (completed === total) {
|
||||
@@ -929,9 +923,8 @@ const viewOrderDetail = (orderId: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
const onApplyRefund = (order: any) => {
|
||||
const orderObj = order as Record<string, any>
|
||||
const orderId = orderObj['id']
|
||||
const onApplyRefund = (order: OrderItem) => {
|
||||
const orderId = order.id
|
||||
uni.navigateTo({
|
||||
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) => {
|
||||
if (action === '取消订单') {
|
||||
@@ -963,6 +982,8 @@ const handleOrderAction = (order: OrderItem, action: string) => {
|
||||
deleteOrder(order.id)
|
||||
} else if (action === '退款进度') {
|
||||
viewRefundProgress(order.id)
|
||||
} else if (action === '取消退款') {
|
||||
cancelRefund(order.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,7 +1003,7 @@ const showOrderMenu = (order: OrderItem) => {
|
||||
} else if (status === 5) {
|
||||
actions = ['删除订单', '再次购买', '联系卖家']
|
||||
} else if (status === 6) {
|
||||
actions = ['退款进度', '联系卖家']
|
||||
actions = ['取消退款', '退款进度', '联系卖家']
|
||||
} else if (status === 7) {
|
||||
actions = ['再次购买', '联系卖家']
|
||||
}
|
||||
@@ -1001,59 +1022,75 @@ const navigateToSearch = () => {
|
||||
uni.navigateTo({ url: '/pages/mall/consumer/search' })
|
||||
}
|
||||
|
||||
const navigateToProduct = (product: any) => {
|
||||
const productObj = product as Record<string, any>
|
||||
const productId = productObj['id']
|
||||
const navigateToProduct = (product: OrderProduct) => {
|
||||
const productId = product.id
|
||||
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${productId}` })
|
||||
}
|
||||
|
||||
const goShopping = () => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
uni.switchTab({ url: '/pages/main/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.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;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* #endif */
|
||||
/* #ifdef H5 */
|
||||
position: relative;
|
||||
/* #endif */
|
||||
background-color: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
/* 头部:确保显式可见且不被遮挡 */
|
||||
.orders-header {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
background-color: #ffffff;
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
z-index: 999;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-search.full-width {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
border: 1px solid #ddd;
|
||||
line-height: normal;
|
||||
border: 1px solid #dddddd;
|
||||
border-radius: 18px;
|
||||
padding: 0 40px 0 16px;
|
||||
font-size: 14px;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
color: #333333;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
@@ -1086,7 +1123,7 @@ const goShopping = () => {
|
||||
/* cursor: pointer; removed */
|
||||
}
|
||||
|
||||
/* 标签页 */
|
||||
/* 标签页容器:确保在安卓下层级正确且不随内容滚动 */
|
||||
.order-tabs-fixed-container {
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
@@ -1094,26 +1131,25 @@ const goShopping = () => {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
height: 50px;
|
||||
z-index: 100 !important;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 安卓端滚动修复 */
|
||||
/* 安卓端滚动修复:关键容器 */
|
||||
.orders-content {
|
||||
flex: 1;
|
||||
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;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
/* 顶部内容区域必须显式不可伸缩,防止撑占滚动空间 */
|
||||
.orders-header, .order-tabs-fixed-container {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tab-item-fixed {
|
||||
@@ -1456,6 +1492,16 @@ const goShopping = () => {
|
||||
border-color: #34c759;
|
||||
}
|
||||
|
||||
.action-btn.refund {
|
||||
color: #666;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.action-btn.cancel {
|
||||
color: #ff6b6b;
|
||||
border-color: #ff6b6b;
|
||||
}
|
||||
|
||||
.action-btn.review {
|
||||
color: #ff9500;
|
||||
border-color: #ff9500;
|
||||
|
||||
@@ -95,7 +95,7 @@ const viewOrder = () => {
|
||||
|
||||
const goHome = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- 支付页面 -->
|
||||
<template>
|
||||
<view class="payment-page">
|
||||
<view class="payment-content">
|
||||
<scroll-view class="payment-content" direction="vertical">
|
||||
<!-- 价格明细 -->
|
||||
<view class="price-detail-section">
|
||||
<text class="section-title">价格明细</text>
|
||||
@@ -71,7 +71,7 @@
|
||||
</view>
|
||||
<text class="forgot-password" @click="forgotPassword">忘记密码?</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部支付按钮 -->
|
||||
<view class="payment-bottom">
|
||||
@@ -614,8 +614,15 @@ onUnmounted(() => {
|
||||
.payment-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.payment-header {
|
||||
|
||||
@@ -832,7 +832,7 @@ export default {
|
||||
},
|
||||
|
||||
goToCart() {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/cart' })
|
||||
uni.switchTab({ url: '/pages/main/cart' })
|
||||
},
|
||||
|
||||
decreaseQuantity() {
|
||||
|
||||
@@ -107,7 +107,7 @@ onMounted(() => {
|
||||
|
||||
const usePacket = (item: RedPacket) => {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<!-- 评价页面 -->
|
||||
<template>
|
||||
<view class="review-page">
|
||||
<!-- 顶部栏 -->
|
||||
<!-- 顶部栏:已移除“发表评价”文字 -->
|
||||
<view class="review-header">
|
||||
<text class="back-btn" @click="goBack">‹</text>
|
||||
<text class="header-title">评价商品</text>
|
||||
<view class="header-back" @click="goBack">
|
||||
<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>
|
||||
|
||||
<scroll-view class="review-content" direction="vertical">
|
||||
@@ -145,7 +148,6 @@
|
||||
<script setup lang="uts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
|
||||
type OrderType = {
|
||||
@@ -195,72 +197,58 @@ const isSubmitting = ref<boolean>(false)
|
||||
|
||||
const loadOrderData = async (): Promise<void> => {
|
||||
try {
|
||||
const orderRes = await supa
|
||||
.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>
|
||||
}
|
||||
console.log('[loadOrderData] 开始加载订单数据, orderId:', orderId.value)
|
||||
|
||||
const processedItems: Array<OrderItemType> = []
|
||||
for (let i: number = 0; i < itemsArray.length; i++) {
|
||||
const item = itemsArray[i] as UTSJSONObject
|
||||
const productObjRaw = item.get('product')
|
||||
const productObj = (productObjRaw != null) ? (productObjRaw as UTSJSONObject) : null
|
||||
const imagesArrRaw = (productObj != null) ? productObj.get('images') : null
|
||||
const imagesArr = (imagesArrRaw != null) ? (imagesArrRaw as Array<string>) : []
|
||||
const firstImage = (imagesArr.length > 0) ? imagesArr[0] : '/static/default-product.png'
|
||||
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)
|
||||
// 使用 supabaseService 获取订单详情
|
||||
const orderDetailRaw = await supabaseService.getOrderDetail(orderId.value)
|
||||
console.log('[loadOrderData] orderDetailRaw:', JSON.stringify(orderDetailRaw))
|
||||
|
||||
if (orderDetailRaw == null) {
|
||||
console.error('加载订单失败: 未找到订单')
|
||||
uni.showToast({ title: '订单不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
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 newRatings: Array<number> = []
|
||||
const newContents: Array<string> = []
|
||||
@@ -274,23 +262,25 @@ const loadOrderData = async (): Promise<void> => {
|
||||
contents.value = newContents
|
||||
images.value = newImages
|
||||
|
||||
const orderObj = order.value as UTSJSONObject
|
||||
const merchantId = orderObj.getString('merchant_id')
|
||||
if (merchantId != null && merchantId !== '') {
|
||||
const merchantRes = await supa
|
||||
.from('ml_shops')
|
||||
.select('id, shop_name, rating')
|
||||
.eq('id', merchantId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (merchantRes.error == null && merchantRes.data != null) {
|
||||
merchant.value = merchantRes.data as MerchantType
|
||||
// 获取商家信息
|
||||
const orderObj = order.value
|
||||
if (orderObj != null) {
|
||||
const merchantId = orderObj.merchant_id
|
||||
if (merchantId != '') {
|
||||
const shopInfo = await supabaseService.getShopByMerchantId(merchantId)
|
||||
if (shopInfo != null) {
|
||||
merchant.value = {
|
||||
id: shopInfo.id,
|
||||
shop_name: shopInfo.shop_name,
|
||||
rating: shopInfo.rating_avg ?? 5
|
||||
} as MerchantType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('加载订单数据异常:', err)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,10 +312,19 @@ const formatTime = (timeStr?: string): string => {
|
||||
|
||||
const getSpecText = (specs: any | null): string => {
|
||||
if (specs == null) return ''
|
||||
if (specs instanceof UTSJSONObject) {
|
||||
return '规格信息'
|
||||
if (typeof specs === 'string') return specs as string
|
||||
|
||||
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 {
|
||||
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;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 24px;
|
||||
color: #333333;
|
||||
padding: 5px;
|
||||
margin-right: 15px;
|
||||
.back-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
.header-title-placeholder {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.review-content {
|
||||
@@ -632,39 +645,46 @@ const goBack = (): void => {
|
||||
|
||||
.rating-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.rating-label {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
margin-right: 15px;
|
||||
font-weight: 500;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.rating-stars {
|
||||
display: flex;
|
||||
/* gap: 10px; removed */
|
||||
}
|
||||
|
||||
.rating-stars.small {
|
||||
/* gap: 5px; removed */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
font-size: 24px;
|
||||
color: #cccccc;
|
||||
margin-right: 10px;
|
||||
font-size: 26px;
|
||||
margin-right: 8px;
|
||||
color: #e0e0e0;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.star-icon.active {
|
||||
color: #ff5000;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.rating-text {
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
color: #999999;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.rating-stars.small {
|
||||
/* gap: 5px; removed */
|
||||
}
|
||||
|
||||
.content-section {
|
||||
@@ -783,28 +803,49 @@ const goBack = (): void => {
|
||||
|
||||
.merchant-section {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding: 20px 15px;
|
||||
margin-top: 15px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
/* display: block; removed */
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #ff5000;
|
||||
}
|
||||
|
||||
.merchant-rating {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.rating-item {
|
||||
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 {
|
||||
|
||||
@@ -821,7 +821,7 @@ const goBack = () => {
|
||||
} else {
|
||||
// 如果只有一页(由于深链接或重定向),返回首页
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
url: '/pages/main/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ onBackPress((options) => {
|
||||
// 无论是什么触发的返回(系统返回键或导航栏返回按钮),都跳转到profile
|
||||
// 注意:onBackPress 只能在 page 中使用,component 中无效
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/profile'
|
||||
url: '/pages/main/profile'
|
||||
})
|
||||
// 返回 true 表示阻止默认返回行为
|
||||
return true
|
||||
|
||||
@@ -115,7 +115,7 @@ const goToShop = (shop: FollowedShop): void => {
|
||||
}
|
||||
|
||||
const goHome = (): void => {
|
||||
uni.switchTab({ url: '/pages/mall/consumer/index' })
|
||||
uni.switchTab({ url: '/pages/main/index' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -135,7 +135,7 @@ const confirmSubscribe = async () => {
|
||||
if (ins != null && ins.error == null) {
|
||||
uni.showToast({ title: '订阅成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: '/pages/mall/consumer/profile' })
|
||||
uni.redirectTo({ url: '/pages/main/profile' })
|
||||
}, 600)
|
||||
} else {
|
||||
uni.showToast({ title: ins?.error?.message ?? '订阅失败', icon: 'none' })
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
try {
|
||||
const sessionInfo = supa.getSession()
|
||||
if (sessionInfo != null && sessionInfo.user != null) {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/main/index' })
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -182,12 +182,12 @@ const checkLoginStatus = (): void => {
|
||||
const opts = currentPage.options as UTSJSONObject
|
||||
const redirect = opts.getString('redirect')
|
||||
if (redirect != null && redirect != '') {
|
||||
uni.reLaunch({ url: `/pages/mall/consumer/index` })
|
||||
uni.reLaunch({ url: `/pages/main/index` })
|
||||
} else {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/main/index' })
|
||||
}
|
||||
} else {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/main/index' })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -291,7 +291,7 @@ const handleLogin = async () => {
|
||||
|
||||
uni.showToast({ title: '管理员登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/main/index' })
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
@@ -360,7 +360,7 @@ const handleLogin = async () => {
|
||||
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/mall/consumer/index' })
|
||||
uni.reLaunch({ url: '/pages/main/index' })
|
||||
}, 500)
|
||||
} catch (err) {
|
||||
console.error('登录错误:', err)
|
||||
|
||||
25
project.config.json
Normal file
25
project.config.json
Normal 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": {}
|
||||
}
|
||||
14
project.private.config.json
Normal file
14
project.private.config.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -194,11 +194,12 @@ export class AkReq {
|
||||
resolve(result);
|
||||
},
|
||||
fail: (err) => {
|
||||
const errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;
|
||||
const result = AkReq.createResponse<any>(
|
||||
err.errCode,
|
||||
err.data ?? {},
|
||||
errStatus,
|
||||
{} 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);
|
||||
}
|
||||
@@ -302,11 +303,12 @@ export class AkReq {
|
||||
resolve(result);
|
||||
},
|
||||
fail: (err) => {
|
||||
const errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;
|
||||
const result = AkReq.createResponse<any>(
|
||||
err.errCode,
|
||||
err.data ?? {},
|
||||
errStatus,
|
||||
{} 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);
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user