|
|
@@ -2,16 +2,10 @@
|
|
|
<view class="page">
|
|
|
<NavBar title="订单详情" :showBack="true" />
|
|
|
|
|
|
- <!-- 加载 -->
|
|
|
<view class="load-tip" v-if="loading">
|
|
|
- <view class="dot-row">
|
|
|
- <view class="pulse-dot"></view>
|
|
|
- <view class="pulse-dot"></view>
|
|
|
- <view class="pulse-dot"></view>
|
|
|
- </view>
|
|
|
+ <view class="dot-row"><view class="pulse-dot"></view><view class="pulse-dot"></view><view class="pulse-dot"></view></view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 错误 -->
|
|
|
<view class="error-state" v-if="!loading && errorMsg">
|
|
|
<view class="error-icon"></view>
|
|
|
<text class="error-text">{{ errorMsg }}</text>
|
|
|
@@ -19,15 +13,17 @@
|
|
|
</view>
|
|
|
|
|
|
<scroll-view class="detail-scroll" scroll-y v-if="!loading && order">
|
|
|
- <!-- 状态区域 -->
|
|
|
- <view class="status-section">
|
|
|
- <view class="status-row">
|
|
|
- <view class="status-tag" :class="getPayStatusClass(order.payStatus)">
|
|
|
- <text>{{ order.payStatusLabel || getPayStatusText(order.payStatus) }}</text>
|
|
|
+ <!-- 状态卡片 -->
|
|
|
+ <view class="card status-card">
|
|
|
+ <view class="status-left">
|
|
|
+ <view class="status-circle" :class="getPayStatusClass(order.payStatus)">
|
|
|
+ <text>{{ getStatusIcon(order.payStatus) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="status-text-wrap">
|
|
|
+ <text class="status-label">{{ order.payStatusLabel || getPayStatusText(order.payStatus) }}</text>
|
|
|
+ <text class="status-desc">{{ getStatusDesc(order.status, order.payStatus) }}</text>
|
|
|
</view>
|
|
|
- <text class="order-no">订单 {{ order.orderNo }}</text>
|
|
|
</view>
|
|
|
- <text class="status-desc" :class="getPayStatusClass(order.payStatus)">{{ getStatusDesc(order.status, order.payStatus) }}</text>
|
|
|
</view>
|
|
|
|
|
|
<!-- 视频 -->
|
|
|
@@ -36,18 +32,19 @@
|
|
|
</view>
|
|
|
|
|
|
<!-- 订单明细 -->
|
|
|
- <view class="section">
|
|
|
- <text class="section-title">订单明细</text>
|
|
|
+ <view class="card">
|
|
|
+ <text class="card-title">订单明细</text>
|
|
|
+
|
|
|
<view class="product-list" v-if="order.products && order.products.length > 0">
|
|
|
<view class="product-item" v-for="item in order.products" :key="item.productId">
|
|
|
<image class="product-img" :src="item.pic || '/static/images/default-product.png'" mode="aspectFill" />
|
|
|
<view class="product-main">
|
|
|
<text class="product-name">{{ item.productName }}</text>
|
|
|
- <text class="product-meta">x{{ item.productNum }}</text>
|
|
|
+ <text class="product-price">¥{{ formatMoney(item.price) }}</text>
|
|
|
</view>
|
|
|
- <view class="product-price">
|
|
|
- <text class="price-unit">¥{{ formatMoney(item.price) }}</text>
|
|
|
- <text class="price-total">¥{{ formatMoney(item.price * item.productNum) }}</text>
|
|
|
+ <view class="product-right">
|
|
|
+ <text class="product-qty">x{{ item.productNum }}</text>
|
|
|
+ <text class="product-subtotal">¥{{ formatMoney(item.price * item.productNum) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -57,70 +54,151 @@
|
|
|
</view>
|
|
|
|
|
|
<view class="amount-section">
|
|
|
- <view class="amount-row" v-if="order.discountAmount > 0">
|
|
|
+ <view class="amount-row discount" v-if="order.discountAmount > 0">
|
|
|
<text class="amount-label">优惠金额</text>
|
|
|
- <text class="amount-value discount">-¥{{ formatMoney(order.discountAmount) }}</text>
|
|
|
+ <text class="amount-value">-¥{{ formatMoney(order.discountAmount) }}</text>
|
|
|
</view>
|
|
|
- <view class="amount-row total">
|
|
|
+ <view class="amount-row paid">
|
|
|
<text class="amount-label">实付款</text>
|
|
|
<text class="amount-value">¥{{ formatMoney(order.paidAmount) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 订单信息 -->
|
|
|
- <view class="section">
|
|
|
- <text class="section-title">订单信息</text>
|
|
|
-
|
|
|
- <view class="info-row">
|
|
|
- <text class="info-label">订单编号</text>
|
|
|
- <view class="info-value-row">
|
|
|
- <text class="info-value mono">{{ order.orderNo }}</text>
|
|
|
- <view class="copy-btn" @click="copyText(order.orderNo)"><text>复制</text></view>
|
|
|
+ <!-- 退款记录 -->
|
|
|
+ <view class="card" v-if="order.refunds && order.refunds.length > 0">
|
|
|
+ <text class="card-title">退款记录</text>
|
|
|
+ <view class="refund-list">
|
|
|
+ <view class="refund-item" v-for="(r, i) in order.refunds" :key="i">
|
|
|
+ <view class="refund-timeline">
|
|
|
+ <view class="timeline-dot" :class="r.status === 'REFUNDED' ? 'success' : r.status === 'REJECTED' ? 'rejected' : 'pending'"></view>
|
|
|
+ <view class="timeline-line" v-if="i < order.refunds.length - 1"></view>
|
|
|
+ </view>
|
|
|
+ <view class="refund-content">
|
|
|
+ <view class="refund-header">
|
|
|
+ <text class="refund-status" :class="r.status === 'REFUNDED' ? 'success' : r.status === 'REJECTED' ? 'rejected' : 'pending'">
|
|
|
+ {{ r.status === 'REFUNDED' ? '已退款' : r.status === 'REJECTED' ? '已拒绝' : '审核中' }}
|
|
|
+ </text>
|
|
|
+ <text class="refund-amount">¥{{ formatMoney(r.refundAmount) }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="refund-time">{{ r.createTime }}</text>
|
|
|
+ <text class="refund-reason" v-if="r.reason">原因: {{ r.reason }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <view class="info-row">
|
|
|
- <text class="info-label">下单时间</text>
|
|
|
- <text class="info-value">{{ order.createTime || '-' }}</text>
|
|
|
- </view>
|
|
|
- <view class="info-row" v-if="order.payTime">
|
|
|
- <text class="info-label">支付时间</text>
|
|
|
- <text class="info-value">{{ order.payTime }}</text>
|
|
|
- </view>
|
|
|
- <view class="info-row">
|
|
|
- <text class="info-label">门店名称</text>
|
|
|
- <text class="info-value">{{ order.shopName || '-' }}</text>
|
|
|
- </view>
|
|
|
- <view class="info-row">
|
|
|
- <text class="info-label">设备名称</text>
|
|
|
- <text class="info-value">{{ order.deviceName || '-' }}</text>
|
|
|
- </view>
|
|
|
- <view class="info-row">
|
|
|
- <text class="info-label">设备编号</text>
|
|
|
- <text class="info-value mono">{{ order.deviceId || '-' }}</text>
|
|
|
- </view>
|
|
|
- <view class="info-row" v-if="order.phone">
|
|
|
- <text class="info-label">消费者</text>
|
|
|
- <view class="info-value-row">
|
|
|
- <text class="info-value">{{ formatPhone(order.phone) }}</text>
|
|
|
- <view class="action-btn call-btn" @click="callPhone(order.phone)"><text>呼叫</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 订单信息 -->
|
|
|
+ <view class="card">
|
|
|
+ <text class="card-title">订单信息</text>
|
|
|
+ <view class="info-list">
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">订单编号</text>
|
|
|
+ <view class="info-value-row">
|
|
|
+ <text class="info-value">{{ order.orderNo }}</text>
|
|
|
+ <view class="copy-btn" @click="copyText(order.orderNo)"><text>复制</text></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">下单时间</text>
|
|
|
+ <text class="info-value">{{ order.createTime || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row" v-if="order.payTime">
|
|
|
+ <text class="info-label">支付时间</text>
|
|
|
+ <text class="info-value">{{ order.payTime }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">门店名称</text>
|
|
|
+ <text class="info-value">{{ order.shopName || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">设备名称</text>
|
|
|
+ <text class="info-value">{{ order.deviceName || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">设备编号</text>
|
|
|
+ <text class="info-value mono">{{ order.deviceId || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row" v-if="order.userName || order.phone">
|
|
|
+ <text class="info-label">消费者</text>
|
|
|
+ <text class="info-value">
|
|
|
+ {{ [order.userName, order.phone ? formatPhone(order.phone) : ''].filter(Boolean).join(' ') }}
|
|
|
+ </text>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- <view class="info-row" v-if="order.userName">
|
|
|
- <text class="info-label">用户名</text>
|
|
|
- <text class="info-value">{{ order.userName }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 底部占位 -->
|
|
|
- <view class="bottom-spacer" v-if="canRefund(order)"></view>
|
|
|
</scroll-view>
|
|
|
|
|
|
- <!-- 退款操作 -->
|
|
|
<view class="bottom-bar" v-if="order && canRefund(order)">
|
|
|
- <view class="refund-btn" :class="{ disabled: isRefunding }" @click="handleRefund">
|
|
|
- <view class="loading-spinner" v-if="isRefunding"></view>
|
|
|
- <text class="refund-btn-text">{{ isRefunding ? '处理中' : '退款' }}</text>
|
|
|
+ <view class="refund-btn" @click="openRefundModal"><text class="refund-btn-text">退款</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 退款弹窗 -->
|
|
|
+ <view class="refund-modal" v-if="showRefundModal" @click="showRefundModal = false">
|
|
|
+ <view class="refund-modal-content" @click.stop>
|
|
|
+ <view class="modal-header">
|
|
|
+ <text class="modal-title">退款</text>
|
|
|
+ <view class="modal-close" @click="showRefundModal = false"></view>
|
|
|
+ </view>
|
|
|
+ <scroll-view class="modal-body" scroll-y>
|
|
|
+ <!-- 退款方式 -->
|
|
|
+ <view class="refund-type-row">
|
|
|
+ <view class="refund-type-chip" :class="{ active: refundType === 'full' }" @click="refundType = 'full'; refundCustomAmount = ''; refundProducts = {}"><text>全额退款</text></view>
|
|
|
+ <view class="refund-type-chip" :class="{ active: refundType === 'custom' }" @click="refundType = 'custom'"><text>自定义</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 已退款提示 -->
|
|
|
+ <view class="refunded-tip" v-if="alreadyRefunded() > 0">
|
|
|
+ <text>已退款 ¥{{ formatMoney(alreadyRefunded()) }},实付 ¥{{ formatMoney(order?.paidAmount || 0) }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 自定义金额 -->
|
|
|
+ <view class="form-group" v-if="refundType === 'custom'">
|
|
|
+ <text class="form-label">退款金额 (元)</text>
|
|
|
+ <input class="form-input" v-model="refundCustomAmount" type="digit" placeholder="留空则为全额退款" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 商品选择 (仅全额模式) -->
|
|
|
+ <view class="form-group" v-if="refundType === 'full' && refundableProducts().length > 0">
|
|
|
+ <text class="form-label">退款商品 (不选则为全额)</text>
|
|
|
+ <view class="refund-product-item" v-for="p in refundableProducts()" :key="p._idx">
|
|
|
+ <view class="rp-left" @click="toggleProduct(p._idx)">
|
|
|
+ <view class="rp-checkbox" :class="{ checked: refundProducts[p._idx] }">
|
|
|
+ <text v-if="refundProducts[p._idx]">✓</text>
|
|
|
+ </view>
|
|
|
+ <image class="rp-img" :src="p.pic || '/static/images/default-product.png'" mode="aspectFill" />
|
|
|
+ <view class="rp-info">
|
|
|
+ <text class="rp-name">{{ p.productName }}</text>
|
|
|
+ <text class="rp-price">¥{{ formatMoney(p.price) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="rp-qty" v-if="refundProducts[p._idx]">
|
|
|
+ <view class="qty-btn" @click="setProductQty(p._idx, (refundProducts[p._idx] || 1) - 1)"><text>-</text></view>
|
|
|
+ <text class="qty-value">{{ refundProducts[p._idx] }}</text>
|
|
|
+ <view class="qty-btn" @click="setProductQty(p._idx, (refundProducts[p._idx] || 1) + 1)"><text>+</text></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 退款原因 -->
|
|
|
+ <view class="form-group">
|
|
|
+ <text class="form-label">退款原因 *</text>
|
|
|
+ <textarea class="form-textarea" v-model="refundReason" placeholder="请输入退款原因" :maxlength="200" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 退款合计 -->
|
|
|
+ <view class="refund-total">
|
|
|
+ <text class="total-label">退款合计</text>
|
|
|
+ <text class="total-value">¥{{ formatMoney(refundTotalAmount()) }}</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ <view class="modal-footer">
|
|
|
+ <button class="btn-cancel" @click="showRefundModal = false">取消</button>
|
|
|
+ <button class="btn-confirm" :class="{ disabled: isRefunding }" @click="handleRefund">
|
|
|
+ <view class="loading-spinner" v-if="isRefunding"></view>
|
|
|
+ <text>{{ isRefunding ? '处理中' : '确认退款' }}</text>
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -141,6 +219,14 @@ interface OrderProduct {
|
|
|
pic?: string;
|
|
|
}
|
|
|
|
|
|
+interface RefundRecord {
|
|
|
+ status: string;
|
|
|
+ refundAmount: number;
|
|
|
+ createTime: string;
|
|
|
+ reason?: string;
|
|
|
+ remark?: string;
|
|
|
+}
|
|
|
+
|
|
|
interface OrderDetail {
|
|
|
id: string;
|
|
|
orderNo: string;
|
|
|
@@ -149,6 +235,7 @@ interface OrderDetail {
|
|
|
payStatusLabel?: string;
|
|
|
paidAmount: number;
|
|
|
discountAmount?: number;
|
|
|
+ refundAmount?: number;
|
|
|
createTime: string;
|
|
|
payTime?: string;
|
|
|
shopName?: string;
|
|
|
@@ -158,6 +245,7 @@ interface OrderDetail {
|
|
|
phone?: string;
|
|
|
videoUrl?: string;
|
|
|
products?: OrderProduct[];
|
|
|
+ refunds?: RefundRecord[];
|
|
|
}
|
|
|
|
|
|
const order = ref<OrderDetail | null>(null);
|
|
|
@@ -166,22 +254,35 @@ const errorMsg = ref('');
|
|
|
const isRefunding = ref(false);
|
|
|
let lastId = '';
|
|
|
|
|
|
+// Refund modal state
|
|
|
+const showRefundModal = ref(false);
|
|
|
+const refundReason = ref('');
|
|
|
+const refundProducts = ref<Record<number, number>>({});
|
|
|
+const refundType = ref<'full' | 'custom'>('full');
|
|
|
+const refundCustomAmount = ref('');
|
|
|
+
|
|
|
const formatMoney = formatMoneyUtil;
|
|
|
const formatPhone = formatPhoneUtil;
|
|
|
|
|
|
-const getPayStatusText = (payStatus: string): string => {
|
|
|
+const getStatusIcon = (payStatus: string) => {
|
|
|
+ if (payStatus === 'PAID') return '✓';
|
|
|
+ if (payStatus === 'REFUND') return '↩';
|
|
|
+ return '○';
|
|
|
+};
|
|
|
+
|
|
|
+const getPayStatusText = (payStatus: string) => {
|
|
|
const map: Record<string, string> = { UNPAID: '未支付', PAID: '已支付', REFUND: '已退款' };
|
|
|
return map[payStatus] || payStatus || '未知';
|
|
|
};
|
|
|
|
|
|
-const getPayStatusClass = (payStatus: string): 'success' | 'danger' | 'warning' => {
|
|
|
+const getPayStatusClass = (payStatus: string): string => {
|
|
|
if (payStatus === 'PAID') return 'success';
|
|
|
if (payStatus === 'REFUND') return 'danger';
|
|
|
return 'warning';
|
|
|
};
|
|
|
|
|
|
const getStatusDesc = (status: number, payStatus: string): string => {
|
|
|
- if (payStatus === 'PAID') return '交易成功';
|
|
|
+ if (payStatus === 'PAID') return '交易成功,款项已到账';
|
|
|
if (payStatus === 'REFUND') return '已退款,款项将退回原支付账户';
|
|
|
if (status === 0) return '等待消费者支付';
|
|
|
if (status === 2) return '订单已取消';
|
|
|
@@ -189,8 +290,61 @@ const getStatusDesc = (status: number, payStatus: string): string => {
|
|
|
return '';
|
|
|
};
|
|
|
|
|
|
-const canRefund = (o: OrderDetail): boolean => {
|
|
|
- return o.payStatus === 'PAID' && o.status === 1;
|
|
|
+const alreadyRefunded = () => (order.value?.refundAmount || 0);
|
|
|
+
|
|
|
+const canRefund = (o: OrderDetail) => {
|
|
|
+ if (o.payStatus !== 'PAID' || o.status !== 1) return false;
|
|
|
+ const paid = o.paidAmount || o.totalAmount || 0;
|
|
|
+ return (o.refundAmount || 0) < paid;
|
|
|
+};
|
|
|
+
|
|
|
+const refundableProducts = () => {
|
|
|
+ if (!order.value?.products) return [];
|
|
|
+ return order.value.products.map((p, i) => ({ ...p, _idx: i }));
|
|
|
+};
|
|
|
+
|
|
|
+const refundTotalAmount = (): number => {
|
|
|
+ const orderData = order.value;
|
|
|
+ if (!orderData) return 0;
|
|
|
+ if (refundType.value === 'custom') {
|
|
|
+ const val = parseFloat(refundCustomAmount.value);
|
|
|
+ return isNaN(val) ? 0 : val;
|
|
|
+ }
|
|
|
+ const selected = refundProducts.value;
|
|
|
+ if (Object.keys(selected).length === 0) {
|
|
|
+ const paid = orderData.paidAmount || 0;
|
|
|
+ return paid - alreadyRefunded();
|
|
|
+ }
|
|
|
+ let total = 0;
|
|
|
+ Object.entries(selected).forEach(([idx, qty]) => {
|
|
|
+ const p = orderData.products?.[Number(idx)];
|
|
|
+ if (p && qty > 0) total += p.price * qty;
|
|
|
+ });
|
|
|
+ return total;
|
|
|
+};
|
|
|
+
|
|
|
+const openRefundModal = () => {
|
|
|
+ refundReason.value = '';
|
|
|
+ refundProducts.value = {};
|
|
|
+ refundType.value = 'full';
|
|
|
+ refundCustomAmount.value = '';
|
|
|
+ showRefundModal.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const toggleProduct = (idx: number) => {
|
|
|
+ const current = refundProducts.value;
|
|
|
+ if (current[idx]) { delete current[idx]; refundProducts.value = { ...current }; }
|
|
|
+ else {
|
|
|
+ const p = order.value?.products?.[idx];
|
|
|
+ refundProducts.value = { ...current, [idx]: p?.productNum || 1 };
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const setProductQty = (idx: number, qty: number) => {
|
|
|
+ const p = order.value?.products?.[idx];
|
|
|
+ if (!p) return;
|
|
|
+ const max = p.productNum || 1;
|
|
|
+ refundProducts.value = { ...refundProducts.value, [idx]: Math.max(1, Math.min(qty, max)) };
|
|
|
};
|
|
|
|
|
|
const copyText = (text: string) => {
|
|
|
@@ -198,421 +352,221 @@ const copyText = (text: string) => {
|
|
|
uni.setClipboardData({ data: text, success: () => showToast('已复制', 'success') });
|
|
|
};
|
|
|
|
|
|
-const callPhone = (phone: string) => {
|
|
|
- uni.makePhoneCall({ phoneNumber: phone });
|
|
|
-};
|
|
|
-
|
|
|
const loadDetail = async (id?: string) => {
|
|
|
- if (!id) {
|
|
|
- errorMsg.value = '缺少订单参数';
|
|
|
- loading.value = false;
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!id) { errorMsg.value = '缺少订单参数'; loading.value = false; return; }
|
|
|
lastId = id;
|
|
|
loading.value = true;
|
|
|
errorMsg.value = '';
|
|
|
- try {
|
|
|
- order.value = await getOrderDetail(id);
|
|
|
- } catch {
|
|
|
- errorMsg.value = '加载订单详情失败,请检查网络后重试';
|
|
|
- order.value = null;
|
|
|
- } finally {
|
|
|
- loading.value = false;
|
|
|
- }
|
|
|
+ try { order.value = await getOrderDetail(id); } catch { errorMsg.value = '加载失败,请检查网络后重试'; order.value = null; }
|
|
|
+ finally { loading.value = false; }
|
|
|
};
|
|
|
|
|
|
const onRetry = () => loadDetail(lastId);
|
|
|
|
|
|
const handleRefund = async () => {
|
|
|
if (isRefunding.value || !order.value) return;
|
|
|
- const confirmed = await showConfirm('确认对该订单进行全额退款?');
|
|
|
- if (!confirmed) return;
|
|
|
+ if (!refundReason.value.trim()) { showToast('请输入退款原因'); return; }
|
|
|
+
|
|
|
+ const orderId = order.value.id;
|
|
|
+ const reason = refundReason.value.trim();
|
|
|
+ const extra: any = {};
|
|
|
+
|
|
|
+ if (refundType.value === 'custom' && refundCustomAmount.value) {
|
|
|
+ const amt = parseFloat(refundCustomAmount.value);
|
|
|
+ if (!isNaN(amt) && amt > 0) extra.refundAmount = amt;
|
|
|
+ } else if (Object.keys(refundProducts.value).length > 0) {
|
|
|
+ extra.products = Object.entries(refundProducts.value)
|
|
|
+ .filter(([_, qty]) => qty > 0)
|
|
|
+ .map(([idx, qty]) => {
|
|
|
+ const p = order.value!.products![Number(idx)];
|
|
|
+ return { productId: p.productId, productName: p.productName, quantity: qty, price: p.price };
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
isRefunding.value = true;
|
|
|
try {
|
|
|
- await refundOrder(order.value.id, '商家申请退款');
|
|
|
+ await refundOrder(orderId, reason, Object.keys(extra).length > 0 ? extra : undefined);
|
|
|
showToast('退款成功', 'success');
|
|
|
+ showRefundModal.value = false;
|
|
|
setTimeout(() => loadDetail(lastId), 1200);
|
|
|
- } catch {
|
|
|
- showToast('退款失败,请重试');
|
|
|
+ } catch (e: any) {
|
|
|
+ showToast(e?.message || '退款失败,请重试');
|
|
|
} finally {
|
|
|
isRefunding.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-onLoad((options?: Record<string, string>) => {
|
|
|
- loadDetail(options?.id);
|
|
|
-});
|
|
|
+onLoad((options?: Record<string, string>) => loadDetail(options?.id));
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
$font-stack: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', system-ui, sans-serif;
|
|
|
|
|
|
-.page {
|
|
|
- height: 100vh;
|
|
|
- background: $bg-color-page;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- font-family: $font-stack;
|
|
|
- overflow-x: hidden;
|
|
|
-}
|
|
|
-
|
|
|
-.detail-scroll {
|
|
|
- flex: 1;
|
|
|
- height: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.bottom-spacer {
|
|
|
- height: 120rpx;
|
|
|
-}
|
|
|
-
|
|
|
-// ====== Loading ======
|
|
|
-.load-tip {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- padding: 200rpx 0;
|
|
|
-}
|
|
|
+.page { height: 100vh; background: $bg-color-page; display: flex; flex-direction: column; font-family: $font-stack; overflow-x: hidden; }
|
|
|
+.detail-scroll { flex: 1; height: 0; padding-bottom: 160rpx; }
|
|
|
|
|
|
+// ====== Loading / Error ======
|
|
|
+.load-tip { display: flex; justify-content: center; padding: 200rpx 0; }
|
|
|
.dot-row { display: flex; gap: $spacing-1; }
|
|
|
-
|
|
|
-.pulse-dot {
|
|
|
- width: 10rpx; height: 10rpx;
|
|
|
- background: $text-color-placeholder;
|
|
|
- border-radius: 50%;
|
|
|
- animation: pulse 1.2s ease-in-out infinite;
|
|
|
- &:nth-child(2) { animation-delay: 0.2s; }
|
|
|
- &:nth-child(3) { animation-delay: 0.4s; }
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes pulse {
|
|
|
- 0%, 80%, 100% { transform: scale(0.5); opacity: 0.4; }
|
|
|
- 40% { transform: scale(1); opacity: 1; }
|
|
|
+.pulse-dot { width: 10rpx; height: 10rpx; background: $text-color-placeholder; border-radius: 50%; animation: pulse 1.2s ease-in-out infinite;
|
|
|
+ &:nth-child(2) { animation-delay: 0.2s; } &:nth-child(3) { animation-delay: 0.4s; }
|
|
|
}
|
|
|
-
|
|
|
-// ====== Error ======
|
|
|
-.error-state {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- padding: 200rpx $spacing-4;
|
|
|
-
|
|
|
- .error-icon {
|
|
|
- width: 80rpx;
|
|
|
- height: 80rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: $error-color-bg;
|
|
|
- margin-bottom: $spacing-3;
|
|
|
- position: relative;
|
|
|
-
|
|
|
- &::before {
|
|
|
- content: '!';
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- font-size: 40rpx;
|
|
|
- font-weight: 700;
|
|
|
- color: $error-color;
|
|
|
- line-height: 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .error-text {
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-secondary;
|
|
|
- margin-bottom: $spacing-4;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
-
|
|
|
- .retry-btn {
|
|
|
- padding: $spacing-2 $spacing-6;
|
|
|
- background: $primary-color;
|
|
|
- border-radius: $radius-full;
|
|
|
-
|
|
|
- text { font-size: $font-size-base; color: $text-color-primary; font-weight: 500; }
|
|
|
+@keyframes pulse { 0%,80%,100% { transform: scale(0.5); opacity: 0.4; } 40% { transform: scale(1); opacity: 1; } }
|
|
|
+.error-state { display: flex; flex-direction: column; align-items: center; padding: 200rpx $spacing-4;
|
|
|
+ .error-icon { width: 80rpx; height: 80rpx; border-radius: 50%; background: $error-color-bg; margin-bottom: $spacing-3; position: relative;
|
|
|
+ &::before { content: '!'; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); font-size: 40rpx; font-weight: 700; color: $error-color; line-height: 1; }
|
|
|
}
|
|
|
+ .error-text { font-size: $font-size-base; color: $text-color-secondary; margin-bottom: $spacing-4; text-align: center; }
|
|
|
+ .retry-btn { padding: $spacing-2 $spacing-6; background: $primary-color; border-radius: $radius-full; text { font-size: $font-size-base; color: $text-color-primary; font-weight: 500; } }
|
|
|
}
|
|
|
|
|
|
-// ====== Status ======
|
|
|
-.status-section {
|
|
|
- padding: $spacing-4 $spacing-4 $spacing-3;
|
|
|
- background: $bg-color-card;
|
|
|
- margin-bottom: $spacing-2;
|
|
|
-
|
|
|
- .status-row {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 12rpx;
|
|
|
-
|
|
|
- .status-tag {
|
|
|
- padding: 4rpx 16rpx;
|
|
|
- border-radius: $radius-sm;
|
|
|
- font-size: 24rpx;
|
|
|
- font-weight: 500;
|
|
|
- margin-right: 16rpx;
|
|
|
-
|
|
|
- &.success { background: $success-color-bg; color: $success-color; }
|
|
|
- &.danger { background: $error-color-bg; color: $error-color; }
|
|
|
- &.warning { background: $warning-color-bg; color: $warning-color; }
|
|
|
- }
|
|
|
-
|
|
|
- .order-no {
|
|
|
- font-size: $font-size-md;
|
|
|
- font-weight: 600;
|
|
|
- color: $text-color-primary;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .status-desc {
|
|
|
- font-size: $font-size-sm;
|
|
|
-
|
|
|
- &.success { color: $success-color; }
|
|
|
- &.danger { color: $error-color; }
|
|
|
- &.warning { color: $warning-color; }
|
|
|
+// ====== Cards ======
|
|
|
+.card { margin-bottom: $spacing-2; padding: $spacing-3 $spacing-4; background: $bg-color-card; }
|
|
|
+.card-title { display: block; font-size: $font-size-md; font-weight: 600; color: $text-color-primary; margin-bottom: $spacing-3; padding-bottom: $spacing-2; border-bottom: 1rpx solid $border-color-light; }
|
|
|
+
|
|
|
+// ====== Status card ======
|
|
|
+.status-card {
|
|
|
+ .status-left { display: flex; align-items: center; gap: 20rpx; }
|
|
|
+ .status-circle {
|
|
|
+ width: 72rpx; height: 72rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
|
+ text { font-size: 36rpx; font-weight: 700; color: #fff; }
|
|
|
+ &.success { background: $success-color; }
|
|
|
+ &.danger { background: $error-color; }
|
|
|
+ &.warning { background: $warning-color; }
|
|
|
}
|
|
|
+ .status-text-wrap { .status-label { display: block; font-size: $font-size-lg; font-weight: 600; color: $text-color-primary; margin-bottom: 4rpx; } .status-desc { font-size: $font-size-sm; color: $text-color-muted; } }
|
|
|
}
|
|
|
|
|
|
// ====== Video ======
|
|
|
-.video-section {
|
|
|
- margin-bottom: $spacing-2;
|
|
|
-
|
|
|
- .order-video {
|
|
|
- width: 100%;
|
|
|
- height: 400rpx;
|
|
|
- background: $bg-color-secondary;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// ====== Section ======
|
|
|
-.section {
|
|
|
- margin-bottom: $spacing-2;
|
|
|
- padding: $spacing-3 $spacing-4;
|
|
|
- background: $bg-color-card;
|
|
|
-
|
|
|
- .section-title {
|
|
|
- display: block;
|
|
|
- font-size: $font-size-md;
|
|
|
- font-weight: 600;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: $spacing-3;
|
|
|
- padding-bottom: $spacing-2;
|
|
|
- border-bottom: 1rpx solid $border-color-light;
|
|
|
- }
|
|
|
-}
|
|
|
+.video-section { margin-bottom: $spacing-2; .order-video { width: 100%; height: 400rpx; background: $bg-color-secondary; } }
|
|
|
|
|
|
// ====== Product list ======
|
|
|
.product-item {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- padding: $spacing-2 0;
|
|
|
- border-bottom: 1rpx solid $border-color-light;
|
|
|
-
|
|
|
+ display: flex; align-items: center; padding: $spacing-2 0; border-bottom: 1rpx solid $border-color-light;
|
|
|
&:last-child { border-bottom: none; }
|
|
|
-
|
|
|
- .product-img {
|
|
|
- width: 100rpx;
|
|
|
- height: 100rpx;
|
|
|
- border-radius: $radius-sm;
|
|
|
- background: $bg-color-page;
|
|
|
- flex-shrink: 0;
|
|
|
- margin-right: 16rpx;
|
|
|
+ .product-img { width: 120rpx; height: 120rpx; border-radius: $radius-sm; background: $bg-color-page; flex-shrink: 0; margin-right: 16rpx; }
|
|
|
+ .product-main { flex: 1;
|
|
|
+ .product-name { display: block; font-size: $font-size-base; color: $text-color-primary; margin-bottom: 6rpx; }
|
|
|
+ .product-price { font-size: $font-size-sm; color: $text-color-muted; }
|
|
|
}
|
|
|
-
|
|
|
- .product-main {
|
|
|
- flex: 1;
|
|
|
-
|
|
|
- .product-name {
|
|
|
- display: block;
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-primary;
|
|
|
- margin-bottom: 4rpx;
|
|
|
- }
|
|
|
-
|
|
|
- .product-meta {
|
|
|
- font-size: $font-size-sm;
|
|
|
- color: $text-color-muted;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .product-price {
|
|
|
- text-align: right;
|
|
|
- flex-shrink: 0;
|
|
|
-
|
|
|
- .price-unit {
|
|
|
- display: block;
|
|
|
- font-size: $font-size-sm;
|
|
|
- color: $text-color-muted;
|
|
|
- margin-bottom: 4rpx;
|
|
|
- }
|
|
|
-
|
|
|
- .price-total {
|
|
|
- font-size: $font-size-base;
|
|
|
- font-weight: 600;
|
|
|
- color: $text-color-primary;
|
|
|
- }
|
|
|
+ .product-right { text-align: right; flex-shrink: 0;
|
|
|
+ .product-qty { display: block; font-size: $font-size-sm; color: $text-color-muted; margin-bottom: 6rpx; }
|
|
|
+ .product-subtotal { font-size: $font-size-base; font-weight: 600; color: $text-color-primary; }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-.product-empty {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- padding: $spacing-5 0;
|
|
|
-
|
|
|
- .empty-doc-icon {
|
|
|
- width: 48rpx;
|
|
|
- height: 56rpx;
|
|
|
- border: 2.5rpx solid $text-color-placeholder;
|
|
|
- border-radius: 4rpx;
|
|
|
- margin-bottom: $spacing-2;
|
|
|
- position: relative;
|
|
|
-
|
|
|
- &::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 16rpx;
|
|
|
- left: 10rpx;
|
|
|
- right: 10rpx;
|
|
|
- height: 2.5rpx;
|
|
|
- background: $text-color-placeholder;
|
|
|
- border-radius: 1rpx;
|
|
|
- box-shadow: 0 10rpx 0 $text-color-placeholder, 0 20rpx 0 $text-color-placeholder;
|
|
|
- }
|
|
|
+.product-empty { display: flex; flex-direction: column; align-items: center; padding: $spacing-5 0;
|
|
|
+ .empty-doc-icon { width: 48rpx; height: 56rpx; border: 2.5rpx solid $text-color-placeholder; border-radius: 4rpx; margin-bottom: $spacing-2; position: relative;
|
|
|
+ &::after { content: ''; position: absolute; top: 16rpx; left:10rpx; right:10rpx; height:2.5rpx; background:$text-color-placeholder; border-radius:1rpx; box-shadow:0 10rpx 0 $text-color-placeholder, 0 20rpx 0 $text-color-placeholder; }
|
|
|
}
|
|
|
-
|
|
|
text { font-size: $font-size-sm; color: $text-color-placeholder; }
|
|
|
}
|
|
|
|
|
|
// ====== Amount ======
|
|
|
-.amount-section {
|
|
|
- margin-top: $spacing-2;
|
|
|
- padding-top: $spacing-2;
|
|
|
- border-top: 1rpx solid $border-color-light;
|
|
|
-}
|
|
|
-
|
|
|
-.amount-row {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- padding: 8rpx 0;
|
|
|
-
|
|
|
- .amount-label {
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-secondary;
|
|
|
+.amount-section { margin-top: $spacing-2; padding-top: $spacing-2; border-top: 1rpx solid $border-color-light; }
|
|
|
+.amount-row { display: flex; align-items: center; justify-content: space-between; padding: 8rpx 0;
|
|
|
+ .amount-label { font-size: $font-size-base; color: $text-color-secondary; }
|
|
|
+ .amount-value { font-size: $font-size-base; font-weight: 600; color: $text-color-primary; }
|
|
|
+ &.discount { .amount-label { color: $success-color; } .amount-value { color: $success-color; font-weight: 500; } }
|
|
|
+ &.paid { padding-top: 16rpx;
|
|
|
+ .amount-value { font-size: $font-size-lg; font-weight: 700; }
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- .amount-value {
|
|
|
- font-size: $font-size-base;
|
|
|
- font-weight: 600;
|
|
|
- color: $text-color-primary;
|
|
|
-
|
|
|
- &.discount { color: $error-color; font-weight: 500; }
|
|
|
+// ====== Refund timeline ======
|
|
|
+.refund-item { display: flex; gap: 16rpx; padding: 8rpx 0;
|
|
|
+ .refund-timeline { display: flex; flex-direction: column; align-items: center; flex-shrink: 0;
|
|
|
+ .timeline-dot { width: 16rpx; height: 16rpx; border-radius: 50%; flex-shrink: 0;
|
|
|
+ &.success { background: $success-color; } &.rejected { background: $error-color; } &.pending { background: $warning-color; }
|
|
|
+ }
|
|
|
+ .timeline-line { width: 2rpx; flex: 1; min-height: 24rpx; background: $border-color-light; }
|
|
|
}
|
|
|
-
|
|
|
- &.total {
|
|
|
- padding-top: 16rpx;
|
|
|
-
|
|
|
- .amount-value { font-size: $font-size-lg; font-weight: 700; }
|
|
|
+ .refund-content { flex: 1; padding-bottom: $spacing-2;
|
|
|
+ .refund-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4rpx;
|
|
|
+ .refund-status { font-size: $font-size-base; font-weight: 500;
|
|
|
+ &.success { color: $success-color; } &.rejected { color: $error-color; } &.pending { color: $warning-color; }
|
|
|
+ }
|
|
|
+ .refund-amount { font-size: $font-size-base; font-weight: 600; color: $text-color-primary; }
|
|
|
+ }
|
|
|
+ .refund-time { font-size: $font-size-sm; color: $text-color-muted; display: block; }
|
|
|
+ .refund-reason { font-size: $font-size-sm; color: $text-color-secondary; margin-top: 4rpx; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ====== Info ======
|
|
|
-.info-row {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- padding: $spacing-3 0;
|
|
|
- border-bottom: 1rpx solid $border-color-light;
|
|
|
-
|
|
|
+.info-list .info-row { display: flex; align-items: center; justify-content: space-between; padding: $spacing-3 0; border-bottom: 1rpx solid $border-color-light;
|
|
|
&:last-child { border-bottom: none; }
|
|
|
-
|
|
|
- .info-label {
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-muted;
|
|
|
- flex-shrink: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info-value {
|
|
|
- font-size: $font-size-base;
|
|
|
- color: $text-color-primary;
|
|
|
- text-align: right;
|
|
|
-
|
|
|
- &.mono { letter-spacing: 0.02em; }
|
|
|
- }
|
|
|
-
|
|
|
- .info-value-row {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 16rpx;
|
|
|
- }
|
|
|
+ .info-label { font-size: $font-size-base; color: $text-color-muted; flex-shrink: 0; }
|
|
|
+ .info-value { font-size: $font-size-base; color: $text-color-primary; text-align: right; &.mono { letter-spacing: 0.02em; } }
|
|
|
+ .info-value-row { display: flex; align-items: center; gap: 16rpx; }
|
|
|
}
|
|
|
-
|
|
|
-// ====== Buttons ======
|
|
|
-.copy-btn, .action-btn {
|
|
|
- padding: 6rpx 20rpx;
|
|
|
- border-radius: $radius-sm;
|
|
|
- flex-shrink: 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- text { font-size: 22rpx; line-height: 1; }
|
|
|
+.copy-btn { padding: 6rpx 20rpx; border-radius: $radius-sm; background: $bg-color-page; flex-shrink: 0; display: flex; align-items: center;
|
|
|
+ text { font-size: 22rpx; color: $text-color-secondary; line-height: 1; }
|
|
|
&:active { opacity: 0.7; }
|
|
|
}
|
|
|
|
|
|
-.copy-btn {
|
|
|
- background: $bg-color-page;
|
|
|
-
|
|
|
- text { color: $text-color-secondary; }
|
|
|
+// ====== Bottom bar ======
|
|
|
+.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; padding: $spacing-3 $spacing-4; padding-bottom: calc($spacing-3 + env(safe-area-inset-bottom)); background: $bg-color-card; border-top: 1rpx solid $border-color-light; }
|
|
|
+.refund-btn { display: flex; align-items: center; justify-content: center; gap: $spacing-2; width: 100%; padding: 26rpx 0; border-radius: $radius-xl; background: $primary-color; transition: opacity 0.15s ease;
|
|
|
+ &:active { opacity: 0.8; } &.disabled { opacity: 0.6; pointer-events: none; }
|
|
|
+ .refund-btn-text { font-size: $font-size-md; font-weight: 600; color: $text-color-primary; }
|
|
|
}
|
|
|
+.loading-spinner { width: 32rpx; height: 32rpx; border: 3rpx solid rgba(30, 41, 59, 0.15); border-top-color: $text-color-primary; border-radius: 50%; animation: spin 0.8s linear infinite; }
|
|
|
+@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
|
|
-.call-btn {
|
|
|
- background: $primary-color-bg;
|
|
|
+// ====== Refund modal ======
|
|
|
+.refund-modal { position: fixed; inset: 0; z-index: 200; display: flex; align-items: flex-end; background: rgba(30, 30, 30, 0.5); }
|
|
|
+.refund-modal-content { width: 100%; max-height: 85vh; background: $bg-color-card; border-radius: $radius-xl $radius-xl 0 0; display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; }
|
|
|
+.modal-header { display: flex; align-items: center; justify-content: space-between; padding: $spacing-3 $spacing-4; border-bottom: 1rpx solid $border-color-light;
|
|
|
+ .modal-title { font-size: $font-size-lg; font-weight: 600; color: $text-color-primary; }
|
|
|
+ .modal-close { width: 44rpx; height: 44rpx; position: relative;
|
|
|
+ &::before,&::after { content: ''; position: absolute; top: 50%; left: 50%; width: 20rpx; height: 2rpx; background: $text-color-muted; border-radius: 1rpx; }
|
|
|
+ &::before { transform: translate(-50%,-50%) rotate(45deg); } &::after { transform: translate(-50%,-50%) rotate(-45deg); }
|
|
|
+ }
|
|
|
+}
|
|
|
+.modal-body { padding: $spacing-3 $spacing-4; flex: 1; max-height: 55vh; }
|
|
|
|
|
|
- text { color: $primary-color-dark; font-weight: 500; }
|
|
|
+.refund-type-row { display: flex; gap: 16rpx; margin-bottom: $spacing-3; }
|
|
|
+.refund-type-chip { padding: 12rpx 32rpx; border-radius: $radius-full; background: $bg-color-page; border: 1rpx solid transparent;
|
|
|
+ text { font-size: $font-size-base; color: $text-color-secondary; }
|
|
|
+ &.active { background: $primary-color-bg; border-color: $primary-color; text { color: $primary-color-dark; font-weight: 500; } }
|
|
|
}
|
|
|
|
|
|
-// ====== Bottom ======
|
|
|
-.bottom-bar {
|
|
|
- position: fixed;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- padding: $spacing-3 $spacing-4;
|
|
|
- padding-bottom: calc($spacing-3 + env(safe-area-inset-bottom));
|
|
|
- background: $bg-color-card;
|
|
|
- border-top: 1rpx solid $border-color-light;
|
|
|
+.refunded-tip { padding: $spacing-2; margin-bottom: $spacing-3; background: $warning-color-bg; border-radius: $radius-sm;
|
|
|
+ text { font-size: $font-size-sm; color: $warning-color; }
|
|
|
}
|
|
|
|
|
|
-.refund-btn {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- gap: $spacing-2;
|
|
|
- width: 100%;
|
|
|
- padding: 26rpx 0;
|
|
|
- border-radius: $radius-xl;
|
|
|
- background: $primary-color;
|
|
|
- transition: opacity 0.15s ease;
|
|
|
-
|
|
|
- &:active { opacity: 0.8; }
|
|
|
-
|
|
|
- &.disabled { opacity: 0.6; pointer-events: none; }
|
|
|
-
|
|
|
- .refund-btn-text {
|
|
|
- font-size: $font-size-md;
|
|
|
- font-weight: 600;
|
|
|
- color: $text-color-primary;
|
|
|
+.form-group { margin-bottom: $spacing-3; }
|
|
|
+.form-label { display: block; font-size: $font-size-base; color: $text-color-secondary; margin-bottom: $spacing-1; }
|
|
|
+.form-input { width: 100%; height: 80rpx; padding: 0 $spacing-3; background: $bg-color-page; border-radius: $radius-sm; font-size: $font-size-base; color: $text-color-primary; box-sizing: border-box; }
|
|
|
+.form-textarea { width: 100%; min-height: 120rpx; padding: $spacing-2 $spacing-3; background: $bg-color-page; border-radius: $radius-sm; font-size: $font-size-base; color: $text-color-primary; box-sizing: border-box; }
|
|
|
+
|
|
|
+.refund-product-item { display: flex; align-items: center; padding: 12rpx 0; width: 100%; box-sizing: border-box;
|
|
|
+ .rp-left { display: flex; align-items: center; gap: 12rpx; flex: 1; min-width: 0; overflow: hidden; }
|
|
|
+ .rp-checkbox { width: 36rpx; height: 36rpx; border: 2rpx solid $border-color; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
|
+ text { font-size: 20rpx; color: $primary-color; }
|
|
|
+ &.checked { background: $primary-color; border-color: $primary-color; text { color: #fff; } }
|
|
|
+ }
|
|
|
+ .rp-img { width: 64rpx; height: 64rpx; border-radius: $radius-sm; background: $bg-color-page; flex-shrink: 0; }
|
|
|
+ .rp-info { flex: 1; min-width: 0; overflow: hidden;
|
|
|
+ .rp-name { display: block; font-size: 26rpx; color: $text-color-primary; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
+ .rp-price { font-size: 22rpx; color: $text-color-muted; }
|
|
|
+ }
|
|
|
+ .rp-qty { display: flex; align-items: center; gap: 8rpx; flex-shrink: 0; margin-left: 8rpx;
|
|
|
+ .qty-btn { width: 44rpx; height: 44rpx; border: 1rpx solid $border-color; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; text { font-size: 28rpx; color: $text-color-secondary; line-height: 1; } }
|
|
|
+ .qty-value { font-size: $font-size-base; color: $text-color-primary; min-width: 32rpx; text-align: center; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.loading-spinner {
|
|
|
- width: 32rpx;
|
|
|
- height: 32rpx;
|
|
|
- border: 3rpx solid rgba(30, 41, 59, 0.15);
|
|
|
- border-top-color: $text-color-primary;
|
|
|
- border-radius: 50%;
|
|
|
- animation: spin 0.8s linear infinite;
|
|
|
+.refund-total { display: flex; align-items: center; justify-content: space-between; padding: $spacing-3 0; margin-top: $spacing-2; border-top: 1rpx solid $border-color-light;
|
|
|
+ .total-label { font-size: $font-size-base; color: $text-color-secondary; }
|
|
|
+ .total-value { font-size: $font-size-xl; font-weight: 700; color: $text-color-primary; }
|
|
|
}
|
|
|
|
|
|
-@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
+.modal-footer { display: flex; gap: $spacing-2; padding: 0 $spacing-4 $spacing-4;
|
|
|
+ button { flex: 1; height: 80rpx; line-height: 80rpx; padding: 0; border: none; border-radius: $radius-base; font-size: $font-size-base; font-weight: 500; &::after { border: none; } }
|
|
|
+ .btn-cancel { background: $bg-color-page; color: $text-color-secondary; }
|
|
|
+ .btn-confirm { background: $primary-color; color: $text-color-primary; display: flex; align-items: center; justify-content: center; gap: $spacing-2;
|
|
|
+ &.disabled { opacity: 0.6; pointer-events: none; }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|