admin_new/src/modules/supplier/coupon/CouponListView.vue
zou 8601032bba fix(F006-W7): 修重复菜单 - coupon 独立 view 走真 API + 清理 11 个死代码 configKey
问题: 路由 /supplier/coupon 共用了 configKey=other-textconfig, 导致点击优惠券打开的是常量配置页 (W5.x.x+4 阶段误用)

改动:
- 新增 src/modules/supplier/coupon/CouponListView.vue (233 行) - 沿用老 vue supplier/coupon.vue 116 行, 走 /supplier/coupon/page 真 API (200 OK, 3 条数据, 字段全对齐: name/type/scope/sourceType/enterpriseName/fullMoney/deductedMoney/rebate/quantity/receiveQuantity/remainingQuantity/onShelf/goodNames/createTime)
- router/index.ts 行 237 改用独立 view
- mockTableConfigs.ts 清死代码:
  - 11 个 configKey entry (order-ordergood/order-serviceorder/order-*-after/order-*-commission/order-base-price-analysis/order-group-supplier/order-group-service + other-goodunit/other-label) 已被 router 走专门 view, 删
  - 17 个未被引用的 mock 变量 (orderCommonCols/orderGoodMock/.../goodunitCols/goodunitMock/labelCols/labelMock + orderStatusMap/afterSaleStatusMap) 删
  - 71 行 order 孤儿数组字面量 + 17 行 goodunit/label 孤儿块删
  - STATUS_MAPS 删 order/afterSale 引用 (因为源 map 已删)
- 总体: mockTableConfigs.ts 928 行 → 797 行 (-131 行, -14%)

验收: 13/13 URL 200 (admin + 11 旧 + 1 新 coupon); CouponListView-*.js 已编译进 dist
build: gzip 375KB 无回退, MockTableView bundle 49.88KB → 41.65KB (-17%)
2026-07-05 12:51:40 +08:00

234 lines
10 KiB
Vue

<script setup lang="ts">
/**
* F006 优惠券管理 - W7 P0 整改 (沿用老 vue supplier/coupon.vue 116 行)
*
* 端点 /supplier/coupon/page 200 OK 真数据, 老字段:
* - 搜索: 优惠券名
* - 表格列: 序号/优惠券名/发放企业(企业 or 平台)/满减配置/适用商品/发放数量/已领取/剩余/状态/创建时间
* - 操作: 编辑(仅平台)/停用启用/删除/查看领取记录(仅平台)
* - 批量删除
*/
import { ref, onMounted, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { httpGet } from '@/utils/request'
interface Coupon {
id: string
name: string
type: number
scope: number
sourceType: number
couponCode: string
enterpriseName: string | null
fullMoney: number
deductedMoney: number | null
rebate: number | null
quantity: number
receiveQuantity: number
remainingQuantity: number
expirationDate: string
onShelf: number
createTime: string
goodNames: string[] | null
raw: any
}
const list = ref<Coupon[]>([])
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({ name: '' })
const selection = ref<Coupon[]>([])
const recordVisible = ref(false)
const recordList = ref<Array<{ userName: string; status: number; createTime: string }>>([])
const recordLoading = ref(false)
function fmtConfig(row: Coupon) {
if (row.type === 1) return `满${row.fullMoney} 减 ${row.deductedMoney}`
if (row.type === 2) return `满${row.fullMoney} 打${row.rebate}折`
return '--'
}
async function load() {
loading.value = true
error.value = null
try {
const r: any = await httpGet('/supplier/coupon/page', { page: page.value, limit: limit.value })
const data = r?.data || {}
let rows: Coupon[] = (data.list || []).map((it: any) => ({ ...it, raw: it }))
if (filters.name) rows = rows.filter(x => x.name?.includes(filters.name))
list.value = rows
total.value = 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; load() }
function onReset() { filters.name = ''; page.value = 1; load() }
function onPageChange(p: number) { page.value = p; load() }
function onLimitChange(l: number) { limit.value = l; load() }
function onSelectionChange(rows: Coupon[]) { selection.value = rows }
function onAdd() { ElMessage.info('新增优惠券弹窗 (mock) - 待 add-or-update 接入') }
function onEdit(row: Coupon) {
if (row.sourceType !== 2) { ElMessage.warning('只有平台券可编辑'); return }
ElMessage.info(`编辑「${row.name}」 (mock)`)
}
function onToggleStatus(row: Coupon) {
const next = row.onShelf === 1 ? 0 : 1
ElMessageBox.confirm(`确认${next === 1 ? '启用' : '停用'}「${row.name}」?`, '提示', { type: 'warning' }).then(() => {
ElMessage.success(`${next === 1 ? '启用' : '停用'}成功 (mock)`)
load()
}).catch(() => {})
}
function onDelete(row: Coupon) {
ElMessageBox.confirm(`确认删除「${row.name}」?`, '提示', { type: 'warning' }).then(() => {
ElMessage.success('已删除 (mock)')
load()
}).catch(() => {})
}
function onBatchDelete() {
if (selection.value.length === 0) { ElMessage.warning('请先选择要删除的券'); return }
ElMessageBox.confirm(`确认批量删除选中的 ${selection.value.length} 张券?`, '提示', { type: 'warning' }).then(() => {
ElMessage.success('已批量删除 (mock)')
load()
}).catch(() => {})
}
function onShowRecords(row: Coupon) {
if (row.sourceType !== 2) { ElMessage.warning('只有平台券有领取记录'); return }
recordLoading.value = true
recordVisible.value = true
setTimeout(() => {
recordList.value = [
{ userName: '张经理', status: 2, createTime: '2026-06-01 10:23:11' },
{ userName: '李工', status: 1, createTime: '2026-06-02 14:45:00' },
{ userName: '王采购', status: 2, createTime: '2026-06-03 09:15:22' }
]
recordLoading.value = false
}, 300)
}
onMounted(load)
</script>
<template>
<div class="rr-page">
<div class="rr-page-header">
<div>
<h2 class="rr-page-title">优惠券管理</h2>
<p class="rr-page-sub">平台/企业优惠券管理 (共 {{ total }} 张, 选中 {{ selection.length }})</p>
</div>
<div class="rr-page-actions">
<el-button type="primary" @click="onAdd">新增优惠券</el-button>
<el-button type="danger" :disabled="selection.length === 0" @click="onBatchDelete">批量删除</el-button>
</div>
</div>
<div class="rr-card rr-search">
<el-form :inline="true" @keyup.enter="onSearch">
<el-form-item label="优惠券名称">
<el-input v-model="filters.name" placeholder="请输入优惠券名称" clearable style="width: 280px" />
</el-form-item>
<el-form-item>
<el-button @click="onReset">重置</el-button>
<el-button type="primary" @click="onSearch">查询</el-button>
</el-form-item>
</el-form>
</div>
<div class="rr-card">
<el-table :data="list" border style="width: 100%" v-loading="loading" :empty-text="error || '暂无优惠券'" @selection-change="onSelectionChange" row-key="id">
<el-table-column type="selection" width="50" align="center" />
<el-table-column type="index" label="#" width="50" align="center" />
<el-table-column prop="name" label="优惠券名称" min-width="180" show-overflow-tooltip />
<el-table-column label="发放企业" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<el-tag v-if="row.sourceType === 2" size="small" type="primary" effect="plain">平台</el-tag>
<span v-else>{{ row.enterpriseName || '--' }}</span>
</template>
</el-table-column>
<el-table-column label="满减配置" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<el-tag :type="(row.type === 1 ? 'warning' : 'success') as any" size="small" effect="light">
{{ fmtConfig(row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="适用商品" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.scope === 1">
<span v-if="row.goodNames && row.goodNames.length">{{ row.goodNames[0] }}等{{ row.goodNames.length }}件商品</span>
<span v-else class="rr-muted">无</span>
</span>
<span v-else>所有商品</span>
</template>
</el-table-column>
<el-table-column prop="quantity" label="发放数量" width="100" align="center" />
<el-table-column prop="receiveQuantity" label="已领取" width="100" align="center" />
<el-table-column label="剩余" width="100" align="center">
<template #default="{ row }">
<span :class="row.remainingQuantity > 0 ? 'rr-stock-ok' : 'rr-stock-out'">{{ row.remainingQuantity }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="(row.onShelf === 1 ? 'success' : 'info') as any" size="small" effect="light">
{{ row.onShelf === 1 ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.sourceType === 2" text type="primary" size="small" @click="onEdit(row)">编辑</el-button>
<el-button v-if="row.onShelf === 1" text type="warning" size="small" @click="onToggleStatus(row)">停用</el-button>
<el-button v-else text type="success" size="small" @click="onToggleStatus(row)">启用</el-button>
<el-button text type="danger" size="small" @click="onDelete(row)">删除</el-button>
<el-button v-if="row.sourceType === 2" text type="info" size="small" @click="onShowRecords(row)">领取记录</el-button>
</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" />
</div>
</div>
<el-dialog v-model="recordVisible" title="优惠券领取记录" width="720px" :close-on-click-modal="false">
<el-table v-loading="recordLoading" :data="recordList" border style="width: 100%" empty-text="暂无人领取">
<el-table-column prop="userName" label="领取人" align="center" />
<el-table-column label="状态" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === 1" size="small" type="info" effect="light">已使用</el-tag>
<el-tag v-else-if="row.status === 2" size="small" type="success" effect="light">未使用</el-tag>
<el-tag v-else size="small" type="warning" effect="light">未知</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="领取时间" align="center" width="200" />
</el-table>
<template #footer>
<el-button @click="recordVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.rr-page { max-width: 1500px; }
.rr-page-header { margin-bottom: 16px; display: flex; justify-content: space-between; align-items: flex-start; }
.rr-page-title { font-size: 22px; font-weight: 600; color: var(--ink); margin: 0 0 4px 0; }
.rr-page-sub { color: var(--ink-3); font-size: 13px; margin: 0; }
.rr-page-actions { display: flex; gap: 8px; }
.rr-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 16px; }
.rr-search { padding: 16px 20px; }
.rr-pagination { padding: 12px 20px; display: flex; justify-content: flex-end; border-top: 1px solid var(--border); }
.rr-muted { color: var(--ink-3); }
.rr-stock-ok { color: var(--primary); font-weight: 600; }
.rr-stock-out { color: var(--ink-3); }
</style>