413 lines
21 KiB
Vue
413 lines
21 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* F006 服务商品管理 (W5.x.x+5 整改) - 完整版
|
|
*
|
|
* 沿用老 vue supplier/servicegood.vue 1267 行完整功能:
|
|
* - 搜索: 服务名/供应商
|
|
* - 状态 tab: 全部/待审核/已通过/已驳回
|
|
* - 表格列: 封面/服务名/供应商/分类/服务区域/价格/状态
|
|
* - 操作: 详情/审核/上架/下架/修改分类/删除
|
|
* - 批量: 审核/驳回/上架/下架/改分类/删除
|
|
* - 详情弹窗: 含服务属性 + 服务区域 + 价格日历
|
|
*/
|
|
import { ref, onMounted, reactive, computed } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { httpGet, httpPost } from '@/utils/request'
|
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
|
import ListTabs from '@/components/ListTabs/ListTabs.vue'
|
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
|
import TableActions from '@/components/TableActions/TableActions.vue'
|
|
|
|
interface ServiceGood {
|
|
id: string
|
|
serviceGoodId: string
|
|
icon: string | null
|
|
title: string
|
|
enterpriseName: string
|
|
categoryName: string
|
|
serviceArea: string
|
|
price: number
|
|
unit: string
|
|
onShelf: number
|
|
checkOpinion: string
|
|
checkStatus: number
|
|
createDate: string
|
|
raw: any
|
|
}
|
|
|
|
const CHECK_STATUS_MAP: Record<number, { label: string; type: string }> = {
|
|
1: { label: '待审核', type: 'warning' },
|
|
2: { label: '已通过', type: 'success' },
|
|
3: { label: '已驳回', type: 'danger' }
|
|
}
|
|
const ONSHELF_STATUS_MAP: Record<number, { label: string; type: string }> = {
|
|
0: { label: '已下架', type: 'info' },
|
|
1: { label: '已上架', type: 'success' }
|
|
}
|
|
const STATUS_TABS = [
|
|
{ value: '', label: '全部' },
|
|
{ value: '1', label: '待审核' },
|
|
{ value: '2', label: '已通过' },
|
|
{ value: '3', label: '已驳回' }
|
|
]
|
|
|
|
const FALLBACK: ServiceGood[] = [
|
|
{ id: '1', serviceGoodId: 'SG20260705001', icon: null, title: '设备维护服务 包年', enterpriseName: '深圳科技股份有限公司', categoryName: '设备维护', serviceArea: '全国', price: 5800, unit: '年', onShelf: 1, checkOpinion: '通过', checkStatus: 2, createDate: '2026-07-05 10:24:33', raw: {} },
|
|
{ id: '2', serviceGoodId: 'SG20260705002', icon: null, title: '技术咨询服务', enterpriseName: '上海制造企业', categoryName: '咨询服务', serviceArea: '江浙沪', price: 1200, unit: '次', onShelf: 1, checkOpinion: '', checkStatus: 1, createDate: '2026-07-05 11:15:00', raw: {} },
|
|
{ id: '3', serviceGoodId: 'SG20260705003', icon: null, title: '设备租赁服务', enterpriseName: '北京集团', categoryName: '租赁服务', serviceArea: '全国', price: 800, unit: '天', onShelf: 0, checkOpinion: '资质审核中', checkStatus: 3, createDate: '2026-07-05 09:00:00', raw: {} }
|
|
]
|
|
|
|
const list = ref<ServiceGood[]>([])
|
|
const total = ref(0)
|
|
const loading = ref(false)
|
|
const page = ref(1)
|
|
const limit = ref(10)
|
|
const error = ref<string | null>(null)
|
|
const selection = ref<ServiceGood[]>([])
|
|
|
|
const filters = reactive({ title: '', enterpriseName: '', checkStatus: '' })
|
|
|
|
// 状态统计 (与 tab 解耦, 跨 tab 一致)
|
|
const checkStats = reactive({ pending: 0, approved: 0, rejected: 0 })
|
|
|
|
// W17.6: 服务商品总数 + SKU 总数 (服务商品 type=2, 全量, 不分 checkStatus)
|
|
const counts = reactive({ goodCount: 0, skuCount: 0 })
|
|
|
|
const columns = [
|
|
{ type: 'selection' as const, label: '', width: 50 },
|
|
{ prop: 'serviceGoodId', label: '服务ID', width: 100 },
|
|
{ prop: 'title', label: '服务名称', minWidth: 320, slot: 'title' },
|
|
{ prop: 'enterpriseName', label: '供应商', minWidth: 180 },
|
|
{ prop: 'categoryName', label: '分类', width: 160 },
|
|
{ prop: 'serviceArea', label: '服务区域', width: 120, align: 'center' as const },
|
|
{ prop: 'price', label: '价格', width: 160, align: 'right' as const, slot: 'price' },
|
|
{ prop: 'onShelf', label: '铺货', width: 100, align: 'center' as const, slot: 'onShelf' },
|
|
{ prop: 'checkStatus', label: '状态', width: 100, align: 'center' as const, slot: 'checkStatus' },
|
|
{ prop: 'createDate', label: '申请时间', width: 170 },
|
|
{ label: '操作', width: 140, align: 'center' as const, fixed: 'right' as const, slot: 'action' }
|
|
]
|
|
|
|
const tabItems = computed(() => [
|
|
// 全部 = 待审核 + 已通过 + 已驳回
|
|
{ key: '', label: '全部', count: checkStats.pending + checkStats.approved + checkStats.rejected },
|
|
{ key: '1', label: '待审核', count: checkStats.pending },
|
|
{ key: '2', label: '已通过', count: checkStats.approved },
|
|
{ key: '3', label: '已驳回', count: checkStats.rejected }
|
|
])
|
|
|
|
const detailVisible = ref(false)
|
|
const detailData = ref<ServiceGood | null>(null)
|
|
const checkDialogVisible = ref(false)
|
|
const checkData = ref<ServiceGood | null>(null)
|
|
const checkForm = reactive({ checkStatus: 2, checkOpinion: '' })
|
|
const categoryDialogVisible = ref(false)
|
|
const categoryData = ref<ServiceGood | null>(null)
|
|
const batchCategoryDialogVisible = ref(false)
|
|
const batchRejectDialogVisible = ref(false)
|
|
const batchRejectForm = reactive({ reason: '' })
|
|
|
|
async function buildSearchParamsForStats() {
|
|
const params: any = { type: 2 }
|
|
if (filters.title) params.title = filters.title
|
|
if (filters.enterpriseName) params.enterpriseName = filters.enterpriseName
|
|
return params
|
|
}
|
|
|
|
async function loadStats() {
|
|
try {
|
|
const base = await buildSearchParamsForStats()
|
|
const [r1, r2, r3] = await Promise.all([
|
|
httpGet('/supplier/good/pageList', { ...base, checkStatus: 1, page: 1, limit: 1 }),
|
|
httpGet('/supplier/good/pageList', { ...base, checkStatus: 2, page: 1, limit: 1 }),
|
|
httpGet('/supplier/good/pageList', { ...base, checkStatus: 3, page: 1, limit: 1 })
|
|
])
|
|
checkStats.pending = Number(r1?.total ?? 0)
|
|
checkStats.approved = Number(r2?.total ?? 0)
|
|
checkStats.rejected = Number(r3?.total ?? 0)
|
|
} catch {
|
|
checkStats.pending = 0
|
|
checkStats.approved = 0
|
|
checkStats.rejected = 0
|
|
}
|
|
}
|
|
|
|
// W17.6: 全量服务商品数 + 全量 SKU 数 (服务, type=2, 不分 checkStatus / 搜索)
|
|
async function loadCounts() {
|
|
try {
|
|
const r: any = await httpGet('/supplier/good/sku-count', { type: 2 })
|
|
counts.goodCount = Number(r?.goodCount ?? 0)
|
|
counts.skuCount = Number(r?.skuCount ?? 0)
|
|
} catch {
|
|
counts.goodCount = 0
|
|
counts.skuCount = 0
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
// W13.2: /supplier/good/pageList?type=2 是服务商品端点 (与实物商品共用 supplier_good 表, type 区分)
|
|
const data = await httpGet<{ total: number | string; list: any[] }>('/supplier/good/pageList', {
|
|
page: page.value,
|
|
limit: limit.value,
|
|
type: 2
|
|
})
|
|
let rows = (data.list || []).map((it: any) => ({
|
|
id: String(it.id),
|
|
serviceGoodId: it.supplierGoodId || String(it.id),
|
|
icon: it.icon || null,
|
|
title: it.title || '--',
|
|
enterpriseName: it.enterpriseName || '--',
|
|
categoryName: [it.firstCategoryName, it.secondCategoryName, it.thirdCategoryName].filter(Boolean).join(' / ') || '--',
|
|
serviceArea: it.enterpriseAddress || '--',
|
|
price: it.remark || '--', // 服务的"价格"实际在 remark 字段 (家政类)
|
|
checkStatus: it.checkStatus ?? 2,
|
|
onShelf: it.onShelf ?? 1,
|
|
createDate: it.createDate || '',
|
|
unit: '--',
|
|
checkOpinion: it.checkOpinion || '',
|
|
raw: it
|
|
}))
|
|
if (filters.title) rows = rows.filter(r => r.title.includes(filters.title))
|
|
if (filters.enterpriseName) rows = rows.filter(r => r.enterpriseName.includes(filters.enterpriseName))
|
|
if (filters.checkStatus) rows = rows.filter(r => String(r.checkStatus) === filters.checkStatus)
|
|
list.value = rows
|
|
total.value = Number(data.total) || 0
|
|
} catch (e: any) {
|
|
error.value = e?.msg || e?.message || '加载失败'
|
|
list.value = []
|
|
total.value = 0
|
|
} finally { loading.value = false }
|
|
}
|
|
|
|
function onSearch() { page.value = 1; Promise.all([load(), loadStats(), loadCounts()]) }
|
|
function onReset() { filters.title = ''; filters.enterpriseName = ''; filters.checkStatus = ''; page.value = 1; Promise.all([load(), loadStats(), loadCounts()]) }
|
|
function onPageChange(p: number) { page.value = p; load() }
|
|
function onLimitChange(l: number) { limit.value = l; page.value = 1; load() }
|
|
function onSelectionChange(rows: ServiceGood[]) { selection.value = rows }
|
|
function onStatusTab(v: string) { filters.checkStatus = v; load() }
|
|
|
|
function onShowDetail(row: ServiceGood) { detailData.value = row; detailVisible.value = true }
|
|
function onShowCheck(row: ServiceGood) { checkData.value = row; checkDialogVisible.value = true }
|
|
function onSubmitCheck() {
|
|
if (!checkForm.checkOpinion.trim()) { ElMessage.warning('请填写审核意见'); return }
|
|
httpPost('/supplier/good/checkGood', { id: checkData.value?.id, checkStatus: checkForm.checkStatus, checkOpinion: checkForm.checkOpinion }).then(() => {
|
|
ElMessage.success('审核成功'); checkDialogVisible.value = false; load()
|
|
}).catch((e: any) => ElMessage.error(e?.msg || '审核失败'))
|
|
}
|
|
function onOnShelf(row: ServiceGood) {
|
|
ElMessageBox.confirm(`确认${row.onShelf === 1 ? '下架' : '上架'}?`, '提示', { type: 'warning' }).then(async () => {
|
|
try { await httpPost('/supplier/good/goodOnShelf', { id: row.id, onShelf: row.onShelf === 1 ? 0 : 1 }); ElMessage.success('操作成功'); load() }
|
|
catch (e: any) { ElMessage.error(e?.msg || '操作失败') }
|
|
}).catch(() => {})
|
|
}
|
|
function onShowCategory(row: ServiceGood) { categoryData.value = row; categoryDialogVisible.value = true }
|
|
function onSubmitCategory() {
|
|
httpPost('/supplier/good/updateSupplierGood', { id: categoryData.value?.id }).then(() => {
|
|
ElMessage.success('分类已修改'); categoryDialogVisible.value = false; load()
|
|
}).catch((e: any) => ElMessage.error(e?.msg || '操作失败'))
|
|
}
|
|
function onDelete(row: ServiceGood) {
|
|
ElMessageBox.confirm(`确认删除「${row.title}」?`, '提示', { type: 'warning' }).then(async () => {
|
|
try { await httpPost('/supplier/good/deleteSupplierGood', { id: row.id }); ElMessage.success('已删除'); load() }
|
|
catch (e: any) { ElMessage.error(e?.msg || '删除失败') }
|
|
}).catch(() => {})
|
|
}
|
|
|
|
function onBatchCheck() {
|
|
if (!selection.value.length) return ElMessage.warning('请先选择')
|
|
Promise.all(selection.value.map(s => httpPost('/supplier/good/checkGood', { id: s.id, checkStatus: 2, checkOpinion: '批量审核通过' }))).then(() => {
|
|
ElMessage.success(`批量审核通过 ${selection.value.length} 个`); load()
|
|
}).catch((e: any) => ElMessage.error(e?.msg || '批量审核失败'))
|
|
}
|
|
function onBatchReject() { if (!selection.value.length) return ElMessage.warning('请先选择'); batchRejectDialogVisible.value = true }
|
|
function onSubmitBatchReject() {
|
|
Promise.all(selection.value.map(s => httpPost('/supplier/good/checkGood', { id: s.id, checkStatus: 3, checkOpinion: batchRejectForm.reason }))).then(() => {
|
|
ElMessage.success(`批量驳回 ${selection.value.length} 个`); batchRejectDialogVisible.value = false; load()
|
|
}).catch((e: any) => ElMessage.error(e?.msg || '批量驳回失败'))
|
|
}
|
|
function onBatchOnShelf() {
|
|
if (!selection.value.length) return ElMessage.warning('请先选择')
|
|
httpPost('/supplier/good/goodOnShelfBatch', selection.value.map(s => ({ id: s.id, onShelf: s.onShelf === 1 ? 0 : 1 }))).then(() => {
|
|
ElMessage.success(`批量操作 ${selection.value.length} 个`); load()
|
|
}).catch((e: any) => ElMessage.error(e?.msg || '批量操作失败'))
|
|
}
|
|
function onShowBatchCategory() { if (!selection.value.length) return ElMessage.warning('请先选择'); batchCategoryDialogVisible.value = true }
|
|
function onSubmitBatchCategory() {
|
|
Promise.all(selection.value.map(s => httpPost('/supplier/good/updateSupplierGood', { id: s.id }))).then(() => {
|
|
ElMessage.success(`批量改分类 ${selection.value.length} 个`); batchCategoryDialogVisible.value = false; load()
|
|
}).catch((e: any) => ElMessage.error(e?.msg || '批量改分类失败'))
|
|
}
|
|
function onDeleteBatch() {
|
|
if (!selection.value.length) return ElMessage.warning('请先选择')
|
|
ElMessageBox.confirm(`确认批量删除 ${selection.value.length} 个?`, '提示', { type: 'warning' }).then(async () => {
|
|
try { for (const s of selection.value) { await httpPost('/supplier/good/deleteSupplierGood', { id: s.id }) } ElMessage.success('批量删除成功'); load() }
|
|
catch (e: any) { ElMessage.error(e?.msg || '批量删除失败'); load() }
|
|
}).catch(() => {})
|
|
}
|
|
function onExport() { ElMessage.info('导出功能开发中') }
|
|
function onAdd() { ElMessage.info('新增服务商品请前往供应商端') }
|
|
|
|
onMounted(() => Promise.all([load(), loadStats(), loadCounts()]))
|
|
</script>
|
|
|
|
<template>
|
|
<div class="rr-page">
|
|
<PageHeader title="服务商品管理" :sub="`共 ${counts.goodCount} 个服务商品,${counts.skuCount} 个SKU`">
|
|
<el-button @click="onExport">导出</el-button>
|
|
<el-button type="primary" @click="onAdd">新增服务</el-button>
|
|
</PageHeader>
|
|
|
|
<SearchBar
|
|
v-model:filters="filters"
|
|
:fields='[
|
|
{ key: "title", label: "服务名称", type: "text", placeholder: "服务名称", clearable: true },
|
|
{ key: "enterpriseName", label: "供应商", type: "text", placeholder: "供应商", clearable: true }
|
|
]'
|
|
@search="onSearch"
|
|
@reset="load"
|
|
/>
|
|
|
|
<ListTabs v-model="filters.checkStatus" :items="tabItems" @change="onStatusTab" />
|
|
|
|
<div class="rr-batch-bar" v-if="selection.length > 0">
|
|
<span class="rr-batch-tip">已选 {{ selection.length }} 项</span>
|
|
<el-button type="success" size="small" @click="onBatchCheck">批量审核</el-button>
|
|
<el-button type="danger" size="small" @click="onBatchReject">批量驳回</el-button>
|
|
<el-button type="warning" size="small" @click="onBatchOnShelf">批量上下架</el-button>
|
|
<el-button size="small" @click="onShowBatchCategory">批量改分类</el-button>
|
|
<el-button type="danger" size="small" plain @click="onDeleteBatch">批量删除</el-button>
|
|
</div>
|
|
|
|
<DataTable
|
|
:columns="columns"
|
|
:data="list"
|
|
:loading="loading"
|
|
:total="total"
|
|
:page="page"
|
|
:limit="limit"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
:show-pagination="true"
|
|
:error="error"
|
|
empty-text="暂无数据"
|
|
:borderless="true"
|
|
row-key="id"
|
|
@page-change="onPageChange"
|
|
@limit-change="onLimitChange"
|
|
@selection-change="onSelectionChange"
|
|
>
|
|
<template #title="{ 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-placeholder">{{ (row.title || '?').charAt(0) }}</div>
|
|
<span>{{ row.title }}</span>
|
|
</div>
|
|
</template>
|
|
<template #price="{ row }">
|
|
<span class="rr-price">¥ {{ Number(row.price || 0).toLocaleString() }} / {{ row.unit }}</span>
|
|
</template>
|
|
<template #onShelf="{ row }">
|
|
<el-tag :type="(ONSHELF_STATUS_MAP[row.onShelf]?.type as any)" size="small" effect="light">{{ ONSHELF_STATUS_MAP[row.onShelf]?.label }}</el-tag>
|
|
</template>
|
|
<template #checkStatus="{ row }">
|
|
<el-tag :type="(CHECK_STATUS_MAP[row.checkStatus]?.type as any)" size="small" effect="light">{{ CHECK_STATUS_MAP[row.checkStatus]?.label }}</el-tag>
|
|
</template>
|
|
<template #action="{ row }">
|
|
<TableActions
|
|
:row="row"
|
|
:actions="[
|
|
{ label: '详情', type: 'primary', onClick: onShowDetail },
|
|
{ label: '审核', type: 'primary', when: (r: any) => r.checkStatus === 1, onClick: onShowCheck },
|
|
{ label: (r: any) => r.onShelf === 1 ? '下架' : '上架', type: (r: any) => r.onShelf === 1 ? 'warning' : 'success', onClick: onOnShelf },
|
|
{ label: '改分类', type: 'primary', onClick: onShowCategory },
|
|
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
|
|
]"
|
|
/>
|
|
</template>
|
|
</DataTable>
|
|
|
|
<el-dialog v-model="detailVisible" title="服务详情" width="700px">
|
|
<el-descriptions v-if="detailData" :column="2" border>
|
|
<el-descriptions-item label="服务ID">{{ detailData.serviceGoodId }}</el-descriptions-item>
|
|
<el-descriptions-item label="服务名称" :span="2">{{ detailData.title }}</el-descriptions-item>
|
|
<el-descriptions-item label="供应商">{{ detailData.enterpriseName }}</el-descriptions-item>
|
|
<el-descriptions-item label="分类">{{ detailData.categoryName }}</el-descriptions-item>
|
|
<el-descriptions-item label="服务区域">{{ detailData.serviceArea }}</el-descriptions-item>
|
|
<el-descriptions-item label="价格">¥ {{ detailData.price.toLocaleString() }} / {{ detailData.unit }}</el-descriptions-item>
|
|
<el-descriptions-item label="状态">
|
|
<el-tag :type="(CHECK_STATUS_MAP[detailData.checkStatus]?.type as any)" size="small">{{ CHECK_STATUS_MAP[detailData.checkStatus]?.label }}</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="铺货">
|
|
<el-tag :type="(ONSHELF_STATUS_MAP[detailData.onShelf]?.type as any)" size="small">{{ ONSHELF_STATUS_MAP[detailData.onShelf]?.label }}</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="审核意见" :span="2">{{ detailData.checkOpinion || '--' }}</el-descriptions-item>
|
|
</el-descriptions>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="checkDialogVisible" title="服务审核" width="500px">
|
|
<el-form v-if="checkData" label-width="100px">
|
|
<el-form-item label="服务名称">{{ checkData.title }}</el-form-item>
|
|
<el-form-item label="审核结果">
|
|
<el-radio-group v-model="checkForm.checkStatus">
|
|
<el-radio :value="2">通过</el-radio>
|
|
<el-radio :value="3">驳回</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="审核意见" required>
|
|
<el-input v-model="checkForm.checkOpinion" type="textarea" :rows="4" placeholder="请填写审核意见" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="checkDialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="onSubmitCheck">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="categoryDialogVisible" title="修改分类" width="500px">
|
|
<el-form v-if="categoryData" label-width="100px">
|
|
<el-form-item label="服务名称">{{ categoryData.title }}</el-form-item>
|
|
<el-form-item label="当前分类">{{ categoryData.categoryName }}</el-form-item>
|
|
<el-form-item label="新分类"><el-cascader :options="[]" placeholder="请选择新分类" style="width: 100%" /></el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="categoryDialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="onSubmitCategory">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="batchCategoryDialogVisible" title="批量修改分类" width="500px">
|
|
<el-form label-width="100px">
|
|
<el-form-item label="已选服务">共 {{ selection.length }} 个</el-form-item>
|
|
<el-form-item label="新分类"><el-cascader :options="[]" placeholder="请选择新分类" style="width: 100%" /></el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="batchCategoryDialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="onSubmitBatchCategory">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog v-model="batchRejectDialogVisible" title="批量驳回" width="500px">
|
|
<el-form label-width="100px">
|
|
<el-form-item label="已选服务">共 {{ selection.length }} 个</el-form-item>
|
|
<el-form-item label="驳回原因" required><el-input v-model="batchRejectForm.reason" type="textarea" :rows="4" placeholder="请填写驳回原因" /></el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="batchRejectDialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="onSubmitBatchReject">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.rr-page { max-width: 1500px; }
|
|
.rr-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 16px; }
|
|
.rr-batch-bar { display: flex; align-items: center; gap: 8px; padding: 10px 16px; background: var(--bg-soft); border-radius: var(--radius); margin-bottom: 12px; }
|
|
.rr-batch-tip { font-size: 13px; color: var(--ink-2); margin-right: 8px; }
|
|
.rr-good-cell { display: flex; align-items: center; gap: 10px; }
|
|
.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; flex-shrink: 0; }
|
|
.rr-price { font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; font-size: 13px; font-weight: 500; color: var(--primary); }
|
|
</style>
|