完成consumer端同步
This commit is contained in:
@@ -0,0 +1,896 @@
|
||||
<!-- 评价页面 -->
|
||||
<template>
|
||||
<view class="review-page">
|
||||
<!-- 顶部栏:已移除“发表评价”文字 -->
|
||||
<view class="review-header">
|
||||
<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">
|
||||
<!-- 订单信息 -->
|
||||
<view class="order-section">
|
||||
<text class="order-no">订单号: {{ order != null ? order.order_no : '' }}</text>
|
||||
<text class="order-time">下单时间: {{ formatTime(order != null ? order.created_at : '') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 商品评价 -->
|
||||
<view class="products-section">
|
||||
<view v-for="(item, index) in orderItems" :key="item.id" class="product-review">
|
||||
<view class="product-header">
|
||||
<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 != null" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评分 -->
|
||||
<view class="rating-section">
|
||||
<text class="rating-label">评分</text>
|
||||
<view class="rating-stars">
|
||||
<text v-for="star in 5"
|
||||
:key="star"
|
||||
class="star-icon"
|
||||
:class="{ active: star <= ratings[index] }"
|
||||
@click="setRating(index, star)">
|
||||
⭐
|
||||
</text>
|
||||
</view>
|
||||
<text class="rating-text">{{ getRatingText(ratings[index]) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 评价内容 -->
|
||||
<view class="content-section">
|
||||
<textarea class="review-textarea"
|
||||
v-model="contents[index]"
|
||||
placeholder="请写下您的使用感受,分享给其他小伙伴吧"
|
||||
maxlength="500"></textarea>
|
||||
<text class="word-count">{{ contents[index]?.length || 0 }}/500</text>
|
||||
</view>
|
||||
|
||||
<!-- 图片上传 -->
|
||||
<view class="images-section">
|
||||
<text class="images-label">上传图片(可选)</text>
|
||||
<view class="images-grid">
|
||||
<view v-for="(image, imgIndex) in images[index]"
|
||||
:key="imgIndex"
|
||||
class="image-item">
|
||||
<image class="uploaded-image" :src="image" />
|
||||
<text class="delete-image" @click="deleteImage(index, imgIndex)">×</text>
|
||||
</view>
|
||||
<view v-if="images[index].length < 9"
|
||||
class="upload-btn"
|
||||
@click="uploadImage(index)">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-text">添加图片</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 匿名评价 -->
|
||||
<view class="anonymous-section">
|
||||
<view class="anonymous-switch">
|
||||
<text class="switch-label">匿名评价</text>
|
||||
<switch :checked="anonymous" @change="toggleAnonymous" />
|
||||
</view>
|
||||
<text class="anonymous-tip">评价内容对其他用户不可见</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 店铺评价 -->
|
||||
<view v-if="merchant" class="merchant-section">
|
||||
<text class="section-title">店铺评价</text>
|
||||
<view class="merchant-rating">
|
||||
<text class="rating-item">商品描述相符</text>
|
||||
<view class="rating-stars small">
|
||||
<text v-for="star in 5"
|
||||
:key="star"
|
||||
class="star-icon"
|
||||
:class="{ active: star <= merchantRating.description }"
|
||||
@click="setMerchantRating('description', star)">
|
||||
⭐
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="merchant-rating">
|
||||
<text class="rating-item">物流服务</text>
|
||||
<view class="rating-stars small">
|
||||
<text v-for="star in 5"
|
||||
:key="star"
|
||||
class="star-icon"
|
||||
:class="{ active: star <= merchantRating.logistics }"
|
||||
@click="setMerchantRating('logistics', star)">
|
||||
⭐
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="merchant-rating">
|
||||
<text class="rating-item">服务态度</text>
|
||||
<view class="rating-stars small">
|
||||
<text v-for="star in 5"
|
||||
:key="star"
|
||||
class="star-icon"
|
||||
:class="{ active: star <= merchantRating.service }"
|
||||
@click="setMerchantRating('service', star)">
|
||||
⭐
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评价提示 -->
|
||||
<view class="tips-section">
|
||||
<text class="tip-title">评价须知</text>
|
||||
<text class="tip-item">1. 评价后不可修改,请谨慎评价</text>
|
||||
<text class="tip-item">2. 上传图片需为真实商品照片</text>
|
||||
<text class="tip-item">3. 恶意评价将被删除并限制评价功能</text>
|
||||
<text class="tip-item">4. 优质评价可获得积分奖励</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<button class="submit-btn"
|
||||
:class="{ disabled: canSubmit === false || isSubmitting }"
|
||||
@click="submitReview">
|
||||
<text v-if="isSubmitting === false" class="submit-text">提交评价</text>
|
||||
<text v-else class="submit-text">提交中...</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
|
||||
type OrderType = {
|
||||
id: string
|
||||
order_no: string
|
||||
created_at: string
|
||||
merchant_id: string
|
||||
}
|
||||
|
||||
type OrderItemType = {
|
||||
id: number
|
||||
order_id: number
|
||||
product_id: number
|
||||
product_name: string
|
||||
product_image: string
|
||||
sku_specifications: any | null
|
||||
price: number
|
||||
quantity: number
|
||||
}
|
||||
|
||||
type MerchantRatingType = {
|
||||
description: number
|
||||
logistics: number
|
||||
service: number
|
||||
}
|
||||
|
||||
type MerchantType = {
|
||||
id: string
|
||||
shop_name: string
|
||||
rating: number
|
||||
}
|
||||
|
||||
const orderId = ref<string>('')
|
||||
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<MerchantRatingType>({
|
||||
description: 5,
|
||||
logistics: 5,
|
||||
service: 5
|
||||
} as MerchantRatingType)
|
||||
const isSubmitting = ref<boolean>(false)
|
||||
|
||||
const loadOrderData = async (): Promise<void> => {
|
||||
try {
|
||||
console.log('[loadOrderData] 开始加载订单数据, orderId:', orderId.value)
|
||||
|
||||
// 使用 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
|
||||
}
|
||||
|
||||
// 转换为 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> = []
|
||||
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
|
||||
|
||||
// 获取商家信息
|
||||
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' })
|
||||
}
|
||||
}
|
||||
|
||||
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 ''
|
||||
const date = new Date(timeStr)
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
const getSpecText = (specs: any | null): string => {
|
||||
if (specs == null) 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 ''
|
||||
}
|
||||
}
|
||||
|
||||
// 获取评分文本
|
||||
const getRatingText = (rating: number): string => {
|
||||
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
|
||||
// 触发响应式更新
|
||||
const newRatings: number[] = []
|
||||
for (let i: number = 0; i < ratings.value.length; i++) {
|
||||
newRatings.push(ratings.value[i])
|
||||
}
|
||||
ratings.value = newRatings
|
||||
}
|
||||
|
||||
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) => {
|
||||
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
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
const uploadImage = async (index: number) => {
|
||||
// 检查图片数量限制
|
||||
if (images.value[index].length >= 9) {
|
||||
uni.showToast({
|
||||
title: '最多上传9张图片',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用uni.chooseImage选择图片
|
||||
uni.chooseImage({
|
||||
count: 9 - images.value[index].length,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const resObj = res as UTSJSONObject
|
||||
const tempFilesRaw = resObj.get('tempFilePaths')
|
||||
const tempFiles = tempFilesRaw != null ? (tempFilesRaw as Array<string>) : []
|
||||
|
||||
uni.showLoading({
|
||||
title: '上传中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
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({
|
||||
title: '上传成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除图片
|
||||
const deleteImage = (index: number, imgIndex: number) => {
|
||||
images.value[index].splice(imgIndex, 1)
|
||||
// 触发响应式更新
|
||||
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
|
||||
}
|
||||
|
||||
// 获取当前用户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
|
||||
|
||||
try {
|
||||
const userId = getCurrentUserId()
|
||||
if (userId == '') {
|
||||
uni.showToast({
|
||||
title: '用户信息错误',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
await supabaseService.submitShopReview(merchantReviewObj)
|
||||
}
|
||||
|
||||
await supabaseService.updateOrderStatus(orderId.value, 4)
|
||||
|
||||
uni.showToast({
|
||||
title: '评价成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
// 跳转到评价成功页面
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
|
||||
} catch (err) {
|
||||
console.error('提交评价失败:', err)
|
||||
uni.showToast({
|
||||
title: '提交失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 返回
|
||||
const goBack = (): void => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.review-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.review-header {
|
||||
background-color: #ffffff;
|
||||
padding: 10px 15px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.header-title-placeholder {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 44px;
|
||||
}
|
||||
|
||||
.review-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.order-section {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.order-time {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.products-section {
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.product-review {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 5px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 5px;
|
||||
/* display: block; removed */
|
||||
}
|
||||
|
||||
.product-spec {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
/* display: block; removed */
|
||||
}
|
||||
|
||||
.rating-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.rating-label {
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.rating-stars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
font-size: 26px;
|
||||
margin-right: 8px;
|
||||
color: #e0e0e0;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.star-icon.active {
|
||||
color: #ff5000;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.rating-text {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.rating-stars.small {
|
||||
/* gap: 5px; removed */
|
||||
}
|
||||
|
||||
.content-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.review-textarea {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
padding: 10px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.word-count {
|
||||
/* display: block; removed */
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.images-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.images-label {
|
||||
/* display: block; removed */
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.images-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* gap: 10px; removed */
|
||||
}
|
||||
|
||||
.image-item {
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uploaded-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.delete-image {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border: 1px dashed #cccccc;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 24px;
|
||||
color: #999999;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 10px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.anonymous-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.anonymous-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
font-size: 14px;
|
||||
/* display: block; removed */
|
||||
}
|
||||
|
||||
.anonymous-tip {
|
||||
/* display: block; removed */
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.merchant-section {
|
||||
background-color: #ffffff;
|
||||
padding: 20px 15px;
|
||||
margin-top: 15px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
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: 20px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.rating-item {
|
||||
font-size: 14px;
|
||||
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 {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
/* display: block; removed */
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
/* display: block; removed */
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
background-color: #ffffff;
|
||||
padding: 15px;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: #007aff;
|
||||
color: #ffffff;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.submit-btn.disabled {
|
||||
background-color: #cccccc;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user