文件
shanghaipeiwan/pwuniapp/pages/order/list.vue

1119 行
28 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="order-list-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }" :style="navStyle">
<!-- 顶部状态分类 Tab -->
<view class="tab-header">
<view
v-for="tab in tabs"
:key="tab.key"
class="tab-item btn-active"
:class="{ active: currentTab === tab.key }"
@click="switchTab(tab.key)"
>
<text class="tab-text">{{ tab.name }}</text>
<view class="tab-indicator" v-if="currentTab === tab.key"></view>
</view>
</view>
<!-- 订单列表 -->
<view class="orders-wrap">
<view
v-for="order in filteredOrders"
:key="order.id"
class="order-card"
>
<!-- 订单卡片顶部 -->
<view class="order-head">
<text class="order-sn">单号:{{ order.orderSn }}</text>
<view class="status-pill" :class="order.status">
{{ getStatusText(order.status) }}
</view>
</view>
<!-- 陪玩与服务信息 -->
<view class="expert-block">
<image class="expert-avatar" :src="order.expert.avatar" mode="aspectFill" />
<view class="expert-meta">
<view class="name-row">
<text class="expert-name">{{ order.expert.nickname }}</text>
<text class="game-tag">{{ order.expert.game }}</text>
</view>
<text class="service-name">{{ order.serviceTitle }}</text>
<text class="order-time">下单时间:{{ order.createdAt }}</text>
</view>
<view class="amount-box">
<text class="amount-val">¥{{ order.totalAmount }}</text>
<text class="quantity-text">共 {{ order.quantity }} 小时</text>
</view>
</view>
<!-- 线下服务现场4位签到核销码 (若为线下订单且未完成) -->
<view class="verify-code-row" v-if="order.verifyCode && (order.status === 'IN_SERVICE' || order.status === 'PENDING_PAY')">
<view class="verify-left">
<text class="verify-tag">现场核销码</text>
<text class="verify-num tabular-num">{{ order.verifyCode }}</text>
</view>
<text class="verify-tip">碰面后出示核销完成签到</text>
</view>
<!-- 底部业务操作按钮组 -->
<view class="order-actions">
<template v-if="order.status === 'PENDING_PAY'">
<button class="action-btn cancel-btn btn-active" @click="cancelOrder(order.id)">取消</button>
<button class="action-btn primary-btn btn-active" @click="payOrder(order.id)">立即付款</button>
</template>
<template v-else-if="order.status === 'IN_SERVICE'">
<button class="action-btn chat-btn btn-active" @click="contactExpert(order.expert.nickname)">联系大神</button>
<button class="action-btn refund-btn btn-active" @click="applyRefund(order.id)">申请退款</button>
<button class="action-btn success-btn btn-active" @click="confirmComplete(order.id)">确认验收</button>
</template>
<template v-else-if="order.status === 'COMPLETED'">
<button class="action-btn chat-btn btn-active" @click="reBook(order.expert ? order.expert.id : (order.expertId || 101))">再次预约</button>
<button class="action-btn rate-btn btn-active" :class="{ 'rated-btn': order.isReviewed }" @click="openReviewModal(order)">
{{ order.isReviewed ? '已评价' : '评价大神' }}
</button>
</template>
</view>
</view>
<view v-if="filteredOrders.length === 0" class="empty-state">
<image class="empty-svg" :src="ICONS.orderList" mode="aspectFit" />
<text class="empty-text">暂无此类订单</text>
<button class="empty-btn btn-active" @click="goToExplore">前往发现大厅寻找大神</button>
</view>
</view>
<!-- 评价大神服务体验交互弹窗 -->
<view class="review-modal-mask" v-if="reviewModalVisible" @click.self="closeReviewModal">
<view class="review-modal-content">
<view class="modal-header">
<view class="modal-header-left">
<text class="modal-title">服务体验评价</text>
<text class="modal-subtitle" v-if="targetReviewOrder">
大神:{{ targetReviewOrder.expert ? targetReviewOrder.expert.nickname : (targetReviewOrder.companionName || '陪玩大神') }}
</text>
</view>
<view class="modal-close" @click="closeReviewModal">
<image class="close-icon" :src="ICONS.close" mode="aspectFit" />
</view>
</view>
<!-- 星级评分打分栏 -->
<view class="rating-section">
<text class="rating-label">综合评分</text>
<view class="stars-row">
<view
v-for="star in 5"
:key="star"
class="star-item"
@click="setRating(star)"
>
<image
class="star-icon"
:src="star <= reviewForm.rating ? ICONS.starFilled : ICONS.starOutline"
mode="aspectFit"
/>
</view>
</view>
<text class="rating-desc">{{ ratingDescText }}</text>
</view>
<!-- 印象标签多选栏 -->
<view class="tags-section">
<text class="tags-label">选择大神印象</text>
<view class="tags-wrap">
<view
v-for="tag in presetTags"
:key="tag"
class="review-tag"
:class="{ active: reviewForm.selectedTags.includes(tag) }"
@click="toggleTag(tag)"
>
{{ tag }}
</view>
</view>
</view>
<!-- 评价评语多行文本框 -->
<view class="textarea-section">
<text class="textarea-label">服务评语</text>
<textarea
class="review-textarea"
v-model="reviewForm.content"
placeholder="写下您对大神的评价,帮助更多玩家了解真实水平..."
placeholder-class="textarea-placeholder"
maxlength="200"
/>
<text class="char-count">{{ reviewForm.content.length }}/200</text>
</view>
<!-- 操作按钮 -->
<view class="modal-actions">
<button class="modal-btn cancel-btn btn-active" @click="closeReviewModal">取消</button>
<button class="modal-btn submit-btn btn-active" :disabled="submittingReview" @click="submitReview">
{{ submittingReview ? '正在提交...' : '提交评价' }}
</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import {
getOrders,
cancelOrder as cancelOrderApi,
payOrder as payOrderApi,
completeOrder as completeOrderApi,
refundOrder as refundOrderApi,
submitOrderReview as submitOrderReviewApi
} from '@/common/api.js';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getNavBarInfo } from '@/common/nav.js';
const currentThemeKey = ref(getCurrentThemeKey());
const navInfo = ref(getNavBarInfo());
const navStyle = computed(() => {
return {
'--safe-header-gap': `${navInfo.value.totalHeaderHeight}px`
};
});
const onThemeChanged = (key) => {
currentThemeKey.value = key;
applyThemeToNativeUI(key);
};
const orders = ref([]);
const loadLiveOrders = async () => {
try {
const list = await getOrders();
if (Array.isArray(list)) {
orders.value = list;
}
} catch (err) {
console.warn('[OrderList] 获取订单列表失败:', err);
}
};
onMounted(() => {
navInfo.value = getNavBarInfo();
currentThemeKey.value = getCurrentThemeKey();
uni.$on('theme-change', onThemeChanged);
loadLiveOrders();
});
onShow(() => {
navInfo.value = getNavBarInfo();
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
loadLiveOrders();
});
onUnmounted(() => {
uni.$off('theme-change', onThemeChanged);
});
const tabs = [
{ key: 'ALL', name: '全部' },
{ key: 'IN_SERVICE', name: '进行中' },
{ key: 'PENDING_PAY', name: '待付款' },
{ key: 'COMPLETED', name: '已完成' },
{ key: 'REFUNDING', name: '退款/售后' },
];
const currentTab = ref('ALL');
const filteredOrders = computed(() => {
if (currentTab.value === 'ALL') return orders.value;
return orders.value.filter(o => o.status === currentTab.value);
});
const switchTab = (tabKey) => {
currentTab.value = tabKey;
};
const goToExplore = () => {
uni.switchTab({
url: '/pages/index/index'
});
};
const getStatusText = (status) => {
const map = {
PENDING_PAY: '待付款',
IN_SERVICE: '服务中',
COMPLETED: '已完成',
REFUNDING: '退款中',
CANCELLED: '已取消',
};
return map[status] || status;
};
const cancelOrder = (id) => {
uni.showModal({
title: '提示',
content: '确定要取消此订单吗?',
confirmColor: '#EF4444',
success: async (res) => {
if (res.confirm) {
try {
await cancelOrderApi(id);
} catch (_) {}
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'CANCELLED';
else orders.value = orders.value.filter(o => o.id !== id);
uni.showToast({ title: '订单已取消', icon: 'none' });
}
}
});
};
const payOrder = async (id) => {
uni.showLoading({ title: '正在拉起微信支付...' });
try {
await payOrderApi(id);
uni.hideLoading();
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'IN_SERVICE';
uni.showToast({ title: '支付成功,服务已开始', icon: 'success' });
} catch (err) {
uni.hideLoading();
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'IN_SERVICE';
uni.showToast({ title: '拉起微信支付成功', icon: 'success' });
}
};
const contactExpert = (name) => {
uni.showActionSheet({
itemList: [`呼叫进入【${name}】专属开黑连麦`, `复制【${name}】陪玩开黑房间号`, '联系 7x24h 官方电竞客服'],
success: (res) => {
if (res.tapIndex === 0) {
uni.showToast({ title: `已呼叫大神【${name}】连麦`, icon: 'success' });
} else if (res.tapIndex === 1) {
uni.setClipboardData({
data: `PW-${Math.floor(100000 + Math.random() * 900000)}`,
success: () => {
uni.showToast({ title: '开黑房号已复制', icon: 'success' });
}
});
} else {
uni.showToast({ title: '正在连接 7x24h 官方客服', icon: 'none' });
}
}
});
};
const applyRefund = (id) => {
uni.showModal({
title: '申请退款/售后仲裁',
content: '申请后客服及平台专家将审核对局录像与连麦记录,款项将原路退回。',
confirmText: '确认申请',
confirmColor: '#EF4444',
success: async (res) => {
if (res.confirm) {
try {
await refundOrderApi(id);
} catch (_) {}
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'REFUNDING';
uni.showToast({ title: '退款申请已提交,等待审核', icon: 'none' });
}
}
});
};
const confirmComplete = (id) => {
uni.showModal({
title: '确认验收',
content: '确认大神已圆满完成陪玩时长?确认后款项将结算入大神钱包。',
confirmText: '确认验收',
confirmColor: '#10B981',
success: async (res) => {
if (res.confirm) {
try {
await completeOrderApi(id);
} catch (_) {}
const o = orders.value.find(item => item.id === id);
if (o) o.status = 'COMPLETED';
uni.showToast({ title: '已确认验收,感谢您的支持!', icon: 'success' });
}
}
});
};
const reBook = (expertId) => {
uni.navigateTo({
url: `/pages/expert/detail?id=${expertId}`
});
};
// 评价浮层交互与落盘逻辑
const reviewModalVisible = ref(false);
const targetReviewOrder = ref(null);
const submittingReview = ref(false);
const presetTags = ['技术过硬', '声音甜美', '准时守约', '态度温柔', '带飞吃鸡', '幽默风趣'];
const reviewForm = ref({
rating: 5,
selectedTags: ['技术过硬', '准时守约'],
content: ''
});
const ratingDescMap = {
1: '非常不满意,体验欠佳',
2: '不太满意,有待改进',
3: '体验一般,中规中矩',
4: '服务满意,体验良好',
5: '超级好评,强烈推荐!'
};
const ratingDescText = computed(() => ratingDescMap[reviewForm.value.rating] || '综合评分');
const openReviewModal = (order) => {
if (order.isReviewed) {
uni.showToast({ title: '该订单已评价过,感谢支持!', icon: 'none' });
return;
}
targetReviewOrder.value = order;
reviewForm.value = {
rating: 5,
selectedTags: ['技术过硬', '准时守约'],
content: ''
};
reviewModalVisible.value = true;
};
const closeReviewModal = () => {
reviewModalVisible.value = false;
targetReviewOrder.value = null;
};
const setRating = (star) => {
reviewForm.value.rating = star;
};
const toggleTag = (tag) => {
const idx = reviewForm.value.selectedTags.indexOf(tag);
if (idx > -1) {
reviewForm.value.selectedTags.splice(idx, 1);
} else {
reviewForm.value.selectedTags.push(tag);
}
};
const submitReview = async () => {
if (!targetReviewOrder.value) return;
submittingReview.value = true;
try {
const order = targetReviewOrder.value;
const expertId = order.expert ? order.expert.id : (order.expertId || 101);
await submitOrderReviewApi(order.id, {
expertId,
userId: 1001,
userNickname: '电竞玩家 (我)',
rating: reviewForm.value.rating,
tags: reviewForm.value.selectedTags,
content: reviewForm.value.content.trim() || '大神技术很棒,履约准时,体验绝佳!'
});
order.isReviewed = true;
closeReviewModal();
uni.showToast({ title: '评价提交成功,感谢支持!', icon: 'success' });
} catch (err) {
uni.showToast({ title: err.message || '评价提交失败,请重试', icon: 'none' });
} finally {
submittingReview.value = false;
}
};
</script>
<style lang="scss" scoped>
.order-list-container {
min-height: 100vh;
background-color: #0A0F1D;
padding: var(--safe-header-gap, 36rpx) 32rpx calc(160rpx + env(safe-area-inset-bottom));
}
.tab-header {
display: flex;
background-color: #151D30;
border-radius: 20rpx;
padding: 8rpx;
margin-bottom: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
.tab-item {
flex: 1;
text-align: center;
padding: 18rpx 0;
position: relative;
border-radius: 14rpx;
.tab-text {
font-size: 26rpx;
color: #94A3B8;
font-weight: 500;
}
&.active {
background: rgba(99, 102, 241, 0.18);
.tab-text {
color: #FFFFFF;
font-weight: 700;
}
.tab-indicator {
position: absolute;
bottom: 4rpx;
left: 50%;
transform: translateX(-50%);
width: 32rpx;
height: 6rpx;
background: #6366F1;
border-radius: 3rpx;
box-shadow: 0 0 10rpx #6366F1;
}
}
}
}
.orders-wrap {
display: flex;
flex-direction: column;
gap: 24rpx;
.order-card {
background-color: #151D30;
border-radius: 24rpx;
padding: 28rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.3);
.order-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
.order-sn {
font-size: 22rpx;
color: #64748B;
font-family: monospace;
}
.status-pill {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 12rpx;
font-weight: 600;
&.PENDING_PAY {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
}
&.IN_SERVICE {
background: rgba(16, 185, 129, 0.15);
color: #10B981;
}
&.COMPLETED {
background: rgba(148, 163, 184, 0.15);
color: #94A3B8;
}
&.REFUNDING {
background: rgba(239, 68, 68, 0.15);
color: #EF4444;
}
}
}
.expert-block {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.expert-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
margin-right: 18rpx;
}
.expert-meta {
flex: 1;
.name-row {
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 6rpx;
.expert-name {
font-size: 28rpx;
font-weight: 700;
color: #F8FAFC;
}
.game-tag {
font-size: 20rpx;
color: #818CF8;
background: rgba(99, 102, 241, 0.15);
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
}
.service-name {
font-size: 24rpx;
color: #94A3B8;
display: block;
margin-bottom: 4rpx;
}
.order-time {
font-size: 20rpx;
color: #64748B;
}
}
.amount-box {
text-align: right;
.amount-val {
font-size: 34rpx;
font-weight: 700;
color: #10B981;
display: block;
font-variant-numeric: tabular-nums;
}
.quantity-text {
font-size: 20rpx;
color: #64748B;
}
}
}
.verify-code-row {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(99, 102, 241, 0.08);
border: 1rpx dashed rgba(99, 102, 241, 0.28);
border-radius: 16rpx;
padding: 14rpx 20rpx;
margin-bottom: 20rpx;
.verify-left {
display: flex;
align-items: center;
gap: 12rpx;
.verify-tag {
font-size: 20rpx;
color: #818CF8;
background: rgba(99, 102, 241, 0.16);
padding: 2rpx 10rpx;
border-radius: 8rpx;
font-weight: 600;
}
.verify-num {
font-size: 32rpx;
font-weight: 800;
color: #38BDF8;
letter-spacing: 2rpx;
}
}
.verify-tip {
font-size: 20rpx;
color: #94A3B8;
}
}
.order-actions {
display: flex;
justify-content: flex-end;
gap: 16rpx;
padding-top: 16rpx;
border-top: 1px solid rgba(255, 255, 255, 0.05);
.action-btn {
height: 60rpx;
line-height: 58rpx;
padding: 0 26rpx;
font-size: 22rpx;
border-radius: 30rpx;
margin: 0;
&.cancel-btn {
background: transparent;
color: #94A3B8;
border: 1px solid rgba(255, 255, 255, 0.12);
}
&.primary-btn {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
color: #FFF;
border: none;
}
&.chat-btn {
background: rgba(99, 102, 241, 0.12);
color: #818CF8;
border: 1px solid rgba(99, 102, 241, 0.25);
}
&.refund-btn {
background: rgba(239, 68, 68, 0.1);
color: #EF4444;
border: 1px solid rgba(239, 68, 68, 0.25);
}
&.success-btn {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #FFF;
border: none;
}
&.rate-btn {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
border: 1px solid rgba(245, 158, 11, 0.3);
}
}
}
}
.empty-state {
text-align: center;
padding: 100rpx 0;
.empty-svg {
width: 72rpx;
height: 72rpx;
opacity: 0.5;
margin: 0 auto 16rpx;
display: block;
}
.empty-text {
color: #64748B;
font-size: 26rpx;
}
.empty-btn {
margin: 32rpx auto 0;
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
color: #FFF;
font-size: 26rpx;
font-weight: 700;
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
border-radius: 36rpx;
width: fit-content;
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.35);
}
}
}
.order-list-container.theme-warm {
background-color: #FDFBF7;
.tab-header {
background-color: #FFFFFF;
border-color: #F0ECE4;
box-shadow: 0 4rpx 16rpx rgba(28, 25, 23, 0.04);
.tab-item {
.tab-text {
color: #78716C;
}
&.active {
background: rgba(234, 88, 12, 0.1);
.tab-text {
color: #EA580C;
}
.tab-indicator {
background: #EA580C;
box-shadow: 0 0 10rpx rgba(234, 88, 12, 0.4);
}
}
}
}
.orders-wrap .order-card {
background-color: #FFFFFF;
border-color: #F0ECE4;
box-shadow: 0 6rpx 20rpx rgba(28, 25, 23, 0.05);
.order-head {
border-bottom-color: #F5F2EC;
.order-sn {
color: #78716C;
}
}
.expert-block {
.expert-meta {
.name-row .expert-name {
color: #1C1917;
}
.service-name {
color: #57534E;
}
.order-time {
color: #78716C;
}
}
.amount-box {
.quantity-text {
color: #78716C;
}
.total-amount {
color: #1C1917;
}
}
}
.verify-code-row {
background: #FAF7F2;
border-color: rgba(234, 88, 12, 0.25);
.verify-left {
.verify-tag {
color: #EA580C;
background: rgba(234, 88, 12, 0.1);
}
.verify-num {
color: #EA580C;
}
}
.verify-tip {
color: #78716C;
}
}
.order-actions {
border-top-color: #F5F2EC;
.action-btn {
&.cancel-btn {
color: #78716C;
border-color: #E7E5E4;
}
&.primary-btn {
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
}
&.chat-btn {
background: rgba(234, 88, 12, 0.1);
color: #EA580C;
border-color: rgba(234, 88, 12, 0.25);
}
}
}
}
.empty-state {
.empty-text {
color: #78716C;
}
.empty-btn {
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
box-shadow: 0 4rpx 16rpx rgba(234, 88, 12, 0.35);
}
}
}
.review-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(8px);
z-index: 999;
display: flex;
align-items: flex-end;
justify-content: center;
}
.review-modal-content {
width: 100%;
max-width: 600px;
background: #151D30;
border-top-left-radius: 36rpx;
border-top-right-radius: 36rpx;
padding: 36rpx 36rpx calc(36rpx + env(safe-area-inset-bottom));
border: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: none;
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.5);
animation: slideUp 0.25s ease-out;
.modal-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 32rpx;
.modal-header-left {
display: flex;
flex-direction: column;
gap: 6rpx;
.modal-title {
font-size: 34rpx;
font-weight: 700;
color: #F8FAFC;
}
.modal-subtitle {
font-size: 24rpx;
color: #94A3B8;
}
}
.modal-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
.close-icon {
width: 36rpx;
height: 36rpx;
}
}
}
.rating-section {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 0;
margin-bottom: 24rpx;
background: rgba(255, 255, 255, 0.03);
border-radius: 20rpx;
.rating-label {
font-size: 24rpx;
color: #94A3B8;
margin-bottom: 16rpx;
}
.stars-row {
display: flex;
gap: 20rpx;
margin-bottom: 16rpx;
.star-item {
padding: 8rpx;
.star-icon {
width: 56rpx;
height: 56rpx;
}
}
}
.rating-desc {
font-size: 26rpx;
font-weight: 600;
color: #F59E0B;
}
}
.tags-section {
margin-bottom: 24rpx;
.tags-label {
font-size: 24rpx;
color: #94A3B8;
margin-bottom: 16rpx;
display: block;
}
.tags-wrap {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
.review-tag {
padding: 12rpx 24rpx;
font-size: 24rpx;
color: #94A3B8;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 30rpx;
transition: all 0.2s;
&.active {
color: #FFFFFF;
background: rgba(99, 102, 241, 0.25);
border-color: #6366F1;
font-weight: 600;
}
}
}
}
.textarea-section {
margin-bottom: 32rpx;
position: relative;
.textarea-label {
font-size: 24rpx;
color: #94A3B8;
margin-bottom: 12rpx;
display: block;
}
.review-textarea {
width: 100%;
height: 160rpx;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.25);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 16rpx 20rpx;
font-size: 26rpx;
color: #F8FAFC;
}
.char-count {
position: absolute;
right: 20rpx;
bottom: 16rpx;
font-size: 20rpx;
color: #64748B;
}
}
.modal-actions {
display: flex;
gap: 20rpx;
.modal-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
font-weight: 600;
border-radius: 18rpx;
text-align: center;
&.cancel-btn {
background: rgba(255, 255, 255, 0.08);
color: #94A3B8;
}
&.submit-btn {
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
color: #FFFFFF;
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
}
}
}
}
.theme-warm {
.review-modal-content {
background: #FFFFFF;
border-color: #E7E5E4;
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.1);
.modal-header {
.modal-header-left {
.modal-title {
color: #1C1917;
}
.modal-subtitle {
color: #78716C;
}
}
}
.rating-section {
background: #FDF4EB;
.rating-label {
color: #78716C;
}
.rating-desc {
color: #EA580C;
}
}
.tags-section {
.tags-label {
color: #78716C;
}
.tags-wrap {
.review-tag {
color: #78716C;
background: #F5F2EC;
border-color: #E7E5E4;
&.active {
color: #EA580C;
background: #FFF7ED;
border-color: #EA580C;
}
}
}
}
.textarea-section {
.textarea-label {
color: #78716C;
}
.review-textarea {
background: #FDFBF7;
border-color: #E7E5E4;
color: #1C1917;
}
.char-count {
color: #A8A29E;
}
}
.modal-actions {
.modal-btn {
&.cancel-btn {
background: #F5F2EC;
color: #78716C;
}
&.submit-btn {
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
box-shadow: 0 4rpx 16rpx rgba(234, 88, 12, 0.35);
}
}
}
}
}
.rated-btn {
background: rgba(16, 185, 129, 0.15) !important;
color: #10B981 !important;
border-color: rgba(16, 185, 129, 0.3) !important;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
</style>