|
@@ -42,36 +42,36 @@
|
|
|
<el-table :max-height="tableHeight" :data="levels" v-loading="levelsLoading" border stripe>
|
|
<el-table :max-height="tableHeight" :data="levels" v-loading="levelsLoading" border stripe>
|
|
|
<el-table-column prop="levelCode" label="等级编码" width="120">
|
|
<el-table-column prop="levelCode" label="等级编码" width="120">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag :type="levelTagType(row.levelCode)" size="mini">{{ row.levelCode }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="row" :type="levelTagType(row.levelCode)" size="mini">{{ row.levelCode }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="levelName" label="等级名称" width="150" />
|
|
<el-table-column prop="levelName" label="等级名称" width="150" />
|
|
|
<el-table-column prop="levelDesc" label="描述" min-width="180" show-overflow-tooltip />
|
|
<el-table-column prop="levelDesc" label="描述" min-width="180" show-overflow-tooltip />
|
|
|
<el-table-column label="月费(元)" width="100">
|
|
<el-table-column label="月费(元)" width="100">
|
|
|
- <template slot-scope="{ row }">{{ formatPrice(row.priceMonthly) }}</template>
|
|
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? formatPrice(row.priceMonthly) : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="季费(元)" width="100">
|
|
<el-table-column label="季费(元)" width="100">
|
|
|
- <template slot-scope="{ row }">{{ formatPrice(row.priceQuarterly) }}</template>
|
|
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? formatPrice(row.priceQuarterly) : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="年费(元)" width="100">
|
|
<el-table-column label="年费(元)" width="100">
|
|
|
- <template slot-scope="{ row }">{{ formatPrice(row.priceYearly) }}</template>
|
|
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? formatPrice(row.priceYearly) : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="maxChildren" label="孩子上限" width="90" />
|
|
<el-table-column prop="maxChildren" label="孩子上限" width="90" />
|
|
|
<el-table-column prop="maxTasksPerDay" label="每日任务" width="90" />
|
|
<el-table-column prop="maxTasksPerDay" label="每日任务" width="90" />
|
|
|
<el-table-column label="AI审核" width="80">
|
|
<el-table-column label="AI审核" width="80">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag :type="row.aiReviewEnabled ? 'success' : 'info'" size="mini">{{ row.aiReviewEnabled ? '是' : '否' }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="row" :type="row.aiReviewEnabled ? 'success' : 'info'" size="mini">{{ row.aiReviewEnabled ? '是' : '否' }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="优先客服" width="80">
|
|
<el-table-column label="优先客服" width="80">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag :type="row.prioritySupport ? 'success' : 'info'" size="mini">{{ row.prioritySupport ? '是' : '否' }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="row" :type="row.prioritySupport ? 'success' : 'info'" size="mini">{{ row.prioritySupport ? '是' : '否' }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-button size="mini" type="primary" @click="handleEditLevel(row)">编辑</el-button>
|
|
|
|
|
- <el-dropdown trigger="hover" @command="(cmd) => handleLevelCmd(row, cmd)">
|
|
|
|
|
|
|
+ <el-button v-if="row" size="mini" type="primary" @click="handleEditLevel(row)">编辑</el-button>
|
|
|
|
|
+ <el-dropdown v-if="row" trigger="hover" @command="(cmd) => handleLevelCmd(row, cmd)">
|
|
|
<el-button size="mini">
|
|
<el-button size="mini">
|
|
|
更多<i class="el-icon-arrow-down el-icon--right"></i>
|
|
更多<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
</el-button>
|
|
</el-button>
|
|
@@ -92,29 +92,29 @@
|
|
|
<el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe :max-height="tableHeight">
|
|
<el-table :data="upgradeRecords" v-loading="recordsLoading" border stripe :max-height="tableHeight">
|
|
|
<el-table-column prop="id" label="ID" width="70" />
|
|
<el-table-column prop="id" label="ID" width="70" />
|
|
|
<el-table-column label="用户" width="120">
|
|
<el-table-column label="用户" width="120">
|
|
|
- <template slot-scope="{ row }">{{ row.userName || '用户' + row.userId }}</template>
|
|
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? (row.userName || '用户' + row.userId) : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="从" width="100">
|
|
<el-table-column label="从" width="100">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag size="mini">{{ row.fromLevel || 'FREE' }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="row" size="mini">{{ row.fromLevel || 'FREE' }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="到" width="100">
|
|
<el-table-column label="到" width="100">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag type="success" size="mini">{{ row.toLevel }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="row" type="success" size="mini">{{ row.toLevel }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="upgradeType" label="升级方式" width="120">
|
|
<el-table-column prop="upgradeType" label="升级方式" width="120">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag :type="upgradeTypeTag(row.upgradeType)" size="mini">{{ upgradeTypeLabel(row.upgradeType) }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag v-if="row" :type="upgradeTypeTag(row.upgradeType)" size="mini">{{ upgradeTypeLabel(row.upgradeType) }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="orderId" label="订单ID" width="80" />
|
|
<el-table-column prop="orderId" label="订单ID" width="80" />
|
|
|
<el-table-column prop="expireTime" label="到期时间" width="160">
|
|
<el-table-column prop="expireTime" label="到期时间" width="160">
|
|
|
- <template slot-scope="{ row }">{{ formatTime(row.expireTime) }}</template>
|
|
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? formatTime(row.expireTime) : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="createdAt" label="创建时间" width="160">
|
|
<el-table-column prop="createdAt" label="创建时间" width="160">
|
|
|
- <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? formatTime(row.createdAt) : '—' }}</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
<el-pagination
|
|
<el-pagination
|
|
@@ -130,6 +130,71 @@
|
|
|
/>
|
|
/>
|
|
|
</el-card>
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
+ <!-- 会员维护 -->
|
|
|
|
|
+ <el-card style="margin-top:20px;">
|
|
|
|
|
+ <div slot="header">
|
|
|
|
|
+ <span>会员维护</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="maintain-row">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="maintainSearch"
|
|
|
|
|
+ placeholder="输入手机号/昵称/用户ID搜索"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width:280px;margin-right:10px;"
|
|
|
|
|
+ @keyup.enter.native="handleMaintainSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-button type="primary" size="small" @click="handleMaintainSearch">搜索</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="maintainUsers.length" style="margin-top:14px;">
|
|
|
|
|
+ <el-table :data="maintainUsers" border stripe size="small">
|
|
|
|
|
+ <el-table-column prop="id" label="ID" width="70" />
|
|
|
|
|
+ <el-table-column prop="nickname" label="昵称" width="140" />
|
|
|
|
|
+ <el-table-column prop="phone" label="手机号" width="140" />
|
|
|
|
|
+ <el-table-column label="当前等级" width="110">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-tag v-if="row" :type="levelTagType(row.memberLevel)" size="mini">{{ row.memberLevel || 'FREE' }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="推荐人ID" width="100">
|
|
|
|
|
+ <template slot-scope="{ row }">{{ row ? (row.referrerId || '—') : '—' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="160">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <el-button v-if="row" size="mini" type="warning" @click="openMaintainDialog(row)">维护</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 会员维护对话框 -->
|
|
|
|
|
+ <el-dialog title="会员维护" :visible.sync="maintainVisible" width="520px" @close="maintainVisible = false">
|
|
|
|
|
+ <el-form :model="maintainForm" label-width="100px" size="small">
|
|
|
|
|
+ <el-form-item label="用户">
|
|
|
|
|
+ <span>{{ maintainForm.nickname }}(ID: {{ maintainForm.id }})</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="会员等级">
|
|
|
|
|
+ <el-select v-model="maintainForm.memberLevel" style="width:200px;" placeholder="选择等级">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="lv in levels"
|
|
|
|
|
+ :key="lv.levelCode"
|
|
|
|
|
+ :label="lv.levelName + ' (' + lv.levelCode + ')'"
|
|
|
|
|
+ :value="lv.levelCode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <span style="color:#999;margin-left:8px;">选终身会员则永久有效</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="推荐人ID">
|
|
|
|
|
+ <el-input v-model="maintainForm.referrerIdStr" placeholder="留空不变,填0清除" style="width:200px;" />
|
|
|
|
|
+ <span style="color:#999;margin-left:8px;">填目标用户ID</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <div slot="footer">
|
|
|
|
|
+ <el-button @click="maintainVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="maintainSubmitting" @click="handleMaintainSubmit">保存</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
<!-- 权限详情对话框 -->
|
|
<!-- 权限详情对话框 -->
|
|
|
<el-dialog title="功能权限详情" :visible.sync="featuresVisible" width="500px">
|
|
<el-dialog title="功能权限详情" :visible.sync="featuresVisible" width="500px">
|
|
|
<div v-if="currentLevel" class="features-content">
|
|
<div v-if="currentLevel" class="features-content">
|
|
@@ -197,7 +262,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { searchUsers } from '@/api/admin'
|
|
|
|
|
|
|
+import { searchUsers, updateMembershipUser } from '@/api/admin'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
name: 'MembershipCenter',
|
|
name: 'MembershipCenter',
|
|
@@ -230,7 +295,18 @@ export default {
|
|
|
},
|
|
},
|
|
|
// dialogs
|
|
// dialogs
|
|
|
featuresVisible: false,
|
|
featuresVisible: false,
|
|
|
- currentLevel: null
|
|
|
|
|
|
|
+ currentLevel: null,
|
|
|
|
|
+ // membership maintain
|
|
|
|
|
+ maintainSearch: '',
|
|
|
|
|
+ maintainUsers: [],
|
|
|
|
|
+ maintainVisible: false,
|
|
|
|
|
+ maintainSubmitting: false,
|
|
|
|
|
+ maintainForm: {
|
|
|
|
|
+ id: null,
|
|
|
|
|
+ nickname: '',
|
|
|
|
|
+ memberLevel: '',
|
|
|
|
|
+ referrerIdStr: ''
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
created() {
|
|
created() {
|
|
@@ -295,7 +371,8 @@ export default {
|
|
|
try {
|
|
try {
|
|
|
const res = await this._request('/api/admin/membership/levels')
|
|
const res = await this._request('/api/admin/membership/levels')
|
|
|
if (res.data && res.data.code === 200) {
|
|
if (res.data && res.data.code === 200) {
|
|
|
- this.levels = res.data.data || []
|
|
|
|
|
|
|
+ const list = res.data.data || []
|
|
|
|
|
+ this.levels = Array.isArray(list) ? list.filter(function(l) { return l && typeof l === 'object' }) : []
|
|
|
}
|
|
}
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
this.$message && this.$message.error && this.$message.error('加载等级配置失败')
|
|
this.$message && this.$message.error && this.$message.error('加载等级配置失败')
|
|
@@ -311,7 +388,8 @@ export default {
|
|
|
size: this.recordFilter.size
|
|
size: this.recordFilter.size
|
|
|
})
|
|
})
|
|
|
if (res.data && res.data.code === 200) {
|
|
if (res.data && res.data.code === 200) {
|
|
|
- this.upgradeRecords = res.data.data.records || []
|
|
|
|
|
|
|
+ const records = (res.data.data && res.data.data.records) || []
|
|
|
|
|
+ this.upgradeRecords = Array.isArray(records) ? records.filter(function(r) { return r && typeof r === 'object' }) : []
|
|
|
this.recordsTotal = res.data.data.total || 0
|
|
this.recordsTotal = res.data.data.total || 0
|
|
|
await this.resolveUserNames(this.upgradeRecords, 'userId')
|
|
await this.resolveUserNames(this.upgradeRecords, 'userId')
|
|
|
}
|
|
}
|
|
@@ -366,6 +444,63 @@ if (res.data && res.data.code === 200) {
|
|
|
item.userName = nameMap[item[idField]] || '用户' + item[idField]
|
|
item.userName = nameMap[item[idField]] || '用户' + item[idField]
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
+ },
|
|
|
|
|
+ async handleMaintainSearch() {
|
|
|
|
|
+ const kw = (this.maintainSearch || '').trim()
|
|
|
|
|
+ if (!kw) {
|
|
|
|
|
+ this.$message && this.$message.warning && this.$message.warning('请输入搜索关键词')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await searchUsers({ keyword: kw })
|
|
|
|
|
+ if (res.data && res.data.length > 0) {
|
|
|
|
|
+ this.maintainUsers = res.data
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.maintainUsers = []
|
|
|
|
|
+ this.$message && this.$message.info && this.$message.info('未找到用户')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.$message && this.$message.error && this.$message.error('搜索失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ openMaintainDialog(row) {
|
|
|
|
|
+ this.maintainForm = {
|
|
|
|
|
+ id: row.id,
|
|
|
|
|
+ nickname: row.nickname || row.realName || ('用户' + row.id),
|
|
|
|
|
+ memberLevel: row.memberLevel || 'FREE',
|
|
|
|
|
+ referrerIdStr: row.referrerId ? String(row.referrerId) : ''
|
|
|
|
|
+ }
|
|
|
|
|
+ this.maintainVisible = true
|
|
|
|
|
+ },
|
|
|
|
|
+ async handleMaintainSubmit() {
|
|
|
|
|
+ if (!this.maintainForm.id) return
|
|
|
|
|
+ this.maintainSubmitting = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = { userId: this.maintainForm.id }
|
|
|
|
|
+ if (this.maintainForm.memberLevel) {
|
|
|
|
|
+ payload.memberLevel = this.maintainForm.memberLevel
|
|
|
|
|
+ }
|
|
|
|
|
+ const refStr = (this.maintainForm.referrerIdStr || '').trim()
|
|
|
|
|
+ if (refStr === '0') {
|
|
|
|
|
+ payload.referrerId = ''
|
|
|
|
|
+ } else if (refStr !== '') {
|
|
|
|
|
+ payload.referrerId = refStr
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await updateMembershipUser(payload)
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ this.$message && this.$message.success && this.$message.success(res.data || '更新成功')
|
|
|
|
|
+ this.maintainVisible = false
|
|
|
|
|
+ this.handleMaintainSearch()
|
|
|
|
|
+ this.loadOverview()
|
|
|
|
|
+ this.loadUpgradeRecords()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$message && this.$message.error && this.$message.error(res.message || '更新失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ this.$message && this.$message.error && this.$message.error('更新失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.maintainSubmitting = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|