admin_new/src/modules/supplier/ActivityListView.vue
Sisyphus 4b77d67dc2 fix(F006-W17.3.7): list 页统一规范 (:borderless + :page-sizes) + product/shop redirect
- 33 个 ListView.vue 加 :borderless=true + :page-sizes=[10,20,50,100]
  覆盖: order/inquiry/finance/operateguide/sys/Dept|Menu|File|Notice|
        Params|User|Role|Post|DictType|DictData|Product|Schedule|RoleGroup
        user/Enterprise|enterpriselevel|enterprisescore|enterpriserating
        supplier/promoter|couponscope|historylog|logistics|ordercomment|Activity
        other/goodunit|label|textconfig
        api/feedback|help|lunbo
        openapi/AuditLog|CompanyAccount
- OrderListView: columns 抽到 script setup (computed) + :borderless +
  :page-sizes + sub 动态显示选中数
- router/index.ts: /product/shop → redirect /supplier/good
  (删 src/modules/product/shop/ShopListView.vue 旧占位, 后端 404 无意义)
- 沿用 W17 设计规范 v2 (PageHeader+SearchBar+DataTable+ListTabs)
2026-07-07 16:54:37 +08:00

191 lines
7.0 KiB
Vue

<script setup lang="ts">
/**
* F006 营销活动管理 - W10 A7 标准化
*
* W10 A7: 套 PageHeader/SearchBar 标准化模板
*
* 后端端点 (ActivityZoneController @ /supplier/activityzone):
* GET /supplier/activityzone/page 分页列表
* GET /supplier/activityzone/{id} 详情
*/
import { onMounted, ref, reactive } from 'vue'
import { httpGet } from '@/utils/request'
import DataTable from '@/components/DataTable/DataTable.vue'
import PageHeader from '@/components/PageHeader/PageHeader.vue'
import SearchBar from '@/components/SearchBar/SearchBar.vue'
interface Activity {
id: string
activityName: string
activityType: number
status: number
startTime: string
endTime: string
createDate: string
creatorName: string
raw: any
}
const ACTIVITY_TYPE_MAP: Record<number, { label: string; type: string }> = {
1: { label: '秒杀', type: 'danger' },
2: { label: '拼团', type: 'warning' },
3: { label: '限时折扣', type: 'success' }
}
const STATUS_MAP: Record<number, { label: string; type: string }> = {
0: { label: '关闭', type: 'info' },
1: { label: '开启', type: 'success' }
}
const list = ref<Activity[]>([])
const total = ref(0)
const loading = ref(false)
const page = ref(1)
const limit = ref(10)
const loadError = ref<string | null>(null)
const filters = reactive({
activityName: '',
status: ''
})
const detailVisible = ref(false)
const detailRow = ref<Activity | null>(null)
function fmtDate(ts?: string | number | null) {
if (!ts) return '--'
const n = typeof ts === 'string' ? Date.parse(ts) : ts
if (!n) return String(ts)
const d = new Date(n)
if (isNaN(d.getTime())) return String(ts)
const pad = (x: number) => String(x).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}
async function load() {
loading.value = true
loadError.value = null
try {
const data = await httpGet<{ total: number; list: any[] }>('/supplier/activityzone/page', {
page: page.value,
limit: limit.value,
...(filters.activityName ? { activityName: filters.activityName } : {}),
...(filters.status !== '' ? { status: Number(filters.status) } : {})
})
list.value = (data.list || []).map((it: any) => ({
id: String(it.id),
activityName: it.activityName || '--',
activityType: it.activityType ?? 0,
status: it.status ?? 0,
startTime: it.startTime || '--',
endTime: it.endTime || '--',
createDate: it.createDate || '--',
creatorName: it.creatorName || '--',
raw: it
}))
total.value = data.total || 0
} catch (e: any) {
loadError.value = e?.msg || e?.message || '加载失败'
} finally {
loading.value = false
}
}
function onSearch() { page.value = 1; load() }
function onReset() { filters.activityName = ''; filters.status = ''; page.value = 1; load() }
function onPageChange(p: number) { page.value = p; load() }
function onLimitChange(l: number) { limit.value = l; page.value = 1; load() }
function showDetail(row: Activity) { detailRow.value = row; detailVisible.value = true }
onMounted(() => { load() })
</script>
<template>
<div class="rr-sys-user">
<PageHeader title="营销活动" :sub="`共 ${total} 个活动`" />
<SearchBar
v-model:filters="filters"
:fields="[
{ key: 'activityName', label: '活动名', type: 'text', placeholder: '活动名搜索' },
{ key: 'status', label: '状态', type: 'select', options: [
{ value: 1, label: '开启' },
{ value: 0, label: '关闭' }
] }
]"
@search="onSearch"
@reset="onReset"
/>
<div class="rr-list-card">
<DataTable
:columns="[
{ prop: 'activityName', label: '活动名称', width: 220 },
{ prop: 'activityType', label: '类型', width: 110, slot: 'activityType' },
{ prop: 'status', label: '状态', width: 90, slot: 'status' },
{ prop: 'creatorName', label: '创建人', width: 120 },
{ prop: 'startTime', label: '开始时间', width: 170, slot: 'startTime' },
{ prop: 'endTime', label: '结束时间', width: 170, slot: 'endTime' },
{ label: '操作', width: 100, slot: 'action' }
]"
:data="list" :borderless="true"
:loading="loading"
:total="total"
:page="page"
:limit="limit"
:show-pagination="true" :page-sizes="[10, 20, 50, 100]"
:error="loadError"
empty-text="暂无活动"
row-key="id"
@page-change="onPageChange"
@limit-change="onLimitChange"
>
<template #activityType="{ row }">
<el-tag v-if="ACTIVITY_TYPE_MAP[row.activityType]" :type="ACTIVITY_TYPE_MAP[row.activityType].type as any" size="small" effect="light">
{{ ACTIVITY_TYPE_MAP[row.activityType].label }}
</el-tag>
<span v-else class="rr-muted">--</span>
</template>
<template #status="{ row }">
<el-tag v-if="STATUS_MAP[row.status]" :type="STATUS_MAP[row.status].type as any" size="small" effect="light">
{{ STATUS_MAP[row.status].label }}
</el-tag>
<span v-else class="rr-muted">--</span>
</template>
<template #startTime="{ row }">
<span class="rr-time">{{ fmtDate(row.startTime) }}</span>
</template>
<template #endTime="{ row }">
<span class="rr-time">{{ fmtDate(row.endTime) }}</span>
</template>
<template #action="{ row }">
<el-button text type="primary" size="small" @click="showDetail(row)">详情</el-button>
</template>
</DataTable>
</div>
<el-dialog v-model="detailVisible" title="活动详情" width="600" :style="{ borderRadius: '12px', overflow: 'hidden' }" align-center destroy-on-close>
<template v-if="detailRow">
<el-descriptions :column="2" border size="default">
<el-descriptions-item label="活动名称" :span="2">{{ detailRow.activityName }}</el-descriptions-item>
<el-descriptions-item label="类型">{{ ACTIVITY_TYPE_MAP[detailRow.activityType]?.label || '--' }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ STATUS_MAP[detailRow.status]?.label || '--' }}</el-descriptions-item>
<el-descriptions-item label="创建人">{{ detailRow.creatorName }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ fmtDate(detailRow.startTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ fmtDate(detailRow.endTime) }}</el-descriptions-item>
</el-descriptions>
</template>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.rr-sys-user { padding: 16px; }
.rr-list-card { background: var(--surface); border-radius: 12px; padding: 4px; }
.rr-time { font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace; font-size: 12px; color: var(--ink-2); }
.rr-muted { color: var(--ink-3); font-size: 12px; }
</style>