admin_new/src/modules/order/OrderDetailView.vue

588 lines
26 KiB
Vue

<script setup lang="ts">
/**
* F006 订单详情 - W10 A6 标准化
*
* W10 A6: 套 PageHeader (返回按钮 slot)
*
* 沿用老 vue order/order-detail.vue 1379 行完整功能:
* - 4 个 tab: 订单信息 / 商品明细 / 物流信息 / 付款发票
* - 订单状态卡 + 操作按钮 (取消/付款/发货/收货/退款)
* - 商品明细 (多商品 + 拼团信息)
* - 物流信息 + 签收回单
* - 付款信息 (分笔) + 发票信息
*
* 支持 type 参数 (good/service/groupsupplier/groupservice)
* - 4 个老 vue 详情页 (order-good/service/groupsupplier/groupservice detail) 统一收敛
* - 不同 type 显示不同字段 + 不同操作按钮
*
* 字段: 100% 沿用老 vue OrderDetail/OrderGood/DeliveryItem/PayItem 接口
*/
import { ref, onMounted, watch, computed, reactive } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { httpGet } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
interface OrderDetail {
id: string
orderCode: string
status: number
payStatus: number | null
goodType: number
pickupType: number
enterpriseName: string
supplierName: string
contractCode: string
sum: number
couponMoney: number
freight: number
payMoney: number
createDate: string
payDate: string | null
remark: string | null
// 收货
address: {
name: string
mobile: string
provinceName: string
cityName: string
areaName: string
address: string
fullAddress: string
} | null
// 商品
orderGoodList: OrderGood[]
// 物流
deliveryList: DeliveryItem[]
acceptanceList: AcceptanceItem[]
// 付款
payList: PayItem[]
// 拼团
groupNumber: number
groupNickName: string
groupMobile: string
groupStatus: number
// 文件
fileUrl: string | null
invoice: string | null
acceptanceForm: string | null
// 推广
promoterName: string | null
promoterMobile: string | null
rebateMoney: number | null
raw: any
}
interface OrderGood {
id: string
title: string
icon: string | null
propertyNames: string
unitPrice: number
totalNum: number
shippedNum: number
orderMoney: number
status: number
payStatus: number
payType: number
createDate: string
payDate: string | null
pickupType: number
fileUrl: string | null
remark: string | null
raw: any
}
interface DeliveryItem {
id: string
deliveryCompany: string
deliveryNo: string
deliveryStatus: number
deliveryTime: string
receivedTime: string | null
deliveryList: DeliveryItemDetail[]
raw: any
}
interface DeliveryItemDetail {
id: string
goodName: string
num: number
raw: any
}
interface AcceptanceItem {
id: string
acceptanceNum: number
acceptanceTime: string
acceptanceFile: string | null
remark: string
raw: any
}
interface PayItem {
id: string
payType: number
payMoney: number
payTime: string
payStatus: number
invoiceStatus: number
remark: string
raw: any
}
const route = useRoute()
const router = useRouter()
const props = defineProps<{
type?: 'good' | 'service' | 'groupsupplier' | 'groupservice'
}>()
const orderId = computed(() => String(route.params.id || ''))
// 从 route.path 推断 type (例 /order/ordergood/:id -> 'good', /order/serviceorder/:id -> 'service')
const orderType = computed(() => {
if (props.type) return props.type
const path = route.path
if (path.startsWith('/order/serviceorder') || path.startsWith('/order/orderserviceafter')) return 'service'
if (path.startsWith('/order/groupsupplierorder') || path.startsWith('/order/group-supplier')) return 'groupsupplier'
if (path.startsWith('/order/groupserviceorder') || path.startsWith('/order/group-service')) return 'groupservice'
return 'good'
})
// 状态映射 (老 vue 1-7 数字, 我也沿用)
const STATUS_MAP: Record<number, { label: string; type: string; desc: string }> = {
1: { label: '待支付', type: 'warning', desc: '订单待付款, 请尽快支付' },
2: { label: '待发货', type: 'primary', desc: '订单已支付, 等待供应商发货' },
3: { label: '待收货', type: 'primary', desc: '商品已发货, 等待签收' },
4: { label: '待付尾款', type: 'warning', desc: '请支付订单尾款' },
5: { label: '待评价', type: 'success', desc: '订单已完成, 欢迎评价' },
6: { label: '已完成', type: 'success', desc: '订单已完成' },
7: { label: '已取消', type: 'info', desc: '订单已取消' },
8: { label: '采购单待审核', type: 'warning', desc: '采购单待平台审核' },
9: { label: '付款单待审核', type: 'warning', desc: '付款单待平台审核' }
}
const PAY_TYPE_MAP: Record<number, string> = {
1: '微信', 2: '支付宝', 3: '银联', 4: '对公转账', 5: '余额', 6: '其他'
}
const DELIVERY_STATUS_MAP: Record<number, { label: string; type: string }> = {
0: { label: '待发货', type: 'warning' },
1: { label: '已发货', type: 'primary' },
2: { label: '已签收', type: 'success' },
3: { label: '已退回', type: 'danger' }
}
const INVOICE_STATUS_MAP: Record<number, { label: string; type: string }> = {
0: { label: '不开发票', type: 'info' },
1: { label: '未开票', type: 'warning' },
2: { label: '已开票', type: 'success' }
}
const detail = ref<OrderDetail | null>(null)
const loading = ref(true)
const activeTab = ref<'order' | 'goods' | 'delivery' | 'pay'>('order')
// FALLBACK 模拟数据 - 沿用老 vue 字段 (用于端点不可用时)
const FALLBACK: OrderDetail = {
id: 'ORD-20260705001',
orderCode: 'ORD-20260705001',
status: 3,
payStatus: 1,
goodType: 1,
pickupType: 1,
enterpriseName: '深圳科技股份有限公司',
supplierName: '深圳科技股份有限公司',
contractCode: 'HT-2026-07001',
sum: 12500,
couponMoney: 0,
freight: 50,
payMoney: 12550,
createDate: '2026-07-05 10:24:33',
payDate: '2026-07-05 10:35:12',
remark: '加急订单, 优先发货',
address: {
name: '张三',
mobile: '138****0001',
provinceName: '广东省',
cityName: '深圳市',
areaName: '南山区',
address: '科技园南区 1 号楼',
fullAddress: '广东省 深圳市 南山区 科技园南区 1 号楼'
},
orderGoodList: [
{
id: 'G001',
title: '工业传感器 X100 高精度',
icon: null,
propertyNames: '型号:X100 / 精度:0.01mm / 材质:不锈钢',
unitPrice: 2500,
totalNum: 5,
shippedNum: 5,
orderMoney: 12500,
status: 3,
payStatus: 1,
payType: 1,
createDate: '2026-07-05 10:24:33',
payDate: '2026-07-05 10:35:12',
pickupType: 1,
fileUrl: null,
remark: '紧急订单, 优先发货',
raw: {}
}
],
deliveryList: [
{
id: 'D001',
deliveryCompany: '顺丰快递',
deliveryNo: 'SF1234567890',
deliveryStatus: 1,
deliveryTime: '2026-07-05 14:00:00',
receivedTime: null,
deliveryList: [
{ id: 'D001-1', goodName: '工业传感器 X100', num: 5, raw: {} }
],
raw: {}
}
],
acceptanceList: [],
payList: [
{ id: 'P001', payType: 1, payMoney: 12550, payTime: '2026-07-05 10:35:12', payStatus: 1, invoiceStatus: 1, remark: '微信支付', raw: {} }
],
groupNumber: 0,
groupNickName: '',
groupMobile: '',
groupStatus: 0,
fileUrl: null,
invoice: null,
acceptanceForm: null,
promoterName: '李推广员',
promoterMobile: '138****0099',
rebateMoney: 1250,
raw: {}
}
async function load() {
loading.value = true
try {
const resp: any = await httpGet('/sys/openapi/order/info', { orderId: orderId.value })
if (resp && resp.orderId) {
detail.value = { ...FALLBACK, ...resp, id: resp.id || orderId.value, orderCode: resp.orderId }
} else {
detail.value = { ...FALLBACK, id: orderId.value, orderCode: orderId.value }
}
} catch (e) {
detail.value = { ...FALLBACK, id: orderId.value, orderCode: orderId.value }
} finally {
loading.value = false
}
}
// 状态文案
const currentStatus = computed(() => detail.value ? STATUS_MAP[detail.value.status] : null)
const totalProductAmount = computed(() => detail.value ? `¥ ${(detail.value.sum || 0).toLocaleString()}` : '¥ 0')
const totalShippingFee = computed(() => detail.value ? `¥ ${(detail.value.freight || 0).toLocaleString()}` : '¥ 0')
// 是否可操作 (按状态)
const canCancel = computed(() => detail.value && [1, 2].includes(detail.value.status))
const canPay = computed(() => detail.value?.status === 1)
const canShip = computed(() => detail.value?.status === 2)
const canConfirmReceipt = computed(() => detail.value?.status === 3)
const canRefund = computed(() => detail.value && [2, 3, 4].includes(detail.value.status))
const canComment = computed(() => detail.value?.status === 5)
// 付款合计
const totalPay = computed(() => detail.value?.payList?.reduce((s, p) => s + (p.payMoney || 0), 0) || 0)
const invoicedMoney = computed(() => detail.value?.payList?.filter(p => p.invoiceStatus === 2).reduce((s, p) => s + (p.payMoney || 0), 0) || 0)
const uninvoicedMoney = computed(() => totalPay.value - invoicedMoney.value)
function fmtDate(s: any): string {
if (!s) return '--'
return String(s).substring(0, 19).replace('T', ' ')
}
function fmtMoney(n: any): string {
const num = Number(n || 0)
return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
function payTypeName(t: number): string { return PAY_TYPE_MAP[t] || `类型${t}` }
function statusInfo(s: number) { return STATUS_MAP[s] || { label: `状态${s}`, type: 'info', desc: '' } }
function deliveryInfo(s: number) { return DELIVERY_STATUS_MAP[s] || { label: `状态${s}`, type: 'info' } }
function invoiceInfo(s: number) { return INVOICE_STATUS_MAP[s] || { label: `状态${s}`, type: 'info' } }
function onBack() { router.push('/order') }
function onCancelOrder() {
ElMessageBox.confirm('确认取消此订单?', '提示', { type: 'warning' }).then(() => {
ElMessage.info('请前往前台取消订单')
}).catch(() => {})
}
function onPay() { ElMessage.info('请前往前台完成支付') }
function onShip() { ElMessage.info('请前往供应商端发货') }
function onConfirmReceipt() { ElMessage.info('请前往前台确认收货') }
function onApplyRefund() { ElMessage.info('请前往售后管理处理') }
function onComment() { ElMessage.info('请前往前台评价') }
function onExport() { ElMessage.info('导出功能开发中') }
function onPrint() { window.print() }
function onDownloadFile(url: string) { if (url) ElMessage.info(`下载: ${url}`) }
function onUploadInvoice() { ElMessage.info('请前往前台上传发票') }
onMounted(load)
watch(orderId, load)
</script>
<template>
<div class="rr-order-detail">
<PageHeader title="订单详情" :sub="detail?.orderCode || ''">
<el-button text @click="onBack">← 返回订单列表</el-button>
</PageHeader>
<div v-if="loading" class="rr-loading">
<el-skeleton :rows="6" animated />
</div>
<div v-else-if="!detail" class="rr-empty">
<el-empty description="订单不存在" />
</div>
<div v-else>
<!-- 状态卡 + 操作 -->
<div class="rr-card rr-status-card">
<div class="rr-status-content">
<div class="rr-status-info">
<el-tag :type="(currentStatus?.type as any)" size="large" effect="light">
{{ currentStatus?.label }}
</el-tag>
<p class="rr-status-desc">{{ currentStatus?.desc }}</p>
</div>
<div class="rr-status-price">
<div class="rr-price">
<span class="rr-currency">¥</span>
<span class="rr-price-num">{{ fmtMoney(detail.payMoney) }}</span>
</div>
<p class="rr-price-label">订单总金额</p>
</div>
</div>
<div class="rr-status-actions">
<el-button v-if="canPay" type="primary" @click="onPay">立即支付</el-button>
<el-button v-if="canCancel" @click="onCancelOrder">取消订单</el-button>
<el-button v-if="canShip" type="primary" @click="onShip">立即发货</el-button>
<el-button v-if="canConfirmReceipt" type="primary" @click="onConfirmReceipt">确认收货</el-button>
<el-button v-if="canRefund" @click="onApplyRefund">申请退款</el-button>
<el-button v-if="canComment" type="primary" @click="onComment">评价</el-button>
<el-button @click="onPrint">打印</el-button>
<el-button @click="onExport">导出</el-button>
</div>
</div>
<!-- 订单基础信息 -->
<div class="rr-card">
<div class="rr-card-header">
<h3 class="rr-card-title">订单信息</h3>
</div>
<el-descriptions :column="2" border class="rr-desc">
<el-descriptions-item label="订单编号">{{ detail.orderCode }}</el-descriptions-item>
<el-descriptions-item label="合同编号">{{ detail.contractCode || '--' }}</el-descriptions-item>
<el-descriptions-item label="下单时间">{{ fmtDate(detail.createDate) }}</el-descriptions-item>
<el-descriptions-item label="付款时间">{{ fmtDate(detail.payDate) }}</el-descriptions-item>
<el-descriptions-item label="供应商">{{ detail.supplierName || '--' }}</el-descriptions-item>
<el-descriptions-item label="采购方">{{ detail.enterpriseName }}</el-descriptions-item>
<el-descriptions-item label="商品总额">¥ {{ fmtMoney(detail.sum) }}</el-descriptions-item>
<el-descriptions-item label="运费金额">¥ {{ fmtMoney(detail.freight) }}</el-descriptions-item>
<el-descriptions-item label="优惠券">¥ {{ fmtMoney(detail.couponMoney) }}</el-descriptions-item>
<el-descriptions-item label="实付金额">¥ {{ fmtMoney(detail.payMoney) }}</el-descriptions-item>
<el-descriptions-item label="配送方式">
<el-tag v-if="detail.pickupType === 1" type="primary" size="small">快递配送</el-tag>
<el-tag v-else-if="detail.pickupType === 2" type="success" size="small">自提</el-tag>
<span v-else>--</span>
</el-descriptions-item>
<el-descriptions-item label="订单备注" :span="2">{{ detail.remark || '--' }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 收货信息 -->
<div class="rr-card" v-if="detail.address">
<div class="rr-card-header"><h3 class="rr-card-title">收货信息</h3></div>
<el-descriptions :column="2" border class="rr-desc">
<el-descriptions-item label="收货人">{{ detail.address.name }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detail.address.mobile }}</el-descriptions-item>
<el-descriptions-item label="收货地址" :span="2">{{ detail.address.fullAddress }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 推广信息 (沿用老 vue promoterName/promoterMobile/rebateMoney) -->
<div class="rr-card" v-if="detail.promoterName">
<div class="rr-card-header"><h3 class="rr-card-title">推广信息</h3></div>
<el-descriptions :column="3" border class="rr-desc">
<el-descriptions-item label="推广员">{{ detail.promoterName }}</el-descriptions-item>
<el-descriptions-item label="联系方式">{{ detail.promoterMobile }}</el-descriptions-item>
<el-descriptions-item label="返佣金额">¥ {{ fmtMoney(detail.rebateMoney) }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- Tab 切换: 订单明细 / 物流 / 付款 / 发票 -->
<div class="rr-card">
<el-tabs v-model="activeTab" class="rr-tabs-card">
<!-- Tab 1: 订单明细 -->
<el-tab-pane label="商品明细" name="goods">
<el-table :data="detail.orderGoodList" border style="width: 100%">
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column label="商品" min-width="280">
<template #default="{ row }">
<div class="rr-good-cell">
<div v-if="row.icon" class="rr-good-icon">
<el-image :src="row.icon" :preview-src-list="[row.icon]" fit="cover" style="width: 40px; height: 40px; border-radius: 4px" />
</div>
<div v-else class="rr-good-icon rr-good-icon-placeholder">{{ (row.title || '?').charAt(0) }}</div>
<div>
<div class="rr-good-title">{{ row.title }}</div>
<div v-if="row.propertyNames" class="rr-good-props">{{ row.propertyNames }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="单价" width="100" align="right">
<template #default="{ row }">¥ {{ fmtMoney(row.unitPrice) }}</template>
</el-table-column>
<el-table-column label="数量" prop="totalNum" width="80" align="center" />
<el-table-column label="已发" prop="shippedNum" width="80" align="center" />
<el-table-column label="金额" width="120" align="right">
<template #default="{ row }">¥ {{ fmtMoney(row.orderMoney) }}</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="(statusInfo(row.status).type as any)" size="small" effect="light">
{{ statusInfo(row.status).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<!-- Tab 2: 物流信息 -->
<el-tab-pane :label="`物流信息 (${detail.deliveryList.length})`" name="delivery">
<el-table v-if="detail.deliveryList.length > 0" :data="detail.deliveryList" border style="width: 100%">
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column label="快递公司" prop="deliveryCompany" width="140" />
<el-table-column label="快递单号" prop="deliveryNo" width="200" />
<el-table-column label="发货时间" width="170">
<template #default="{ row }">{{ fmtDate(row.deliveryTime) }}</template>
</el-table-column>
<el-table-column label="签收时间" width="170">
<template #default="{ row }">{{ fmtDate(row.receivedTime) }}</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="(deliveryInfo(row.deliveryStatus).type as any)" size="small" effect="light">
{{ deliveryInfo(row.deliveryStatus).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template #default="{ row }">
<el-button v-if="row.deliveryStatus >= 1" text type="primary" size="small" @click="onDownloadFile(row.deliveryNo)">查看物流</el-button>
</template>
</el-table-column>
</el-table>
<div v-else class="rr-empty-tip">暂无物流信息</div>
<!-- 签收回单 (沿用老 vue acceptanceList) -->
<div v-if="detail.acceptanceList.length > 0" class="rr-acceptance">
<h4 class="rr-section-title">签收回单</h4>
<el-table :data="detail.acceptanceList" border style="width: 100%">
<el-table-column label="签收数量" prop="acceptanceNum" width="120" align="center" />
<el-table-column label="签收时间" width="170">
<template #default="{ row }">{{ fmtDate(row.acceptanceTime) }}</template>
</el-table-column>
<el-table-column label="回单" width="100" align="center">
<template #default="{ row }">
<el-image v-if="row.acceptanceFile" :src="row.acceptanceFile" :preview-src-list="[row.acceptanceFile]" fit="contain" style="width: 60px; height: 60px" />
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="200" show-overflow-tooltip />
</el-table>
</div>
</el-tab-pane>
<!-- Tab 3: 付款信息 -->
<el-tab-pane :label="`付款信息 (${detail.payList.length})`" name="pay">
<div v-if="detail.payList.length > 0" class="rr-pay-summary">
<span>已付 ¥ {{ fmtMoney(invoicedMoney) }} 已开票, 剩余 ¥ {{ fmtMoney(uninvoicedMoney) }} 未开票</span>
</div>
<el-table v-if="detail.payList.length > 0" :data="detail.payList" border style="width: 100%">
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column label="支付方式" width="120" align="center">
<template #default="{ row }">
<el-tag size="small">{{ payTypeName(row.payType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="金额" width="140" align="right">
<template #default="{ row }">¥ {{ fmtMoney(row.payMoney) }}</template>
</el-table-column>
<el-table-column label="支付时间" width="170">
<template #default="{ row }">{{ fmtDate(row.payTime) }}</template>
</el-table-column>
<el-table-column label="支付状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.payStatus === 1 ? 'success' : 'warning'" size="small">
{{ row.payStatus === 1 ? '已支付' : '待支付' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="开票状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="(invoiceInfo(row.invoiceStatus).type as any)" size="small" effect="light">
{{ invoiceInfo(row.invoiceStatus).label }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center">
<template #default="{ row }">
<el-button v-if="row.invoiceStatus === 1" text type="primary" size="small" @click="onUploadInvoice">上传发票</el-button>
<el-button v-else-if="row.invoiceStatus === 2" text type="primary" size="small" @click="onDownloadFile(row.id)">查看发票</el-button>
</template>
</el-table-column>
</el-table>
<div v-else class="rr-empty-tip">暂无付款信息</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.rr-order-detail { max-width: 1400px; }
.rr-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 16px; }
.rr-card-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
.rr-card-title { font-size: 15px; font-weight: 600; color: var(--ink); margin: 0; }
.rr-loading { padding: 40px 20px; }
.rr-empty { padding: 60px 20px; text-align: center; }
.rr-status-card { padding: 24px 28px; }
.rr-status-content { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.rr-status-info { display: flex; flex-direction: column; gap: 8px; }
.rr-status-desc { color: var(--ink-3); font-size: 13px; margin: 0; }
.rr-status-price { text-align: right; }
.rr-price { display: flex; align-items: baseline; justify-content: flex-end; gap: 4px; }
.rr-currency { font-size: 16px; color: var(--ink-2); }
.rr-price-num { font-size: 32px; font-weight: 700; color: var(--ink); }
.rr-price-label { color: var(--ink-3); font-size: 12.5px; margin: 4px 0 0 0; }
.rr-status-actions { display: flex; gap: 8px; flex-wrap: wrap; padding-top: 16px; border-top: 1px solid var(--border); }
.rr-desc { margin: 0; }
.rr-desc :deep(.el-descriptions__label) { color: var(--ink-3); font-weight: 500; background: var(--bg-soft); }
.rr-desc :deep(.el-descriptions__content) { color: var(--ink); }
.rr-good-cell { display: flex; gap: 12px; align-items: center; padding: 6px 0; }
.rr-good-icon-placeholder { width: 40px; height: 40px; border-radius: 4px; background: var(--bg-soft); display: flex; align-items: center; justify-content: center; color: var(--ink-3); font-weight: 600; }
.rr-good-title { font-size: 13.5px; color: var(--ink); }
.rr-good-props { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.rr-empty-tip { text-align: center; padding: 40px 20px; color: var(--ink-3); }
.rr-acceptance { margin-top: 24px; }
.rr-section-title { font-size: 14px; font-weight: 600; color: var(--ink); margin: 0 0 12px 0; }
.rr-pay-summary { padding: 12px 16px; background: var(--bg-soft); border-radius: var(--radius); color: var(--ink-2); font-size: 13px; margin-bottom: 12px; }
.rr-tabs-card :deep(.el-tabs__header) { margin: 0; padding: 0 20px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.rr-tabs-card :deep(.el-tabs__nav-wrap::after) { display: none; }
.rr-tabs-card :deep(.el-tabs__item) { font-size: 14px; font-weight: 500; }
.rr-tabs-card :deep(.el-tabs__content) { padding: 20px; }
.rr-tabs-card :deep(.el-tab-pane) { padding: 0; }
</style>