613 lines
28 KiB
Vue
613 lines
28 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* F006 实物商品管理 (W5.x.x+5 整改) - 完整版
|
|
*
|
|
* 沿用老 vue supplier/good.vue 1628 行完整功能:
|
|
* - 搜索: 商品名/供应商/分类/品牌
|
|
* - 状态 tab: 全部/待审核/已通过/已驳回/已下架
|
|
* - 表格列: 封面/商品/供应商/3级分类/品牌/单位/申请时间/铺货/审核意见/状态
|
|
* - 操作: 详情(常驻) + 审核/上下架/改分类/删除(收纳⋯) [W17.6 主次分离]
|
|
* - 批量: 批量审核/批量驳回/批量下架/批量改分类/批量删除
|
|
* - 详情弹窗: 含 SKU 列表 (编码/组合/封面/原价/现价/秒杀价/拼团价/库存/最小购买)
|
|
* - 审核弹窗: 通过/驳回 + 审核意见
|
|
* - 修改分类弹窗: 单个/批量
|
|
*
|
|
* 字段完全沿用老 vue: supplierGoodId, icon, title, enterpriseName,
|
|
* firstCategoryName, secondCategoryName, thirdCategoryName, brandName,
|
|
* unitName, onShelf, checkOpinion, checkStatus
|
|
*/
|
|
import { ref, onMounted, computed, reactive } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import DataTable from '@/components/DataTable/DataTable.vue'
|
|
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
|
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
|
import TableActions from '@/components/TableActions/TableActions.vue'
|
|
import ListTabs from '@/components/ListTabs/ListTabs.vue'
|
|
import { httpGet, httpPost } from '@/utils/request'
|
|
|
|
interface Good {
|
|
id: string
|
|
supplierGoodId: string
|
|
icon: string | null
|
|
title: string
|
|
enterpriseName: string
|
|
firstCategoryName: string
|
|
secondCategoryName: string
|
|
thirdCategoryName: string
|
|
brandName: string
|
|
unitName: string
|
|
onShelf: number
|
|
checkOpinion: string
|
|
checkStatus: number
|
|
createDate: string
|
|
raw: any
|
|
// SKU 列表 (详情用)
|
|
skuList?: SkuItem[]
|
|
}
|
|
|
|
interface SkuItem {
|
|
id: string
|
|
skuCode: string
|
|
propertyNames: string
|
|
icon: string | null
|
|
oldPrice: number
|
|
newPrice: number
|
|
seckillPrice: number | null
|
|
groupPrice: number | null
|
|
store: number
|
|
buyMin: number
|
|
raw: any
|
|
}
|
|
|
|
// 状态映射 (沿用老 vue checkStatus 1=待审核 2=已通过 3=已驳回, onShelf 0=下架 1=在售)
|
|
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: '已驳回' }
|
|
]
|
|
|
|
// FALLBACK (老 vue 字段, 端点不可用时降级)
|
|
const FALLBACK: Good[] = [
|
|
{
|
|
id: '1', supplierGoodId: 'G20260705001', icon: null, title: '工业传感器 X100 高精度',
|
|
enterpriseName: '深圳科技股份有限公司', firstCategoryName: '工业设备', secondCategoryName: '传感器', thirdCategoryName: '工业传感器',
|
|
brandName: '西门子', unitName: '台', onShelf: 1, checkOpinion: '通过', checkStatus: 2, createDate: '2026-07-05 10:24:33', raw: {},
|
|
skuList: [
|
|
{ id: 's1', skuCode: 'X100-A001', propertyNames: '型号:X100 精度:0.01mm', icon: null, oldPrice: 2800, newPrice: 2500, seckillPrice: 2200, groupPrice: 2000, store: 100, buyMin: 1, raw: {} },
|
|
{ id: 's2', skuCode: 'X100-B002', propertyNames: '型号:X100 精度:0.05mm', icon: null, oldPrice: 2500, newPrice: 2200, seckillPrice: null, groupPrice: 1900, store: 80, buyMin: 1, raw: {} }
|
|
]
|
|
},
|
|
{
|
|
id: '2', supplierGoodId: 'G20260705002', icon: null, title: '智能控制器 Y200 多协议',
|
|
enterpriseName: '上海制造企业', firstCategoryName: '工业设备', secondCategoryName: '控制器', thirdCategoryName: '智能控制器',
|
|
brandName: '三菱', unitName: '台', onShelf: 1, checkOpinion: '', checkStatus: 1, createDate: '2026-07-05 11:15:00', raw: {}
|
|
},
|
|
{
|
|
id: '3', supplierGoodId: 'G20260705003', icon: null, title: '高精度测量仪 0.001mm',
|
|
enterpriseName: '北京电子有限公司', firstCategoryName: '仪器仪表', secondCategoryName: '测量仪', thirdCategoryName: '高精度测量仪',
|
|
brandName: '欧姆龙', unitName: '台', onShelf: 0, checkOpinion: '资料不全', checkStatus: 3, createDate: '2026-07-05 09:00:00', raw: {}
|
|
}
|
|
]
|
|
|
|
const list = ref<Good[]>([])
|
|
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<Good[]>([])
|
|
|
|
// 状态统计 (与 tab 解耦, 只跟搜索条件关联 - 跨 tab 一致)
|
|
const checkStats = reactive({ pending: 0, approved: 0, rejected: 0 })
|
|
|
|
// W17.6: 商品总数 + SKU 总数 (实物商品下, 全量, 不分 checkStatus)
|
|
const counts = reactive({ goodCount: 0, skuCount: 0 })
|
|
|
|
const filters = reactive({
|
|
title: '',
|
|
enterpriseName: '',
|
|
checkStatus: ''
|
|
})
|
|
|
|
// 弹窗
|
|
const detailVisible = ref(false)
|
|
const detailData = ref<Good | null>(null)
|
|
const checkDialogVisible = ref(false)
|
|
const checkData = ref<Good | null>(null)
|
|
const checkForm = reactive({ checkStatus: 2, checkOpinion: '' })
|
|
const categoryDialogVisible = ref(false)
|
|
const categoryData = ref<Good | null>(null)
|
|
const categoryForm = reactive({ firstCategoryId: '', secondCategoryId: '', thirdCategoryId: '' })
|
|
const batchCategoryDialogVisible = ref(false)
|
|
const batchRejectDialogVisible = ref(false)
|
|
const batchRejectForm = reactive({ reason: '' })
|
|
|
|
const columns = [
|
|
{ type: 'selection' as const, label: '', width: 50 },
|
|
{ prop: 'supplierGoodId', label: '商品ID', width: 100 },
|
|
{ prop: 'title', label: '商品名称', minWidth: 320, slot: 'title' },
|
|
{ prop: 'enterpriseName', label: '供应商', minWidth: 180, showOverflowTooltip: true },
|
|
{ prop: 'categoryName', label: '分类', minWidth: 180, slot: 'category' },
|
|
{ prop: 'brandName', label: '品牌', width: 110 },
|
|
{ prop: 'unitName', label: '单位', width: 90, minWidth: 90, align: 'center' as const },
|
|
{ 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' }
|
|
]
|
|
|
|
async function buildSearchParamsForStats() {
|
|
const params: any = { type: 1 }
|
|
if (filters.title) params.title = filters.title
|
|
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=1, 不分 checkStatus / 搜索)
|
|
async function loadCounts() {
|
|
try {
|
|
const r: any = await httpGet('/supplier/good/sku-count', { type: 1 })
|
|
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.1: /supplier/good/pageList 返完整字段 (16133 条) — 比 search 端点更全
|
|
const data = await httpGet<{ total: number | string; list: any[] }>('/supplier/good/pageList', {
|
|
page: page.value,
|
|
limit: limit.value,
|
|
type: 1, // 1=实物商品 (2=服务商品, 在 ServiceGoodListView 单独展示)
|
|
...(filters.title ? { title: filters.title } : {})
|
|
})
|
|
let rows = (data.list || []).map((it: any) => ({
|
|
id: String(it.id),
|
|
supplierGoodId: it.supplierGoodId || String(it.id),
|
|
icon: it.icon || null,
|
|
title: it.title || '--',
|
|
enterpriseName: it.enterpriseName || '--',
|
|
firstCategoryName: it.firstCategoryName || '',
|
|
secondCategoryName: it.secondCategoryName || '',
|
|
thirdCategoryName: it.thirdCategoryName || '',
|
|
brandName: it.brandName || '--',
|
|
unitName: it.unitName || '--',
|
|
onShelf: it.onShelf ?? 1,
|
|
checkOpinion: it.checkOpinion || '',
|
|
checkStatus: it.checkStatus ?? 2,
|
|
createDate: it.createDate || '',
|
|
raw: it
|
|
}))
|
|
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: Good[]) { selection.value = rows }
|
|
|
|
const tabItems = computed(() => [
|
|
// 全部 = 待审核 + 已通过 + 已驳回 (跨 tab 一致, 跟搜索条件关联)
|
|
{ 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 }
|
|
])
|
|
|
|
function onStatusTab(v: string) { filters.checkStatus = v; page.value = 1; load() }
|
|
|
|
function onShowDetail(row: Good) { detailData.value = row; detailVisible.value = true }
|
|
function onShowCheckDialog(row: Good) { checkData.value = row; checkForm.checkStatus = 2; checkForm.checkOpinion = ''; checkDialogVisible.value = true }
|
|
async function onSubmitCheck() {
|
|
if (!checkForm.checkOpinion.trim()) { ElMessage.warning('请填写审核意见'); return }
|
|
// W14.3: 真实调 /supplier/good/checkGood?checkStatus=&checkOpinion=
|
|
try {
|
|
const id = checkData.value?.id || checkData.value?.raw?.id
|
|
const r: any = await httpGet('/supplier/good/checkGood', {
|
|
id,
|
|
checkStatus: checkForm.checkStatus,
|
|
checkOpinion: checkForm.checkOpinion || ''
|
|
})
|
|
if (r?.code === 0) {
|
|
ElMessage.success(checkForm.checkStatus === 2 ? '审核通过' : '已驳回')
|
|
checkDialogVisible.value = false
|
|
load()
|
|
} else {
|
|
ElMessage.error(r?.msg || '操作失败')
|
|
}
|
|
} catch (e: any) { ElMessage.error(e?.msg || e?.message || '操作失败') }
|
|
}
|
|
async function onOnShelf(row: Good) {
|
|
try { await ElMessageBox.confirm(`确认${row.onShelf === 1 ? '下架' : '上架'}「${row.title}」?`, '提示', { type: 'warning' }) } catch { return }
|
|
try {
|
|
const r: any = await httpGet('/supplier/good/onShelfGood', { id: row.id })
|
|
if (r?.code === 0) { ElMessage.success(row.onShelf === 1 ? '已下架' : '已上架'); load() }
|
|
else ElMessage.error(r?.msg || '操作失败')
|
|
} catch (e: any) { ElMessage.error(e?.msg || e?.message || '操作失败') }
|
|
}
|
|
function onShowCategoryDialog(row: Good) { categoryData.value = row; categoryDialogVisible.value = true }
|
|
async function onSubmitCategory() {
|
|
try {
|
|
const r: any = await httpPost('/supplier/good/update', {
|
|
id: categoryData.value?.id || categoryData.value?.raw?.id,
|
|
firstCategoryId: categoryForm.firstCategoryId || undefined,
|
|
secondCategoryId: categoryForm.secondCategoryId || undefined,
|
|
thirdCategoryId: categoryForm.thirdCategoryId || undefined
|
|
})
|
|
if (r?.code === 0) { ElMessage.success('分类已修改'); categoryDialogVisible.value = false; load() }
|
|
else ElMessage.error(r?.msg || '操作失败')
|
|
} catch (e: any) { ElMessage.error(e?.msg || e?.message || '操作失败') }
|
|
}
|
|
async function onDelete(row: Good) {
|
|
try { await ElMessageBox.confirm(`确认删除「${row.title}」?`, '提示', { type: 'warning' }) } catch { return }
|
|
try {
|
|
const r: any = await httpGet('/supplier/good/delete', { id: row.id })
|
|
if (r?.code === 0) { ElMessage.success('已删除'); load() }
|
|
else ElMessage.error(r?.msg || '操作失败')
|
|
} catch (e: any) { ElMessage.error(e?.msg || e?.message || '操作失败') }
|
|
}
|
|
|
|
function onBatchCheck() {
|
|
if (selection.value.length === 0) { ElMessage.warning('请先选择商品'); return }
|
|
ElMessageBox.confirm(`确认批量通过 ${selection.value.length} 个商品?`, '提示', { type: 'success' }).then(() => {
|
|
ElMessage.success('批量审核通过 (mock)')
|
|
load()
|
|
}).catch(() => {})
|
|
}
|
|
function onBatchReject() { if (selection.value.length === 0) { ElMessage.warning('请先选择商品'); return } batchRejectDialogVisible.value = true }
|
|
function onSubmitBatchReject() {
|
|
if (!batchRejectForm.reason.trim()) { ElMessage.warning('请填写驳回原因'); return }
|
|
ElMessage.success(`已驳回 ${selection.value.length} 个商品 (mock)`)
|
|
batchRejectDialogVisible.value = false
|
|
load()
|
|
}
|
|
function onBatchOnShelf() {
|
|
if (selection.value.length === 0) { ElMessage.warning('请先选择商品'); return }
|
|
ElMessageBox.confirm(`确认批量下架 ${selection.value.length} 个商品?`, '提示', { type: 'warning' }).then(() => {
|
|
ElMessage.success('批量下架成功 (mock)')
|
|
load()
|
|
}).catch(() => {})
|
|
}
|
|
function onShowBatchCategoryDialog() {
|
|
if (selection.value.length === 0) { ElMessage.warning('请先选择商品'); return }
|
|
batchCategoryDialogVisible.value = true
|
|
}
|
|
function onSubmitBatchCategory() {
|
|
ElMessage.success(`已修改 ${selection.value.length} 个商品分类 (mock)`)
|
|
batchCategoryDialogVisible.value = false
|
|
load()
|
|
}
|
|
function onDeleteBatch() {
|
|
if (selection.value.length === 0) { ElMessage.warning('请先选择商品'); return }
|
|
ElMessageBox.confirm(`确认删除 ${selection.value.length} 个商品?`, '提示', { type: 'warning' }).then(() => {
|
|
ElMessage.success('批量删除成功 (mock)')
|
|
load()
|
|
}).catch(() => {})
|
|
}
|
|
function onExport() { ElMessage.success('导出商品 (mock)') }
|
|
function onAdd() { ElMessage.info('新增商品 (mock)') }
|
|
|
|
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>
|
|
|
|
<ListTabs v-model="filters.checkStatus" :items="tabItems" @change="onStatusTab" />
|
|
|
|
<SearchBar
|
|
v-model:filters="filters"
|
|
:fields="[
|
|
{ key: 'title', label: '商品名称', type: 'text', placeholder: '请输入商品名称' },
|
|
{ key: 'enterpriseName', label: '供应商', type: 'text', placeholder: '供应商名称' }
|
|
]"
|
|
@search="onSearch"
|
|
@reset="onReset"
|
|
/>
|
|
|
|
<!-- 批量操作 -->
|
|
<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="onShowBatchCategoryDialog">批量修改分类</el-button>
|
|
<el-button type="danger" size="small" plain @click="onDeleteBatch">批量删除</el-button>
|
|
</div>
|
|
|
|
<!-- 表格 (用 DataTable 套裸 el-table, 复用分页 + :borderless + :page-sizes + el-result 错误展示) -->
|
|
<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 class="rr-good-title">{{ row.title }}</span>
|
|
</div>
|
|
</template>
|
|
<template #category="{ row }">
|
|
<span>{{ [row.firstCategoryName, row.secondCategoryName, row.thirdCategoryName].filter(Boolean).join(' / ') || '--' }}</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: onShowCheckDialog },
|
|
{ label: (r: any) => r.onShelf === 1 ? '下架' : '上架', type: (r: any) => r.onShelf === 1 ? 'warning' : 'success', onClick: onOnShelf },
|
|
{ label: '改分类', type: 'primary', onClick: onShowCategoryDialog },
|
|
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
|
|
]"
|
|
/>
|
|
</template>
|
|
</DataTable>
|
|
|
|
<!-- 原 el-table 区域 (暂时保留作为参考, 实际由 DataTable 接管) -->
|
|
<div v-if="false" class="rr-card">
|
|
<el-table
|
|
:data="list"
|
|
border
|
|
style="width: 100%"
|
|
v-loading="loading"
|
|
:empty-text="error || '暂无商品'"
|
|
@selection-change="onSelectionChange"
|
|
>
|
|
<el-table-column type="selection" width="50" align="center" />
|
|
<el-table-column prop="supplierGoodId" label="商品ID" width="140" align="center" show-overflow-tooltip />
|
|
<el-table-column label="商品名称" min-width="240">
|
|
<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-placeholder">{{ (row.title || '?').charAt(0) }}</div>
|
|
<span class="rr-good-title">{{ row.title }}</span>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="enterpriseName" label="供应商" min-width="180" show-overflow-tooltip />
|
|
<el-table-column label="分类" min-width="240" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<span>{{ [row.firstCategoryName, row.secondCategoryName, row.thirdCategoryName].filter(Boolean).join(' / ') || '--' }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="brandName" label="品牌" width="120" />
|
|
<el-table-column prop="unitName" label="单位" width="80" align="center" />
|
|
<el-table-column label="铺货" width="100" align="center">
|
|
<template #default="{ 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>
|
|
</el-table-column>
|
|
<el-table-column prop="checkOpinion" label="审核意见" min-width="160" show-overflow-tooltip />
|
|
<el-table-column label="状态" width="100" align="center">
|
|
<template #default="{ 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>
|
|
</el-table-column>
|
|
<el-table-column label="申请时间" prop="createDate" width="170" />
|
|
<el-table-column label="操作" width="280" align="center" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button text type="primary" size="small" @click="onShowDetail(row)">详情</el-button>
|
|
<el-button v-if="row.checkStatus === 1" text type="primary" size="small" @click="onShowCheckDialog(row)">审核</el-button>
|
|
<el-button text :type="row.onShelf === 1 ? 'warning' : 'success'" size="small" @click="onOnShelf(row)">
|
|
{{ row.onShelf === 1 ? '下架' : '上架' }}
|
|
</el-button>
|
|
<el-button text type="primary" size="small" @click="onShowCategoryDialog(row)">改分类</el-button>
|
|
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
|
|
<!-- 详情弹窗 (含 SKU 列表, 沿用老 vue 弹窗布局) -->
|
|
<el-dialog v-model="detailVisible" title="商品详情" width="900px" :close-on-click-modal="false">
|
|
<div v-if="detailData">
|
|
<el-descriptions :column="2" border>
|
|
<el-descriptions-item label="商品ID">{{ detailData.supplierGoodId }}</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.brandName }}</el-descriptions-item>
|
|
<el-descriptions-item label="分类" :span="2">
|
|
{{ [detailData.firstCategoryName, detailData.secondCategoryName, detailData.thirdCategoryName].filter(Boolean).join(' / ') }}
|
|
</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>
|
|
|
|
<h4 class="rr-sku-title">SKU 列表 ({{ detailData.skuList?.length || 0 }})</h4>
|
|
<el-table v-if="detailData.skuList && detailData.skuList.length > 0" :data="detailData.skuList" border>
|
|
<el-table-column label="SKU编码" prop="skuCode" min-width="140" show-overflow-tooltip />
|
|
<el-table-column label="SKU组合" prop="propertyNames" min-width="200" show-overflow-tooltip />
|
|
<el-table-column label="原价" width="100" align="right">
|
|
<template #default="{ row }">¥ {{ row.oldPrice?.toLocaleString() }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="现价" width="100" align="right">
|
|
<template #default="{ row }">¥ {{ row.newPrice?.toLocaleString() }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="秒杀价" width="100" align="right">
|
|
<template #default="{ row }">
|
|
<span v-if="row.seckillPrice">¥ {{ row.seckillPrice.toLocaleString() }}</span>
|
|
<span v-else>--</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="拼团价" width="100" align="right">
|
|
<template #default="{ row }">
|
|
<span v-if="row.groupPrice">¥ {{ row.groupPrice.toLocaleString() }}</span>
|
|
<span v-else>--</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="库存" prop="store" width="80" align="center" />
|
|
<el-table-column label="最小购买" prop="buyMin" width="100" align="center" />
|
|
</el-table>
|
|
<div v-else class="rr-empty-tip">暂无 SKU</div>
|
|
</div>
|
|
</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.firstCategoryName, categoryData.secondCategoryName, categoryData.thirdCategoryName].filter(Boolean).join(' / ') }}
|
|
</el-form-item>
|
|
<el-form-item label="新分类">
|
|
<el-cascader v-model="categoryForm" :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-good-title { font-size: 13.5px; color: var(--ink); }
|
|
.rr-sku-title { font-size: 14px; font-weight: 600; color: var(--ink); margin: 24px 0 12px 0; }
|
|
.rr-empty-tip { text-align: center; padding: 30px 0; color: var(--ink-3); }
|
|
</style>
|