问题: 路由 /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%)
234 lines
10 KiB
Vue
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>
|