admin_new/src/modules/supplier/coupon/CouponListView.vue

262 lines
12 KiB
Vue

<script setup lang="ts">
/**
* F006 优惠券管理 - W10 A4 标准化
*
* W10 A4: 套 PageHeader/SearchBar 标准化模板 (沿用老 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, httpPost } from '@/utils/request'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
import TableActions from '@/components/TableActions/TableActions.vue'
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 }
const editVisible = ref(false)
const editMode = ref<'add' | 'edit'>('add')
const editForm = reactive<any>({ name: '', type: 1, fullMoney: 0, deductedMoney: 0, quantity: 0, rebate: 1, limitCollar: 1, expirationDate: '' })
function onAdd() {
editMode.value = 'add'
Object.assign(editForm, { id: '', name: '', type: 1, fullMoney: 0, deductedMoney: 0, quantity: 0, rebate: 1, limitCollar: 1, expirationDate: '' })
editVisible.value = true
}
function onEdit(row: Coupon) {
if (row.sourceType !== 2) { ElMessage.warning('只有平台券可编辑'); return }
editMode.value = 'edit'
Object.assign(editForm, JSON.parse(JSON.stringify(row)))
editVisible.value = true
}
function onSubmitEdit() {
if (!editForm.name?.trim()) { ElMessage.warning('请填写券名称'); return }
const ep = editMode.value === 'add' ? '/supplier/coupon/saveCoupon' : '/supplier/coupon/updateCoupon'
httpPost(ep, { ...editForm, sourceType: 2 }).then(() => {
ElMessage.success(editMode.value === 'add' ? '新增成功' : '修改成功'); editVisible.value = false; load()
}).catch((e: any) => ElMessage.error(e?.msg || '操作失败'))
}
function onToggleStatus(row: Coupon) {
const next = row.onShelf === 1 ? 0 : 1
ElMessageBox.confirm(`确认${next === 1 ? '启用' : '停用'}「${row.name}」?`, '提示', { type: 'warning' }).then(async () => {
try { await httpPost('/supplier/coupon/updateCoupon', { id: row.id, onShelf: next }); ElMessage.success(`${next === 1 ? '启用' : '停用'}成功`); load() }
catch (e: any) { ElMessage.error(e?.msg || '操作失败') }
}).catch(() => {})
}
function onDelete(row: Coupon) {
ElMessageBox.confirm(`确认删除「${row.name}」?`, '提示', { type: 'warning' }).then(async () => {
try { await httpPost('/supplier/coupon/delete', { id: row.id }); ElMessage.success('已删除'); load() }
catch (e: any) { ElMessage.error(e?.msg || '删除失败') }
}).catch(() => {})
}
function onBatchDelete() {
if (selection.value.length === 0) { ElMessage.warning('请先选择要删除的券'); return }
ElMessageBox.confirm(`确认批量删除选中的 ${selection.value.length} 张券?`, '提示', { type: 'warning' }).then(async () => {
try { await httpPost('/supplier/coupon/delete', selection.value.map(s => s.id)); ElMessage.success(`已批量删除 ${selection.value.length} 张`); load() }
catch (e: any) { ElMessage.error(e?.msg || '批量删除失败') }
}).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">
<PageHeader title="优惠券管理" :sub="`平台/企业优惠券管理 (共 ${total} 张, 选中 ${selection.length})`">
<el-button type="primary" @click="onAdd">新增优惠券</el-button>
<el-button type="danger" :disabled="selection.length === 0" @click="onBatchDelete">批量删除</el-button>
</PageHeader>
<SearchBar
v-model:filters="filters"
:fields="[
{ key: 'name', label: '优惠券名称', type: 'text', placeholder: '请输入优惠券名称' }
]"
@search="onSearch"
@reset="onReset"
/>
<div class="rr-card">
<el-table :data="list" border style="width: 100%" v-loading="loading" :empty-text="error || '暂无优惠券'" @selection-change="onSelectionChange" row-key="id" :header-cell-style="{ textAlign: 'center' }">
<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="140" align="center" fixed="right">
<template #default="{ row }">
<TableActions
:row="row"
:actions="[
{ label: '编辑', type: 'primary', when: (r: any) => r.sourceType === 2, onClick: onEdit },
{ label: (r: any) => r.onShelf === 1 ? '停用' : '启用', type: (r: any) => r.onShelf === 1 ? 'warning' : 'success', onClick: onToggleStatus },
{ label: '领取记录', type: 'primary', when: (r: any) => r.sourceType === 2, onClick: onShowRecords },
{ label: '删除', type: 'danger', divided: true, onClick: onDelete }
]"
/>
</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>
<el-dialog v-model="editVisible" :title="editMode === 'add' ? '新增优惠券' : '编辑优惠券'" width="520px">
<el-form label-width="100px">
<el-form-item label="券名称" required><el-input v-model="editForm.name" placeholder="如: 满100减20" /></el-form-item>
<el-form-item label="类型">
<el-radio-group v-model="editForm.type">
<el-radio :value="1">满减券</el-radio>
<el-radio :value="2">折扣券</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="满"><el-input-number v-model="editForm.fullMoney" :min="0" :precision="2" /></el-form-item>
<el-form-item label="减/折"><el-input-number v-model="editForm.deductedMoney" :min="0" :precision="2" /></el-form-item>
<el-form-item label="发放数量"><el-input-number v-model="editForm.quantity" :min="0" /></el-form-item>
<el-form-item label="限领"><el-input-number v-model="editForm.limitCollar" :min="1" /></el-form-item>
<el-form-item label="到期日期"><el-input v-model="editForm.expirationDate" placeholder="如: 2026-12-31" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="editVisible = false">取消</el-button>
<el-button type="primary" @click="onSubmitEdit">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.rr-page { max-width: 1400px; }
</style>