| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- <template>
- <div class="activities admin-page">
- <el-card>
- <div slot="header" class="admin-page-header">
- <span class="admin-page-title">活动管理</span>
- <div>
- <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
- <el-select
- v-model="filter.status"
- placeholder="状态筛选"
- size="mini"
- clearable
- @change="loadActivities"
- >
- <el-option label="全部状态" value="" />
- <el-option label="草稿" value="draft" />
- <el-option label="已发布" value="published" />
- <el-option label="已结束" value="ended" />
- </el-select>
- <el-button type="primary" size="mini" @click="$router.push('/activity-edit')">创建活动</el-button>
- </div>
- </div>
- <div class="table-scroll-wrap-lg">
- <div style="overflow:auto;max-height:calc(100vh - 300px);">
- <el-table style="max-height:calc(100vh - 300px);" :data="activities" v-loading="loading" border stripe>
- <el-table-column prop="id" label="ID" width="80" />
- <el-table-column label="封面" width="80">
- <template slot-scope="{ row }">
- <img v-if="row.coverImage" :src="row.coverImage" style="width:40px;height:40px;object-fit:cover;border-radius:4px;" />
- <span v-else style="color:#999">无</span>
- </template>
- </el-table-column>
- <el-table-column prop="title" label="活动标题" min-width="160" show-overflow-tooltip />
- <el-table-column prop="dimensionCode" label="维度" width="100">
- <template slot-scope="{ row }">{{ dimensionLabel(row.dimensionCode) }}</template>
- </el-table-column>
- <el-table-column label="类型" width="90">
- <template slot-scope="{ row }">{{ activityTypeLabel(row.activityType) }}</template>
- </el-table-column>
- <el-table-column prop="location" label="地点" width="120" show-overflow-tooltip />
- <el-table-column label="费用(元)" width="90">
- <template slot-scope="{ row }">{{ row.price ? (row.price / 100).toFixed(2) : '免费' }}</template>
- </el-table-column>
- <el-table-column label="人数" width="90">
- <template slot-scope="{ row }">
- <el-button type="text" size="mini" @click="showRegistrations(row)" style="padding:0;">
- {{ row.currentParticipants || 0 }}/{{ row.maxParticipants || '不限' }}
- </el-button>
- </template>
- </el-table-column>
- <el-table-column label="状态" width="100">
- <template slot-scope="{ row }">
- <el-tag :type="statusType(row.status)" size="mini">
- {{ statusLabel(row.status) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="startTime" label="开始时间" width="170" />
- <el-table-column prop="endTime" label="结束时间" width="170" />
- <el-table-column label="操作" width="200" fixed="right">
- <template slot-scope="{ row }">
- <el-button size="mini" type="primary" @click="$router.push('/activity-edit?id=' + row.id)">编辑</el-button>
- <el-dropdown trigger="hover" @command="(cmd) => handleMoreCmd(row, cmd)">
- <el-button size="mini">
- 更多<i class="el-icon-arrow-down el-icon--right"></i>
- </el-button>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item v-if="row.status !== 'draft'" command="view" icon="el-icon-view">查看</el-dropdown-item>
- <el-dropdown-item v-if="row.status === 'draft'" command="publish" icon="el-icon-upload2">发布</el-dropdown-item>
- <el-dropdown-item v-if="row.status === 'published'" command="end" icon="el-icon-circle-close">结束</el-dropdown-item>
- <el-dropdown-item command="registrations" icon="el-icon-document">报名列表</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </template>
- </el-table-column>
- </el-table></div>
- </div>
- <el-pagination
- v-if="total > 0"
- @size-change="handleSizeChange"
- @current-change="handlePageChange"
- :current-page="filter.page"
- :page-sizes="[10, 20, 50]"
- :page-size="filter.size"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total"
- class="pagination-wrap"
- />
- </el-card>
- <!-- 报名列表对话框(保留) -->
- <el-dialog
- :title="'报名列表 - ' + (currentActivity ? currentActivity.title : '')"
- :visible.sync="regDialogVisible"
- width="800px"
- @closed="regDialogVisible = false"
- >
- <el-table :data="registrations" v-loading="regLoading" border stripe>
- <el-table-column prop="id" label="ID" width="60" />
- <el-table-column prop="userId" label="用户ID" width="70" />
- <el-table-column prop="childId" label="孩子ID" width="70" />
- <el-table-column label="状态" width="90">
- <template slot-scope="{ row }">
- <el-tag :type="row.status === 'approved' ? 'success' : row.status === 'rejected' ? 'danger' : 'warning'">
- {{ row.status === 'approved' ? '已通过' : row.status === 'rejected' ? '已拒绝' : '待审核' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="registeredAt" label="报名时间" width="160">
- <template slot-scope="{ row }">
- {{ formatTime(row.registeredAt || row.createdAt) }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="200" fixed="right">
- <template slot-scope="{ row }">
- <el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleRegApprove(row)">通过</el-button>
- <el-button v-if="row.status === 'pending'" type="danger" size="small" @click="handleRegReject(row)">拒绝</el-button>
- <span v-else style="color: #999;">已处理</span>
- </template>
- </el-table-column>
- </el-table>
- <p v-if="!registrations.length && !regLoading" style="text-align:center;color:#999;">暂无报名记录</p>
- </el-dialog>
- </div>
- </template>
- <script>
- import { getActivityList, publishActivity, endActivity, getRegistrationList, approveRegistration, rejectRegistration } from '@/api/activity'
- const STATUS_MAP = {
- draft: { label: '草稿', type: 'info' },
- published: { label: '已发布', type: 'success' },
- ended: { label: '已结束', type: 'danger' }
- }
- const ACTIVITY_TYPE_MAP = {
- offline: '线下活动',
- online: '线上活动',
- campaign: '主题活动'
- }
- const DIMENSION_MAP = {
- body: '身',
- mind: '智',
- action: '行',
- wealth: '富',
- heart: '心'
- }
- export default {
- name: 'Activities',
- data() {
- return {
- loading: false,
- activities: [],
- total: 0,
- keyword: '',
- filter: {
- status: '',
- page: 1,
- size: 10
- },
- // 报名列表对话框
- regDialogVisible: false,
- regLoading: false,
- currentActivity: null,
- registrations: []
- }
- },
- created() {
- this.loadActivities()
- },
- methods: {
- statusType(status) {
- return (STATUS_MAP[status] && STATUS_MAP[status].type) || 'info'
- },
- statusLabel(status) {
- return (STATUS_MAP[status] && STATUS_MAP[status].label) || status
- },
- activityTypeLabel(type) {
- return ACTIVITY_TYPE_MAP[type] || type || '—'
- },
- dimensionLabel(code) {
- return DIMENSION_MAP[code] || code || '—'
- },
- async loadActivities() {
- this.loading = true
- try {
- const params = {
- page: this.filter.page,
- size: this.filter.size
- }
- if (this.filter.status) {
- params.status = this.filter.status
- }
- if (this.keyword) {
- params.keyword = this.keyword
- }
- const res = await getActivityList(params)
- this.activities = (res.data && res.data.records) || []
- this.total = (res.data && res.data.total) || 0
- } catch (e) {
- this.$message && this.$message.error && this.$message.error('加载活动列表失败')
- } finally {
- this.loading = false
- }
- },
- handleSizeChange(size) {
- this.filter.size = size
- this.filter.page = 1
- this.loadActivities()
- },
- handlePageChange(page) {
- this.filter.page = page
- this.loadActivities()
- },
- async handleMoreCmd(row, cmd) {
- switch (cmd) {
- case 'view':
- this.$router.push('/activity-edit?id=' + row.id + '&readonly=1')
- break
- case 'publish':
- try {
- await this.$confirm('确认发布该活动?', '提示')
- await publishActivity(row.id)
- this.$message.success('发布成功')
- this.loadActivities()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error('发布失败')
- }
- }
- break
- case 'end':
- try {
- await this.$confirm('确认结束该活动?该操作不可逆', '提示')
- await endActivity(row.id)
- this.$message.success('活动已结束')
- this.loadActivities()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error('操作失败')
- }
- }
- break
- case 'registrations':
- this.showRegistrations(row)
- break
- }
- },
- handleSearch() {
- this.filter.page = 1
- this.loadActivities()
- },
- async handlePublish(row) {
- try {
- await this.$confirm('确认发布该活动?', '提示')
- await publishActivity(row.id)
- this.$message.success('发布成功')
- this.loadActivities()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error('发布失败')
- }
- }
- },
- async handleEnd(row) {
- try {
- await this.$confirm('确认结束该活动?该操作不可逆', '提示')
- await endActivity(row.id)
- this.$message.success('活动已结束')
- this.loadActivities()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error('操作失败')
- }
- }
- },
- // ===== 报名列表 =====
- showRegistrations(row) {
- this.currentActivity = row
- this.regDialogVisible = true
- this.loadRegistrations(row.id)
- },
- async loadRegistrations(activityId) {
- this.regLoading = true
- try {
- const res = await getRegistrationList({ activityId })
- if (res.code === 200) {
- this.registrations = res.data || []
- }
- } catch (e) {
- this.$message.error('加载报名列表失败')
- } finally {
- this.regLoading = false
- }
- },
- formatTime(t) {
- if (!t) return '-'
- return t.replace ? t.replace('T', ' ').substring(0, 19) : t
- },
- async handleRegApprove(row) {
- try {
- const res = await approveRegistration({ registrationId: row.id })
- if (res.code === 200) {
- this.$message.success('已通过')
- this.loadRegistrations(this.currentActivity.id)
- } else {
- this.$message.error(res.message || '操作失败')
- }
- } catch (e) {
- this.$message.error('操作失败')
- }
- },
- async handleRegReject(row) {
- try {
- const res = await rejectRegistration({ registrationId: row.id })
- if (res.code === 200) {
- this.$message.success('已拒绝')
- this.loadRegistrations(this.currentActivity.id)
- } else {
- this.$message.error(res.message || '操作失败')
- }
- } catch (e) {
- this.$message.error('操作失败')
- }
- }
- }
- }
- </script>
- <style scoped>
- .activities { padding: 20px; flex: 1; display: flex; flex-direction: column; min-height: 0; }
- .activities > .el-card { flex: 1; display: flex; flex-direction: column; min-height: 0; }
- .activities > .el-card >>> .el-card__body { flex: 1; display: flex; flex-direction: column; min-height: 0; overflow-y: auto; }
- .activities >>> .el-table { flex: 1; }
- .activities >>> .el-table__body-wrapper { overflow-y: auto; }
- </style>
|