consumer模块完成度95%,能编译在安卓端运行,在解决数据获取和页面布局问题
This commit is contained in:
@@ -7,11 +7,11 @@
|
||||
<text class="header-title">评价商品</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="review-content" scroll-y>
|
||||
<scroll-view class="review-content" direction="vertical">
|
||||
<!-- 订单信息 -->
|
||||
<view class="order-section">
|
||||
<text class="order-no">订单号: {{ order?.order_no }}</text>
|
||||
<text class="order-time">下单时间: {{ formatTime(order?.created_at) }}</text>
|
||||
<text class="order-no">订单号: {{ order != null ? order.order_no : '' }}</text>
|
||||
<text class="order-time">下单时间: {{ formatTime(order != null ? order.created_at : '') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 商品评价 -->
|
||||
@@ -21,7 +21,7 @@
|
||||
<image class="product-image" :src="item.product_image ?? '/static/default-product.png'" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.product_name }}</text>
|
||||
<text v-if="item.sku_specifications" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
|
||||
<text v-if="item.sku_specifications != null" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -133,9 +133,9 @@
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn"
|
||||
:class="{ disabled: !canSubmit || isSubmitting }"
|
||||
:class="{ disabled: canSubmit === false || isSubmitting }"
|
||||
@click="submitReview">
|
||||
<text v-if="!isSubmitting" class="submit-text">提交评价</text>
|
||||
<text v-if="isSubmitting === false" class="submit-text">提交评价</text>
|
||||
<text v-else class="submit-text">提交中...</text>
|
||||
</button>
|
||||
</view>
|
||||
@@ -146,17 +146,32 @@
|
||||
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 = {
|
||||
id: string
|
||||
order_no: string
|
||||
created_at: string
|
||||
merchant_id: string
|
||||
}
|
||||
|
||||
type OrderItemType = {
|
||||
id: string
|
||||
product_id: string
|
||||
id: number
|
||||
order_id: number
|
||||
product_id: number
|
||||
product_name: string
|
||||
product_image: string
|
||||
sku_specifications: any
|
||||
sku_specifications: any | null
|
||||
price: number
|
||||
quantity: number
|
||||
}
|
||||
|
||||
type MerchantRatingType = {
|
||||
description: number
|
||||
logistics: number
|
||||
service: number
|
||||
}
|
||||
|
||||
type MerchantType = {
|
||||
id: string
|
||||
shop_name: string
|
||||
@@ -164,85 +179,113 @@ type MerchantType = {
|
||||
}
|
||||
|
||||
const orderId = ref<string>('')
|
||||
const order = ref<any>({})
|
||||
const order = ref<OrderType | null>(null)
|
||||
const orderItems = ref<Array<OrderItemType>>([])
|
||||
const merchant = ref<MerchantType | null>(null)
|
||||
const ratings = ref<Array<number>>([])
|
||||
const contents = ref<Array<string>>([])
|
||||
const images = ref<Array<Array<string>>>([])
|
||||
const anonymous = ref<boolean>(false)
|
||||
const merchantRating = ref({
|
||||
const merchantRating = ref<MerchantRatingType>({
|
||||
description: 5,
|
||||
logistics: 5,
|
||||
service: 5
|
||||
})
|
||||
} as MerchantRatingType)
|
||||
const isSubmitting = ref<boolean>(false)
|
||||
|
||||
// 计算属性
|
||||
const canSubmit = computed(() => {
|
||||
// 检查是否所有商品都已评分
|
||||
if (ratings.value.length === 0) return false
|
||||
return ratings.value.every(rating => rating > 0)
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onLoad((options: any) => {
|
||||
const optObj = (options instanceof UTSJSONObject) ? (options as UTSJSONObject) : (JSON.parse(JSON.stringify(options ?? {})) as UTSJSONObject)
|
||||
orderId.value = optObj.getString('orderId') ?? ''
|
||||
if (orderId.value != '') loadOrderData()
|
||||
})
|
||||
|
||||
// 加载订单数据
|
||||
const loadOrderData = async () => {
|
||||
const loadOrderData = async (): Promise<void> => {
|
||||
try {
|
||||
const { data: orderData, error: orderError } = await supa
|
||||
const orderRes = await supa
|
||||
.from('ml_orders')
|
||||
.select('*')
|
||||
.eq('id', orderId.value)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (orderError !== null) {
|
||||
console.error('加载订单失败:', orderError)
|
||||
if (orderRes.error != null) {
|
||||
console.error('加载订单失败:', orderRes.error)
|
||||
return
|
||||
}
|
||||
|
||||
order.value = orderData
|
||||
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 { data: itemsData, error: itemsError } = await supa
|
||||
const itemsRes = await supa
|
||||
.from('ml_order_items')
|
||||
.select(`
|
||||
*,
|
||||
product:product_id(images)
|
||||
`)
|
||||
.eq('order_id', orderId.value)
|
||||
.execute()
|
||||
|
||||
if (itemsError !== null) {
|
||||
console.error('加载订单商品失败:', itemsError)
|
||||
if (itemsRes.error != null) {
|
||||
console.error('加载订单商品失败:', itemsRes.error)
|
||||
return
|
||||
}
|
||||
|
||||
orderItems.value = (itemsData ?? []).map((item: any) => ({
|
||||
...item,
|
||||
product_image: item.product?.images?.[0] ?? '/static/default-product.png'
|
||||
}))
|
||||
const rawData = itemsRes.data
|
||||
let itemsArray: Array<any> = []
|
||||
if (rawData != null) {
|
||||
itemsArray = rawData as Array<any>
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
orderItems.value = processedItems
|
||||
|
||||
// 初始化评分和内容数组
|
||||
const count = orderItems.value.length
|
||||
ratings.value = new Array(count).fill(5)
|
||||
contents.value = new Array(count).fill('')
|
||||
images.value = new Array(count).fill([])
|
||||
const newRatings: Array<number> = []
|
||||
const newContents: Array<string> = []
|
||||
const newImages: Array<Array<string>> = []
|
||||
for (let i: number = 0; i < count; i++) {
|
||||
newRatings.push(5)
|
||||
newContents.push('')
|
||||
newImages.push([])
|
||||
}
|
||||
ratings.value = newRatings
|
||||
contents.value = newContents
|
||||
images.value = newImages
|
||||
|
||||
// 加载商家信息
|
||||
if (order.value.merchant_id) {
|
||||
const { data: merchantData, error: merchantError } = await supa
|
||||
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', order.value.merchant_id)
|
||||
.eq('id', merchantId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (merchantError == null) {
|
||||
merchant.value = merchantData
|
||||
if (merchantRes.error == null && merchantRes.data != null) {
|
||||
merchant.value = merchantRes.data as MerchantType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +294,22 @@ const loadOrderData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const canSubmit = computed((): boolean => {
|
||||
if (ratings.value.length === 0) return false
|
||||
for (let i: number = 0; i < ratings.value.length; i++) {
|
||||
if (ratings.value[i] <= 0) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (options != null) {
|
||||
const optObj = options as UTSJSONObject
|
||||
orderId.value = optObj.getString('orderId') ?? ''
|
||||
if (orderId.value != '') loadOrderData()
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr?: string): string => {
|
||||
if (timeStr == null) return ''
|
||||
@@ -261,38 +320,52 @@ const formatTime = (timeStr?: string): string => {
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 获取规格文本
|
||||
const getSpecText = (specs: any): string => {
|
||||
const getSpecText = (specs: any | null): string => {
|
||||
if (specs == null) return ''
|
||||
if (typeof specs === 'object') {
|
||||
return Object.keys(specs)
|
||||
.map(key => `${key}: ${specs[key]}`)
|
||||
.join('; ')
|
||||
if (specs instanceof UTSJSONObject) {
|
||||
return '规格信息'
|
||||
}
|
||||
return String(specs)
|
||||
return specs as string
|
||||
}
|
||||
|
||||
// 获取评分文本
|
||||
const getRatingText = (rating: number): string => {
|
||||
const texts = ['非常差', '差', '一般', '好', '非常好']
|
||||
return texts[rating - 1] ?? '未评价'
|
||||
if (rating === 1) return '非常差'
|
||||
if (rating === 2) return '差'
|
||||
if (rating === 3) return '一般'
|
||||
if (rating === 4) return '好'
|
||||
if (rating === 5) return '非常好'
|
||||
return '未评价'
|
||||
}
|
||||
|
||||
// 设置商品评分
|
||||
const setRating = (index: number, rating: number) => {
|
||||
ratings.value[index] = rating
|
||||
ratings.value = [...ratings.value]
|
||||
// 触发响应式更新
|
||||
const newRatings: number[] = []
|
||||
for (let i: number = 0; i < ratings.value.length; i++) {
|
||||
newRatings.push(ratings.value[i])
|
||||
}
|
||||
ratings.value = newRatings
|
||||
}
|
||||
|
||||
// 设置商家评分
|
||||
const setMerchantRating = (type: keyof typeof merchantRating.value, rating: number) => {
|
||||
merchantRating.value[type] = rating
|
||||
merchantRating.value = { ...merchantRating.value }
|
||||
const setMerchantRating = (type: string, rating: number) => {
|
||||
if (type === 'description') {
|
||||
merchantRating.value.description = rating
|
||||
} else if (type === 'logistics') {
|
||||
merchantRating.value.logistics = rating
|
||||
} else if (type === 'service') {
|
||||
merchantRating.value.service = rating
|
||||
}
|
||||
}
|
||||
|
||||
// 切换匿名
|
||||
const toggleAnonymous = (event: any) => {
|
||||
anonymous.value = event.detail.value
|
||||
const eventObj = event as UTSJSONObject
|
||||
const detailRaw = eventObj.get('detail')
|
||||
const detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject())
|
||||
const valueRaw = detail.get('value')
|
||||
anonymous.value = valueRaw != null ? (valueRaw as boolean) : false
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
@@ -312,17 +385,27 @@ const uploadImage = async (index: number) => {
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const tempFiles = res.tempFilePaths
|
||||
const resObj = res as UTSJSONObject
|
||||
const tempFilesRaw = resObj.get('tempFilePaths')
|
||||
const tempFiles = tempFilesRaw != null ? (tempFilesRaw as Array<string>) : []
|
||||
|
||||
// 模拟上传过程
|
||||
uni.showLoading({
|
||||
title: '上传中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
// 这里应该调用真实的上传接口
|
||||
images.value[index].push(...tempFiles)
|
||||
images.value = [...images.value]
|
||||
for (let i: number = 0; i < tempFiles.length; i++) {
|
||||
images.value[index].push(tempFiles[i])
|
||||
}
|
||||
const newImages: Array<Array<string>> = []
|
||||
for (let i: number = 0; i < images.value.length; i++) {
|
||||
const innerArray: Array<string> = []
|
||||
for (let j: number = 0; j < images.value[i].length; j++) {
|
||||
innerArray.push(images.value[i][j])
|
||||
}
|
||||
newImages.push(innerArray)
|
||||
}
|
||||
images.value = newImages
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
@@ -337,12 +420,28 @@ const uploadImage = async (index: number) => {
|
||||
// 删除图片
|
||||
const deleteImage = (index: number, imgIndex: number) => {
|
||||
images.value[index].splice(imgIndex, 1)
|
||||
images.value = [...images.value]
|
||||
// 触发响应式更新
|
||||
const newImages: string[][] = []
|
||||
for (let i: number = 0; i < images.value.length; i++) {
|
||||
const innerArray: string[] = []
|
||||
for (let j: number = 0; j < images.value[i].length; j++) {
|
||||
innerArray.push(images.value[i][j])
|
||||
}
|
||||
newImages.push(innerArray)
|
||||
}
|
||||
images.value = newImages
|
||||
}
|
||||
|
||||
// 提交评价
|
||||
const submitReview = async () => {
|
||||
if (!canSubmit.value || isSubmitting.value) return
|
||||
// 获取当前用户ID
|
||||
const getCurrentUserId = (): string => {
|
||||
const userStore = uni.getStorageSync('userInfo')
|
||||
if (userStore == null) return ''
|
||||
const userInfo = userStore as UTSJSONObject
|
||||
return userInfo.getString('id') ?? ''
|
||||
}
|
||||
|
||||
const submitReview = async (): Promise<void> => {
|
||||
if (canSubmit.value === false || isSubmitting.value) return
|
||||
|
||||
isSubmitting.value = true
|
||||
|
||||
@@ -356,57 +455,61 @@ const submitReview = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 提交商品评价
|
||||
const productReviews = orderItems.value.map((item, index) => ({
|
||||
user_id: userId,
|
||||
product_id: item.product_id,
|
||||
order_id: orderId.value,
|
||||
rating: ratings.value[index],
|
||||
content: contents.value[index] != '' ? contents.value[index] : '',
|
||||
images: images.value[index],
|
||||
is_anonymous: anonymous.value
|
||||
}))
|
||||
|
||||
const { error: reviewsError } = await supa
|
||||
.from('ml_product_reviews')
|
||||
.insert(productReviews)
|
||||
|
||||
if (reviewsError !== null) {
|
||||
throw reviewsError
|
||||
type ProductReviewType = {
|
||||
user_id: string,
|
||||
product_id: number,
|
||||
order_id: string,
|
||||
rating: number,
|
||||
content: string,
|
||||
images: Array<string>,
|
||||
is_anonymous: boolean
|
||||
}
|
||||
const productReviews: Array<UTSJSONObject> = []
|
||||
for (let index: number = 0; index < orderItems.value.length; index++) {
|
||||
const item = orderItems.value[index]
|
||||
const reviewObj: UTSJSONObject = new UTSJSONObject()
|
||||
reviewObj.set('user_id', userId)
|
||||
reviewObj.set('product_id', item.product_id)
|
||||
reviewObj.set('order_id', orderId.value)
|
||||
reviewObj.set('rating', ratings.value[index])
|
||||
reviewObj.set('content', contents.value[index] != '' ? contents.value[index] : '')
|
||||
reviewObj.set('images', images.value[index])
|
||||
reviewObj.set('is_anonymous', anonymous.value)
|
||||
productReviews.push(reviewObj)
|
||||
}
|
||||
|
||||
// 提交店铺评价
|
||||
if (merchant.value) {
|
||||
const merchantReview = {
|
||||
user_id: userId,
|
||||
shop_id: merchant.value.id,
|
||||
order_id: orderId.value,
|
||||
description_rating: merchantRating.value.description,
|
||||
logistics_rating: merchantRating.value.logistics,
|
||||
service_rating: merchantRating.value.service
|
||||
const reviewsSuccess = await supabaseService.submitProductReviews(productReviews)
|
||||
if (reviewsSuccess == false) {
|
||||
uni.showToast({
|
||||
title: '提交失败',
|
||||
icon: 'none'
|
||||
})
|
||||
isSubmitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (merchant.value != null) {
|
||||
type MerchantReviewType = {
|
||||
user_id: string,
|
||||
shop_id: string,
|
||||
order_id: string,
|
||||
description_rating: number,
|
||||
logistics_rating: number,
|
||||
service_rating: number
|
||||
}
|
||||
const merchantReviewObj: UTSJSONObject = new UTSJSONObject()
|
||||
merchantReviewObj.set('user_id', userId)
|
||||
merchantReviewObj.set('shop_id', merchant.value.id)
|
||||
merchantReviewObj.set('order_id', orderId.value)
|
||||
merchantReviewObj.set('description_rating', merchantRating.value.description)
|
||||
merchantReviewObj.set('logistics_rating', merchantRating.value.logistics)
|
||||
merchantReviewObj.set('service_rating', merchantRating.value.service)
|
||||
|
||||
const { error: merchantError } = await supa
|
||||
.from('ml_shop_reviews')
|
||||
.insert(merchantReview)
|
||||
|
||||
if (merchantError !== null) {
|
||||
console.error('提交店铺评价失败:', merchantError)
|
||||
}
|
||||
await supabaseService.submitShopReview(merchantReviewObj)
|
||||
}
|
||||
|
||||
// 更新订单状态为已评价 (如果需要标记为已评价,可以在这里处理,例如 status=5 implies Reviewed or keeping at 4)
|
||||
// 这里保持为 4 (Completed)
|
||||
const { error: orderError } = await supa
|
||||
.from('ml_orders')
|
||||
.update({ order_status: 4 })
|
||||
.eq('id', orderId.value)
|
||||
await supabaseService.updateOrderStatus(orderId.value, 4)
|
||||
|
||||
if (orderError !== null) {
|
||||
console.error('更新订单状态失败:', orderError)
|
||||
}
|
||||
|
||||
// 显示成功提示
|
||||
uni.showToast({
|
||||
title: '评价成功',
|
||||
icon: 'success',
|
||||
@@ -429,14 +532,8 @@ const submitReview = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
const getCurrentUserId = (): string => {
|
||||
const userStore = uni.getStorageSync('userInfo')
|
||||
return userStore?.getString('id') ?? ''
|
||||
}
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
const goBack = (): void => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
@@ -503,10 +600,6 @@ const goBack = () => {
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.product-review:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
@@ -643,9 +736,7 @@ const goBack = () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
|
||||
.upload-btn {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
@@ -738,10 +829,6 @@ margin-right: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tip-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
|
||||
Reference in New Issue
Block a user