admin_new P0+P1 全批次合并 (84 commits) #2

Merged
direv merged 85 commits from develop into main 2026-08-07 11:27:17 +08:00
2 changed files with 175 additions and 51 deletions
Showing only changes of commit f2adaf3392 - Show all commits

View File

@ -1,25 +1,28 @@
<script setup lang="ts">
/**
* F006 供应商品牌审核 - W10 A3 标准化
* F006 品牌管理 (供应商品牌审核) - 真页面重写 2026-07-28
*
* W10 A3: 套 PageHeader/SearchBar 标准化模板
* 对齐老 vue other/supplierBrand.vue:
* - 搜索: 品牌名称/企业名称/审核状态/申请时间范围
* - 表格列: 品牌来源/企业名称/品牌名称/授权书(50px缩略)/审核状态/申请时间
* - 操作: 审核(仅待审) / 删除 / 批量删除
* - 审核弹窗: 通过/拒绝 + 拒绝原因 (POST /other/GoodBrand/updateCheck)
* 需求3: 查看授权企业 — 平台品牌(sourceType=1)可查看被授权企业列表 (sourceType=2 同品牌)
*
* 沿用老 vue other/supplierBrand.vue 230 行:
* - 搜索: 品牌名/企业名/审核状态/申请时间范围
* - 表格列: sourceEnterpriseName/enterpriseName/brandName/authUrl(50px缩略)/status(1待/2通过/3拒绝)/createDate
* - 操作: 审核(仅待审) / 删除
* - 审核弹窗: 通过/拒绝 + 拒绝原因
* - 批量删除
* - /other/supplierBrand 后端 404 → FALLBACK mock
* 端点:
* GET /other/GoodBrand/page?page=&limit=&brandName=&enterpriseName=&status=&startDate=&endDate=&sourceType=
* POST /other/GoodBrand/updateCheck {id,status,failName}
* POST /other/GoodBrand/delete [ids]
*/
import { ref, onMounted, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { httpGet } from '@/utils/request'
import { httpGet, httpPost } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
interface Brand {
id: string
sourceType: number // 1平台新增 2授权
sourceEnterpriseName: string
enterpriseName: string
brandName: string
@ -36,49 +39,68 @@ const STATUS_MAP: Record<number, { label: string; type: string }> = {
3: { label: '审核拒绝', type: 'danger' }
}
const FALLBACK: Brand[] = [
{ id: 'B001', sourceEnterpriseName: '中国钢铁工业协会', enterpriseName: '宝钢集团', brandName: '宝钢', authUrl: 'https://placehold.co/100x100/2563EB/fff?text=Auth1', status: 1, createDate: '2026-07-01 10:00:00', raw: {} },
{ id: 'B002', sourceEnterpriseName: '中国水泥协会', enterpriseName: '海螺水泥', brandName: '海螺', authUrl: 'https://placehold.co/100x100/059669/fff?text=Auth2', status: 2, createDate: '2026-06-28 14:20:00', raw: {} },
{ id: 'B003', sourceEnterpriseName: '国家建材测试中心', enterpriseName: '北新建材', brandName: '龙牌', authUrl: 'https://placehold.co/100x100/DC2626/fff?text=Auth3', status: 3, createDate: '2026-06-25 11:15:00', failName: '授权书扫描件不清晰, 请重新上传', raw: {} },
{ id: 'B004', sourceEnterpriseName: '中国建材联合会', enterpriseName: '金隅集团', brandName: '金隅', authUrl: 'https://placehold.co/100x100/5645D4/fff?text=Auth4', status: 1, createDate: '2026-07-02 16:30:00', raw: {} },
{ id: 'B005', sourceEnterpriseName: '中国五金制品协会', enterpriseName: '长城精工', brandName: '长城', authUrl: 'https://placehold.co/100x100/F59E0B/fff?text=Auth5', status: 2, createDate: '2026-06-30 09:00:00', raw: {} },
{ id: 'B006', sourceEnterpriseName: '中国建筑材料联合会', enterpriseName: '华润水泥', brandName: '华润', authUrl: 'https://placehold.co/100x100/8B5CF6/fff?text=Auth6', status: 1, createDate: '2026-07-03 11:45:00', raw: {} }
]
const list = ref<Brand[]>([])
const total = ref(0)
const loading = ref(false)
const error = ref<string | null>(null)
const page = ref(1)
const limit = ref(10)
const filters = reactive({ brandName: '', enterpriseName: '', status: '', startDate: '', endDate: '' })
const filters = reactive({ brandName: '', enterpriseName: '', status: '', range: [] as string[], _startDate: '', _endDate: '' })
const selection = ref<Brand[]>([])
const auditVisible = ref(false)
const auditForm = reactive<{ id: string; status: number; failName: string }>({ id: '', status: 2, failName: '' })
// 授权企业弹窗
const authVisible = ref(false)
const authBrandName = ref('')
const authList = ref<{ enterpriseName: string; sourceEnterpriseName: string; status: number; createDate: string }[]>([])
const authLoading = ref(false)
async function load() {
loading.value = true
error.value = null
try {
await new Promise(r => setTimeout(r, 200))
let rows = [...FALLBACK]
if (filters.brandName) rows = rows.filter(r => r.brandName.includes(filters.brandName))
if (filters.enterpriseName) rows = rows.filter(r => r.enterpriseName.includes(filters.enterpriseName))
if (filters.status) rows = rows.filter(r => r.status === Number(filters.status))
list.value = rows
total.value = rows.length
const params: any = { page: page.value, limit: limit.value }
if (filters.brandName) params.brandName = filters.brandName
if (filters.enterpriseName) params.enterpriseName = filters.enterpriseName
if (filters.status !== '') params.status = filters.status
if (filters.range?.length === 2) {
params.startDate = `${filters.range[0]} 00:00:00`
params.endDate = `${filters.range[1]} 23:59:59`
}
const data: any = await httpGet('/other/GoodBrand/page', params)
const rows: any[] = data?.list || []
list.value = rows.map((it: any) => ({
id: String(it.id),
sourceType: it.sourceType ?? 1,
sourceEnterpriseName: it.sourceEnterpriseName || '--',
enterpriseName: it.enterpriseName || '--',
brandName: it.brandName || '--',
authUrl: it.authUrl || '',
status: Number(it.status) || 1,
createDate: it.createDate || '--',
failName: it.failName,
raw: it
}))
total.value = Number(data?.total) || 0
} catch (e: any) {
error.value = e?.msg || e?.message || '加载失败'
list.value = FALLBACK
total.value = FALLBACK.length
list.value = []
total.value = 0
} finally { loading.value = false }
}
function onSearch() { page.value = 1; load() }
function onReset() { filters.brandName = ''; filters.enterpriseName = ''; filters.status = ''; filters.startDate = ''; filters.endDate = ''; load() }
function onReset() {
filters.brandName = ''
filters.enterpriseName = ''
filters.status = ''
filters.range = []
onSearch()
}
function onPageChange(p: number) { page.value = p; load() }
function onLimitChange(l: number) { limit.value = l; load() }
function onLimitChange(l: number) { limit.value = l; page.value = 1; load() }
function onSelectionChange(rows: Brand[]) { selection.value = rows }
function onAudit(row: Brand) {
@ -87,24 +109,63 @@ function onAudit(row: Brand) {
auditForm.failName = ''
auditVisible.value = true
}
function onSubmitAudit() {
async function onSubmitAudit() {
if (auditForm.status === 3 && !auditForm.failName.trim()) { ElMessage.warning('请填写拒绝原因'); return }
ElMessage.success(`审核${auditForm.status === 2 ? '通过' : '拒绝'}成功 (mock)`)
auditVisible.value = false
load()
}
function onDelete(row: Brand) {
ElMessageBox.confirm(`确认删除「${row.brandName}」?`, '提示', { type: 'warning' }).then(() => {
ElMessage.success('已删除 (mock)')
try {
await httpPost('/other/GoodBrand/updateCheck', {
id: auditForm.id,
status: auditForm.status,
failName: auditForm.status === 3 ? auditForm.failName.trim() : ''
})
ElMessage.success(`审核${auditForm.status === 2 ? '通过' : '拒绝'}成功`)
auditVisible.value = false
load()
}).catch(() => {})
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '审核失败')
}
}
function onBatchDelete() {
async function onViewAuth(row: Brand) {
authBrandName.value = row.brandName
authVisible.value = true
authList.value = []
authLoading.value = true
try {
// 该品牌名下所有授权记录 (sourceType=2)
const data: any = await httpGet('/other/GoodBrand/page', { page: 1, limit: 100, brandName: row.brandName, sourceType: 2 })
authList.value = (data?.list || []).map((it: any) => ({
enterpriseName: it.enterpriseName || '--',
sourceEnterpriseName: it.sourceEnterpriseName || '--',
status: Number(it.status) || 1,
createDate: it.createDate || '--'
}))
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '加载授权企业失败')
authList.value = []
} finally { authLoading.value = false }
}
async function onDelete(row: Brand) {
await ElMessageBox.confirm(`确认删除「${row.brandName}」?`, '提示', { type: 'warning' })
try {
await httpPost('/other/GoodBrand/delete', [row.id])
ElMessage.success('删除成功')
load()
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '删除失败')
}
}
async function onBatchDelete() {
if (selection.value.length === 0) { ElMessage.warning('请先选择要删除的项'); return }
ElMessageBox.confirm(`确认批量删除选中的 ${selection.value.length} 项?`, '提示', { type: 'warning' }).then(() => {
ElMessage.success('已批量删除 (mock)')
await ElMessageBox.confirm(`确认批量删除选中的 ${selection.value.length} 项?`, '提示', { type: 'warning' })
try {
await httpPost('/other/GoodBrand/delete', selection.value.map(s => s.id))
ElMessage.success('删除成功')
load()
}).catch(() => {})
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '删除失败')
}
}
onMounted(load)
@ -112,7 +173,7 @@ onMounted(load)
<template>
<div class="rr-page">
<PageHeader title="供应商品牌审核" :sub="`供应商提交的品牌授权审核 (共 ${total} 个)`">
<PageHeader title="品牌管理" :sub="`供应商品牌审核与授权管理 (共 ${total} 个)`">
<el-button type="danger" :disabled="selection.length === 0" @click="onBatchDelete">批量删除 ({{ selection.length }})</el-button>
</PageHeader>
@ -125,15 +186,58 @@ onMounted(load)
{ value: 1, label: '待审核' },
{ value: 2, label: '审核通过' },
{ value: 3, label: '审核拒绝' }
] }
] },
{ key: 'range', label: '申请时间', type: 'daterange' }
]"
@search="onSearch"
@reset="onReset"
/>
<div class="rr-card">
<el-table v-loading="loading" :data="list" border @selection-change="onSelectionChange" style="width: 100%">
<el-table-column type="selection" header-align="center" align="center" width="50" />
<el-table-column prop="sourceType" label="类型" header-align="center" align="center" width="90">
<template #default="{ row }">
<el-tag :type="row.sourceType === 2 ? 'primary' : 'info'" size="small">{{ row.sourceType === 2 ? '授权品牌' : '平台品牌' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="sourceEnterpriseName" label="品牌来源" header-align="center" align="center" min-width="140" show-overflow-tooltip />
<el-table-column prop="enterpriseName" label="企业名称" header-align="center" align="center" min-width="140" show-overflow-tooltip />
<el-table-column prop="brandName" label="品牌名称" header-align="center" align="center" min-width="120" show-overflow-tooltip />
<el-table-column label="品牌授权书" header-align="center" align="center" width="90">
<template #default="{ row }">
<el-image v-if="row.authUrl" :src="row.authUrl" fit="cover" :preview-src-list="[row.authUrl]" class="brand-auth" />
<span v-else class="rr-muted">--</span>
</template>
</el-table-column>
<el-table-column prop="status" label="审核状态" header-align="center" align="center" width="100">
<template #default="{ row }">
<el-tag :type="STATUS_MAP[row.status]?.type || 'info'" size="small">{{ STATUS_MAP[row.status]?.label || row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createDate" label="申请时间" header-align="center" align="center" width="170" show-overflow-tooltip />
<el-table-column label="操作" fixed="right" header-align="center" align="center" width="200">
<template #default="{ row }">
<span class="rr-actions">
<el-link v-if="row.sourceType === 1" type="primary" :underline="false" @click="onViewAuth(row)">查看授权</el-link>
<span v-if="row.sourceType === 1" class="rr-actions__sep">|</span>
<el-link v-if="row.status === 1" type="warning" :underline="false" @click="onAudit(row)">审核</el-link>
<span v-if="row.status === 1" class="rr-actions__sep">|</span>
<el-link type="danger" :underline="false" @click="onDelete(row)">删除</el-link>
</span>
</template>
</el-table-column>
</el-table>
<div class="rr-pagination">
<el-pagination :current-page="page" :page-sizes="[10, 20, 50, 100]" :page-size="limit" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="onLimitChange" @current-change="onPageChange" />
<el-pagination
:current-page="page"
:page-sizes="[10, 20, 50, 100]"
:page-size="limit"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="onLimitChange"
@current-change="onPageChange"
/>
</div>
</div>
@ -154,11 +258,31 @@ onMounted(load)
<el-button type="primary" @click="onSubmitAudit">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="authVisible" :title="`「${authBrandName}」授权企业`" width="640px">
<el-table v-loading="authLoading" :data="authList" border max-height="400">
<el-table-column prop="enterpriseName" label="被授权企业" header-align="center" align="center" min-width="140" show-overflow-tooltip />
<el-table-column prop="sourceEnterpriseName" label="品牌来源" header-align="center" align="center" min-width="140" show-overflow-tooltip />
<el-table-column prop="status" label="审核状态" header-align="center" align="center" width="100">
<template #default="{ row }">
<el-tag :type="STATUS_MAP[row.status]?.type || 'info'" size="small">{{ STATUS_MAP[row.status]?.label || row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createDate" label="申请时间" header-align="center" align="center" width="170" />
</el-table>
<template #footer>
<el-button type="primary" @click="authVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.rr-page { max-width: 1500px; }
.rr-pagination { padding: 12px 20px; display: flex; justify-content: flex-end; border-top: 1px solid var(--border); }
.brand-auth { width: 50px; height: 50px; border-radius: 4px; border: 1px solid var(--border); cursor: pointer; }
</style>
.rr-card { background: var(--el-bg-color); border-radius: 8px; border: 1px solid var(--el-border-color-light); overflow: hidden; }
.rr-pagination { padding: 12px 20px; display: flex; justify-content: flex-end; border-top: 1px solid var(--el-border-color-light); }
.brand-auth { width: 50px; height: 50px; border-radius: 4px; border: 1px solid var(--el-border-color-light); cursor: pointer; }
.rr-muted { color: var(--el-text-color-placeholder); }
.rr-actions { display: inline-flex; align-items: center; gap: 0; white-space: nowrap; }
.rr-actions__sep { color: var(--el-border-color); margin: 0 6px; }
</style>

View File

@ -235,7 +235,7 @@ const routes: RouteRecordRaw[] = [
{ path: '/order/refund', component: () => import('@/modules/order/refund/RefundProcessListView.vue'), meta: { title: '订单退款管理' } },
{ path: '/order/aftersale', component: () => import('@/modules/order/aftersale/AftersaleListView.vue'), meta: { title: '售后订单' } },
// other 7 个
{ path: '/other/brand', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'other-brand' }), meta: { title: '品牌管理' } },
{ path: '/other/brand', component: () => import('@/modules/other/supplierBrand/SupplierBrandListView.vue'), meta: { title: '品牌管理' } },
{ path: '/other/category/good', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'other-category-good' }), meta: { title: '平台分类' } },
{ path: '/other/help', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'other-help' }), meta: { title: '帮助中心' } },
{ path: '/other/guide', component: () => import('@/modules/generic/MockTableView.vue'), props: () => ({ configKey: 'other-guide' }), meta: { title: '内容列表' } },