|
|
@@ -0,0 +1,805 @@
|
|
|
+<template>
|
|
|
+ <cwg-page-wrapper>
|
|
|
+ <view class="order-detail-page">
|
|
|
+
|
|
|
+ <!-- Content -->
|
|
|
+ <view class="content">
|
|
|
+ <!-- Status Card -->
|
|
|
+ <!-- <view class="status-card">
|
|
|
+ <view class="status-icon-wrapper">
|
|
|
+ <view :class="['status-icon', `status-icon-${orderDetail.orderStatus}`]">
|
|
|
+ <uni-icons :type="getOrderStatusIcon(orderDetail.orderStatus)" size="40"
|
|
|
+ :color="getOrderStatusColor(orderDetail.orderStatus)" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="status-title">{{ getOrderStatusText(orderDetail.orderStatus) }}</text>
|
|
|
+ <text class="status-subtitle">{{ orderDetail.statusMessage }}</text>
|
|
|
+ </view> -->
|
|
|
+ <!-- Amount Info -->
|
|
|
+ <view class="section-card">
|
|
|
+ <view class="section-header">
|
|
|
+ <uni-icons type="wallet" size="18" color="#2563eb" />
|
|
|
+ <text class="section-title">{{ t('card.Form.f37') }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-list">
|
|
|
+ <view class="info-row" v-for="(item, index) in list.common" :key="index">
|
|
|
+ <text class="info-label">{{ item.name }}</text>
|
|
|
+ <text class="info-value">{{ item.value }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">{{ t('card.Form.f37') }}</text>
|
|
|
+ <text class="info-value amount-highlight">
|
|
|
+ {{ detailData.deductionAmount || '0' }}
|
|
|
+ <text class="info-valuecurrency">USD</text>
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label">{{ t('card.Form.f30') }}</text>
|
|
|
+ <text class="info-value">
|
|
|
+ {{ detailData.deductionFee || '0' }}
|
|
|
+ <text class="currency">USD</text>
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- <view class="divider"></view>
|
|
|
+
|
|
|
+ <view class="info-row">
|
|
|
+ <text class="info-label total-label">{{ t('card.Form.f55') }}</text>
|
|
|
+ <text class="info-value total-value">
|
|
|
+ {{ detailData.actualAmount.toFixed(2) }} <text class="currency">{{ orderDetail.currency || 'USD'
|
|
|
+ }}</text>
|
|
|
+ </text>
|
|
|
+ </view> -->
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- Order Info -->
|
|
|
+ <view class="section-card">
|
|
|
+ <view class="section-header">
|
|
|
+ <uni-icons type="list" size="18" color="#2563eb" />
|
|
|
+ <text class="section-title">{{ getGroupTitle('sender') }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-list">
|
|
|
+ <!-- <view class="info-row" v-if="orderDetail.orderNo">
|
|
|
+ <text class="info-label">{{ t('card.Form.f35') }}</text>
|
|
|
+ <view class="info-value-wrapper">
|
|
|
+ <text class="info-value">{{ orderDetail.orderNo }}</text>
|
|
|
+ <cwg-icon name="copy" :size="14" color="#9ca3af" @click.stop="copyOrderNo" />
|
|
|
+ </view>
|
|
|
+ </view> -->
|
|
|
+
|
|
|
+ <view class="info-row" v-for="(item, index) in list.sender" :key="index">
|
|
|
+ <text class="info-label">{{ item.name }}</text>
|
|
|
+ <text class="info-value">{{ item.value }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="section-card">
|
|
|
+ <view class="section-header">
|
|
|
+ <uni-icons type="list" size="18" color="#2563eb" />
|
|
|
+ <text class="section-title">{{ getGroupTitle('receiver') }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-list">
|
|
|
+ <!-- <view class="info-row" v-if="orderDetail.orderNo">
|
|
|
+ <text class="info-label">{{ t('card.Form.f35') }}</text>
|
|
|
+ <view class="info-value-wrapper">
|
|
|
+ <text class="info-value">{{ orderDetail.orderNo }}</text>
|
|
|
+ <cwg-icon name="copy" :size="14" color="#9ca3af" @click.stop="copyOrderNo" />
|
|
|
+ </view>
|
|
|
+ </view> -->
|
|
|
+
|
|
|
+ <view class="info-row" v-for="(item, index) in list.receiver" :key="index">
|
|
|
+ <text class="info-label">{{ item.name }}</text>
|
|
|
+ <text class="info-value">{{ item.value }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </cwg-page-wrapper>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, nextTick, computed } from 'vue'
|
|
|
+import { onLoad, onUnload } from '@dcloudio/uni-app';
|
|
|
+import { useI18n } from 'vue-i18n';
|
|
|
+import useCardStore from '@/stores/use-card-store';
|
|
|
+import { ucardApi } from "@/api/ucard";
|
|
|
+
|
|
|
+type OrderStatus = 'success' | 'processing' | 'failed' | 'cancelled';
|
|
|
+type ApprovalStatus = 'completed' | 'current' | 'pending';
|
|
|
+
|
|
|
+interface ApprovalStep {
|
|
|
+ title: string;
|
|
|
+ status: ApprovalStatus;
|
|
|
+ operator?: string;
|
|
|
+ time?: string;
|
|
|
+ remark?: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface OrderDetail {
|
|
|
+ category?: 'recharge' | 'transaction' | 'deduction';
|
|
|
+ orderNo: string;
|
|
|
+ type: string;
|
|
|
+ amount: number;
|
|
|
+ fee: number;
|
|
|
+ actualAmount: number;
|
|
|
+ currency?: string;
|
|
|
+ orderStatus: OrderStatus;
|
|
|
+ statusMessage: string;
|
|
|
+ createTime: string;
|
|
|
+ completeTime?: string;
|
|
|
+ merchant?: string;
|
|
|
+ bankCard?: string;
|
|
|
+ remark?: string;
|
|
|
+ approvalSteps: ApprovalStep[];
|
|
|
+}
|
|
|
+
|
|
|
+// 订单详情数据(默认占位,进入页面后会用缓存覆盖)
|
|
|
+const orderDetail = reactive<OrderDetail>({});
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
+const cardStore = useCardStore();
|
|
|
+
|
|
|
+const getGroupTitle = (type) => {
|
|
|
+ const map = {
|
|
|
+ common: "global.GlobalOrder.common",
|
|
|
+ receiver: "global.GlobalOrder.receiver",
|
|
|
+ sender: "global.GlobalOrder.sender",
|
|
|
+ other: "global.GlobalOrder.other",
|
|
|
+ submitRfi: "global.GlobalOrder.submitRfi",
|
|
|
+ };
|
|
|
+ return t(map[type] || type);
|
|
|
+}
|
|
|
+
|
|
|
+// 获取订单状态图标
|
|
|
+const getOrderStatusIcon = (status: OrderStatus): string => {
|
|
|
+ switch (status) {
|
|
|
+ case 'success':
|
|
|
+ return 'checkmarkempty';
|
|
|
+ case 'processing':
|
|
|
+ return 'loop';
|
|
|
+ case 'failed':
|
|
|
+ return 'closeempty';
|
|
|
+ case 'cancelled':
|
|
|
+ return 'closeempty';
|
|
|
+ default:
|
|
|
+ return 'info';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取订单状态颜色
|
|
|
+const getOrderStatusColor = (status: OrderStatus): string => {
|
|
|
+ switch (status) {
|
|
|
+ case 'success':
|
|
|
+ return '#22c55e';
|
|
|
+ case 'processing':
|
|
|
+ return '#eab308';
|
|
|
+ case 'failed':
|
|
|
+ return '#ef4444';
|
|
|
+ case 'cancelled':
|
|
|
+ return '#9ca3af';
|
|
|
+ default:
|
|
|
+ return '#9ca3af';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取订单状态文本
|
|
|
+const getOrderStatusText = (status: OrderStatus): string => {
|
|
|
+ switch (status) {
|
|
|
+ case 'success':
|
|
|
+ return t('card.Status.t1'); // 成功
|
|
|
+ case 'processing':
|
|
|
+ return t('card.Status.t3'); // 处理中
|
|
|
+ case 'failed':
|
|
|
+ return t('card.Status.t2'); // 失败
|
|
|
+ case 'cancelled':
|
|
|
+ return t('card.Status.t5'); // 待处理 / 已取消,复用状态文案
|
|
|
+ default:
|
|
|
+ return t('card.Status.t5');
|
|
|
+ }
|
|
|
+};
|
|
|
+// 复制订单号
|
|
|
+const copyOrderNo = () => {
|
|
|
+ uni.setClipboardData({
|
|
|
+ data: orderDetail.orderNo,
|
|
|
+ success: () => {
|
|
|
+ uni.showToast({
|
|
|
+ title: '订单号已复制',
|
|
|
+ icon: 'success'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 取消订单
|
|
|
+const cancelOrder = () => {
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认取消',
|
|
|
+ content: '确定要取消此订单吗?',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '订单已取消',
|
|
|
+ icon: 'success'
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 申诉订单
|
|
|
+const appealOrder = () => {
|
|
|
+ uni.showToast({
|
|
|
+ title: '提交申诉',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+const isView = ref(false)
|
|
|
+
|
|
|
+const detailData = ref<any>({})
|
|
|
+const list = ref<Record<string, any[]>>({})
|
|
|
+
|
|
|
+const complianceItems = ref<any[]>([])
|
|
|
+const globalForm = reactive<Record<string, any>>({})
|
|
|
+
|
|
|
+const complianceStatus = ref(false)
|
|
|
+
|
|
|
+// 外部参数
|
|
|
+const type = ref<'1' | '2'>('1')
|
|
|
+async function getOrderDetail(id: string | number) {
|
|
|
+ try {
|
|
|
+ isView.value = false
|
|
|
+
|
|
|
+ const res = await ucardApi.globalOrdersDetail({ id })
|
|
|
+
|
|
|
+ if (res.code !== 200) {
|
|
|
+ // ElMessage.error(res.msg)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ detailData.value = res.data
|
|
|
+ console.log(21312312, detailData.value, [...detailData.value.fieldDtos]);
|
|
|
+
|
|
|
+
|
|
|
+ /** 1️⃣ 处理 fieldDtos */
|
|
|
+ /** 1️⃣ 处理 fieldDtos */
|
|
|
+ /** 1️⃣ 处理 fieldDtos */
|
|
|
+ let a = []
|
|
|
+ let listData = [...detailData.value.fieldDtos]
|
|
|
+ .sort((a, b) => a.sorting - b.sorting)
|
|
|
+ listData.map((item) => {
|
|
|
+ const key = Object.keys(detailData.value).find(
|
|
|
+ (k) => k.toLowerCase() === item.fieldName.toLowerCase()
|
|
|
+ )
|
|
|
+
|
|
|
+ // const { t } = useI18n()
|
|
|
+ let name = item.fieldName // 默认使用字段名
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 检查国际化键是否存在
|
|
|
+ const i18nKey = `global.fieldName.${item.fieldName}.fieldTitle`
|
|
|
+ name = t(i18nKey)
|
|
|
+ // 如果国际化返回的值和键相同,说明未找到翻译,使用默认值
|
|
|
+ if (name === i18nKey) {
|
|
|
+ name = item.fieldName
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('国际化处理错误:', error)
|
|
|
+ name = item.fieldName
|
|
|
+ }
|
|
|
+
|
|
|
+ let value = key ? detailData.value[key] : item.fixedValue
|
|
|
+
|
|
|
+ // 特殊处理 select 类型字段
|
|
|
+ if (
|
|
|
+ item.fieldType === 'select' &&
|
|
|
+ key &&
|
|
|
+ !['transferType', 'payoutMethod'].includes(key)
|
|
|
+ ) {
|
|
|
+ value = detailData.value[key + 'Value'] || value
|
|
|
+ }
|
|
|
+ // 确定字段类型,提供默认值
|
|
|
+ let type = item.fieldUserType || 'other'
|
|
|
+ let b = {
|
|
|
+ name,
|
|
|
+ value,
|
|
|
+ type,
|
|
|
+ fieldName: item.fieldName,
|
|
|
+ fieldType: item.fieldType,
|
|
|
+ options: item.options || null
|
|
|
+ }
|
|
|
+ if (item.fieldName === 'transferAmount') b.value = value + ' ' + detailData.value.payoutCurrency
|
|
|
+ a.push(b)
|
|
|
+ return {
|
|
|
+ name,
|
|
|
+ value,
|
|
|
+ type,
|
|
|
+ fieldName: item.fieldName,
|
|
|
+ fieldType: item.fieldType,
|
|
|
+ options: item.options || null
|
|
|
+ }
|
|
|
+ })
|
|
|
+ /** 2️⃣ 分组 */
|
|
|
+ const groups: Record<string, any[]> = {}
|
|
|
+
|
|
|
+ a.forEach((field) => {
|
|
|
+
|
|
|
+ let type = field.type || field.fieldUserType || 'other'
|
|
|
+ // if (type === 'common') type = 'sender'
|
|
|
+ if (!groups[type]) groups[type] = []
|
|
|
+ groups[type].push(field)
|
|
|
+ })
|
|
|
+
|
|
|
+ list.value = groups
|
|
|
+ console.log(list.value, 22222);
|
|
|
+
|
|
|
+
|
|
|
+ /** 3️⃣ nextTick 后处理合规字段 */
|
|
|
+ await nextTick()
|
|
|
+
|
|
|
+ let items: any[] = []
|
|
|
+
|
|
|
+ if (detailData.value?.dataDtos) {
|
|
|
+ items = detailData.value.dataDtos.map((item) => {
|
|
|
+ item.fieldName = `${item.customerType}_${item.fieldName}`
|
|
|
+
|
|
|
+ if (item.status !== 'pending_check') {
|
|
|
+ globalForm[item.fieldName] =
|
|
|
+ item.fieldType === 'file'
|
|
|
+ ? item.rfiValueUrl
|
|
|
+ : item.rfiValue
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ disabled: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return { ...item }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ complianceItems.value = items
|
|
|
+
|
|
|
+ /** 4️⃣ 合规状态 */
|
|
|
+ let status = false
|
|
|
+
|
|
|
+ if (type.value === '1') {
|
|
|
+ status =
|
|
|
+ detailData.value?.complianceStatus !== 'pending_check' &&
|
|
|
+ detailData.value?.complianceStatus !== null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (type.value === '2') {
|
|
|
+ status = true
|
|
|
+ }
|
|
|
+
|
|
|
+ complianceStatus.value = status
|
|
|
+ isView.value = true
|
|
|
+ } catch (error: any) {
|
|
|
+ console.log(error, 12121);
|
|
|
+
|
|
|
+ // ElMessage.error(error?.message || 'System Error')
|
|
|
+ } finally {
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 页面加载时,从 store 中读取订单详情
|
|
|
+onLoad((e) => {
|
|
|
+ console.log(e, 2313);
|
|
|
+
|
|
|
+ getOrderDetail(e.id);
|
|
|
+
|
|
|
+});
|
|
|
+
|
|
|
+// 离开页面时清空订单详情
|
|
|
+onUnload(() => {
|
|
|
+ // cardStore.clearOrderDetail();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+@import "@/uni.scss";
|
|
|
+
|
|
|
+.page-wrapper {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.order-detail-page {
|
|
|
+ background-color: #f9fafb;
|
|
|
+ padding-bottom: px2rpx(80);
|
|
|
+}
|
|
|
+
|
|
|
+/* Header */
|
|
|
+.header {
|
|
|
+ background: linear-gradient(135deg, #2563eb 0%, #60a5fa 100%);
|
|
|
+ padding: px2rpx(12) px2rpx(16);
|
|
|
+ padding-top: calc(px2rpx(12) + env(safe-area-inset-top));
|
|
|
+}
|
|
|
+
|
|
|
+.header-nav {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.back-btn {
|
|
|
+ width: px2rpx(40);
|
|
|
+ height: px2rpx(40);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.header-title {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: px2rpx(18);
|
|
|
+}
|
|
|
+
|
|
|
+.header-action {
|
|
|
+ width: px2rpx(40);
|
|
|
+}
|
|
|
+
|
|
|
+/* Content */
|
|
|
+.content {
|
|
|
+ padding: px2rpx(16);
|
|
|
+}
|
|
|
+
|
|
|
+/* Status Card */
|
|
|
+.status-card {
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: px2rpx(16);
|
|
|
+ padding: px2rpx(32) px2rpx(24);
|
|
|
+ margin-bottom: px2rpx(16);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.status-icon-wrapper {
|
|
|
+ margin-bottom: px2rpx(16);
|
|
|
+}
|
|
|
+
|
|
|
+.status-icon {
|
|
|
+ width: px2rpx(80);
|
|
|
+ height: px2rpx(80);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.status-icon-success {
|
|
|
+ background-color: #f0fdf4;
|
|
|
+}
|
|
|
+
|
|
|
+.status-icon-processing {
|
|
|
+ background-color: #fefce8;
|
|
|
+}
|
|
|
+
|
|
|
+.status-icon-failed {
|
|
|
+ background-color: #fef2f2;
|
|
|
+}
|
|
|
+
|
|
|
+.status-icon-cancelled {
|
|
|
+ background-color: #f9fafb;
|
|
|
+}
|
|
|
+
|
|
|
+.status-title {
|
|
|
+ font-size: px2rpx(22);
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: px2rpx(8);
|
|
|
+}
|
|
|
+
|
|
|
+.status-subtitle {
|
|
|
+ font-size: px2rpx(14);
|
|
|
+ color: #6b7280;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* Section Card */
|
|
|
+.section-card {
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: px2rpx(12);
|
|
|
+ padding: px2rpx(16);
|
|
|
+ margin-bottom: px2rpx(16);
|
|
|
+}
|
|
|
+
|
|
|
+.section-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: px2rpx(8);
|
|
|
+ margin-bottom: px2rpx(16);
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: px2rpx(16);
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+/* Approval Timeline */
|
|
|
+.approval-timeline {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-item {
|
|
|
+ display: flex;
|
|
|
+ gap: px2rpx(12);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-left {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-dot {
|
|
|
+ width: px2rpx(24);
|
|
|
+ height: px2rpx(24);
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #f3f4f6;
|
|
|
+ border: 2px solid #e5e7eb;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-dot-active {
|
|
|
+ background-color: #22c55e;
|
|
|
+ border-color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-dot-current {
|
|
|
+ background-color: #eab308;
|
|
|
+ border-color: #eab308;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes pulse {
|
|
|
+
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 50% {
|
|
|
+ opacity: 0.7;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-line {
|
|
|
+ width: px2rpx(2);
|
|
|
+ flex: 1;
|
|
|
+ background-color: #e5e7eb;
|
|
|
+ margin: px2rpx(4) 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-right {
|
|
|
+ flex: 1;
|
|
|
+ padding-bottom: px2rpx(24);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: px2rpx(6);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-title {
|
|
|
+ font-size: px2rpx(15);
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: px2rpx(4);
|
|
|
+ padding: px2rpx(2) px2rpx(8);
|
|
|
+ border-radius: px2rpx(12);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status.completed {
|
|
|
+ background-color: #f0fdf4;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status.current {
|
|
|
+ background-color: #fefce8;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status.pending {
|
|
|
+ background-color: #f9fafb;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status-text {
|
|
|
+ font-size: px2rpx(12);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status.completed .timeline-status-text {
|
|
|
+ color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status.current .timeline-status-text {
|
|
|
+ color: #eab308;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-status.pending .timeline-status-text {
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-operator {
|
|
|
+ font-size: px2rpx(13);
|
|
|
+ color: #6b7280;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: px2rpx(4);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-time {
|
|
|
+ font-size: px2rpx(12);
|
|
|
+ color: #9ca3af;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: px2rpx(4);
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-remark {
|
|
|
+ font-size: px2rpx(13);
|
|
|
+ color: #6b7280;
|
|
|
+ display: block;
|
|
|
+ margin-top: px2rpx(6);
|
|
|
+ padding: px2rpx(8);
|
|
|
+ background-color: #f9fafb;
|
|
|
+ border-radius: px2rpx(6);
|
|
|
+}
|
|
|
+
|
|
|
+/* Info List */
|
|
|
+.info-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: px2rpx(12);
|
|
|
+}
|
|
|
+
|
|
|
+.info-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: px2rpx(12);
|
|
|
+ padding: px2rpx(4) 0;
|
|
|
+}
|
|
|
+
|
|
|
+.info-row.vertical {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.info-label {
|
|
|
+ font-size: px2rpx(14);
|
|
|
+ color: #6b7280;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.info-value {
|
|
|
+ font-size: px2rpx(14);
|
|
|
+ color: #111827;
|
|
|
+ text-align: right;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.info-value-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: px2rpx(8);
|
|
|
+ flex: 1;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.amount-highlight {
|
|
|
+ font-size: px2rpx(20);
|
|
|
+ color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.total-label {
|
|
|
+ font-size: px2rpx(15);
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+.total-value {
|
|
|
+ font-size: px2rpx(18);
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.remark-text {
|
|
|
+ text-align: left;
|
|
|
+ color: #6b7280;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.divider {
|
|
|
+ height: px2rpx(1);
|
|
|
+ background-color: #f3f4f6;
|
|
|
+ margin: px2rpx(4) 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Service Card */
|
|
|
+.service-card {
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: px2rpx(12);
|
|
|
+ padding: px2rpx(16);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: px2rpx(12);
|
|
|
+ margin-bottom: px2rpx(16);
|
|
|
+}
|
|
|
+
|
|
|
+.service-text {
|
|
|
+ flex: 1;
|
|
|
+ font-size: px2rpx(15);
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+/* Bottom Actions */
|
|
|
+.bottom-actions {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
+ padding: px2rpx(12) px2rpx(16);
|
|
|
+ padding-bottom: calc(px2rpx(12) + env(safe-area-inset-bottom));
|
|
|
+ display: flex;
|
|
|
+ gap: px2rpx(12);
|
|
|
+}
|
|
|
+
|
|
|
+.action-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: px2rpx(44);
|
|
|
+ border-radius: px2rpx(8);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.cancel-btn {
|
|
|
+ background-color: #f3f4f6;
|
|
|
+}
|
|
|
+
|
|
|
+.cancel-btn .btn-text {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-btn {
|
|
|
+ background-color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-btn .btn-text {
|
|
|
+ color: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+.delete-btn {
|
|
|
+ background-color: #f3f4f6;
|
|
|
+}
|
|
|
+
|
|
|
+.delete-btn .btn-text {
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-text {
|
|
|
+ font-size: px2rpx(15);
|
|
|
+}
|
|
|
+
|
|
|
+.currency {
|
|
|
+ font-size: px2rpx(12);
|
|
|
+}
|
|
|
+</style>
|