admin_new/src/modules/other/seckill/SeckillListView.vue

148 lines
6.9 KiB
Vue

<script setup lang="ts">
/**
* F006 秒杀活动 - W10 A3 标准化
*
* W10 A3: 套 PageHeader/SearchBar 标准化模板
*
* 沿用老 vue other/seckill.vue 113 行:
* - 搜索: 活动名称
* - 表格列: name/enterpriseName/beginTime/endTime/type/status(0启用1停用)/createDate
* - 操作: 查看 / 启用停用 / 删除
* - 批量删除
* - /other/seckill 后端 404 → FALLBACK mock
*/
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'
interface Seckill {
id: string
name: string
enterpriseName: string
beginTime: string
endTime: string
type: string
status: number
createDate: string
raw: any
}
const STATUS_MAP: Record<number, { label: string; type: string }> = {
0: { label: '启用', type: 'success' },
1: { label: '停用', type: 'danger' }
}
const FALLBACK: Seckill[] = [
{ id: 'S001', name: '整点秒杀 - 12点档', enterpriseName: '深圳科技股份有限公司', beginTime: '2026-07-10 12:00:00', endTime: '2026-07-10 13:00:00', type: '螺纹钢 100 件', status: 0, createDate: '2026-07-01 10:00:00', raw: {} },
{ id: 'S002', name: '整点秒杀 - 18点档', enterpriseName: '上海制造企业', beginTime: '2026-07-10 18:00:00', endTime: '2026-07-10 19:00:00', type: '水泥 50 吨', status: 0, createDate: '2026-07-01 11:00:00', raw: {} },
{ id: 'S003', name: '周末限时秒杀', enterpriseName: '北京电子有限公司', beginTime: '2026-07-12 00:00:00', endTime: '2026-07-13 23:59:59', type: '热轧钢板 30 吨', status: 0, createDate: '2026-07-02 14:00:00', raw: {} },
{ id: 'S004', name: '夏季特惠秒杀', enterpriseName: '广州工厂', beginTime: '2026-06-15 10:00:00', endTime: '2026-06-15 12:00:00', type: '盘螺 50 吨', status: 1, createDate: '2026-06-10 09:00:00', raw: {} },
{ id: 'S005', name: '建材爆款秒杀', enterpriseName: '杭州电子', beginTime: '2026-07-15 20:00:00', endTime: '2026-07-15 22:00:00', type: '砂石料 200 吨', status: 0, createDate: '2026-07-03 16:20:00', raw: {} },
{ id: 'S006', name: '工友福利秒杀', enterpriseName: '深圳科技股份有限公司', beginTime: '2026-07-20 09:00:00', endTime: '2026-07-20 10:00:00', type: '五金工具套装', status: 0, createDate: '2026-07-04 11:00:00', raw: {} }
]
const list = ref<Seckill[]>([])
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<Seckill[]>([])
const viewVisible = ref(false)
const viewData = ref<Seckill | null>(null)
async function load() {
loading.value = true
error.value = null
try {
await new Promise(r => setTimeout(r, 200))
let rows = [...FALLBACK]
if (filters.name) rows = rows.filter(r => r.name.includes(filters.name))
list.value = rows
total.value = rows.length
} catch (e: any) {
error.value = e?.msg || e?.message || '加载失败'
list.value = FALLBACK
total.value = FALLBACK.length
} finally { loading.value = false }
}
function onSearch() { page.value = 1; load() }
function onReset() { filters.name = ''; load() }
function onPageChange(p: number) { page.value = p; load() }
function onLimitChange(l: number) { limit.value = l; load() }
function onSelectionChange(rows: Seckill[]) { selection.value = rows }
function onView(row: Seckill) { viewData.value = row; viewVisible.value = true }
function onToggleStatus(row: Seckill) {
const newStatus = row.status === 0 ? 1 : 0
ElMessageBox.confirm(`确认${newStatus === 0 ? '启用' : '停用'}「${row.name}」?`, '提示', { type: 'warning' }).then(async () => {
try { await httpPost('/other/seckill/updateSeckill', { id: row.id, status: newStatus }); ElMessage.success(`${newStatus === 0 ? '启用' : '停用'}成功`); load() }
catch (e: any) { ElMessage.error(e?.msg || '操作失败') }
}).catch(() => {})
}
function onDelete(row: Seckill) {
ElMessageBox.confirm(`确认删除「${row.name}」?`, '提示', { type: 'warning' }).then(async () => {
try { await httpPost('/other/seckill/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('/other/seckill/delete', selection.value.map(s => s.id)); ElMessage.success(`已批量删除 ${selection.value.length} 个`); load() }
catch (e: any) { ElMessage.error(e?.msg || '批量删除失败') }
}).catch(() => {})
}
onMounted(load)
</script>
<template>
<div class="rr-page">
<PageHeader title="秒杀活动" :sub="`限时秒杀活动管理 (共 ${total} 个)`">
<el-button type="danger" :disabled="selection.length === 0" @click="onBatchDelete">批量删除 ({{ selection.length }})</el-button>
</PageHeader>
<SearchBar
v-model:filters="filters"
:fields="[
{ key: 'name', label: '活动名称', type: 'text', placeholder: '请输入活动名称' }
]"
@search="onSearch"
@reset="onReset"
/>
<div class="rr-card">
<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="viewVisible" title="活动详情" width="640px">
<el-descriptions v-if="viewData" :column="2" border>
<el-descriptions-item label="活动名称">{{ viewData.name }}</el-descriptions-item>
<el-descriptions-item label="活动商家">{{ viewData.enterpriseName }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ viewData.beginTime }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ viewData.endTime }}</el-descriptions-item>
<el-descriptions-item label="活动商品" :span="2">{{ viewData.type }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="(STATUS_MAP[viewData.status]?.type as any)" size="small">{{ STATUS_MAP[viewData.status]?.label }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ viewData.createDate }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="viewVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.rr-page { max-width: 1400px; }
.rr-pagination { padding: 12px 20px; display: flex; justify-content: flex-end; border-top: 1px solid var(--border); }
</style>