Files
Vue3/src/views/user/PlayerDetail.vue
XingQue 6e7e775c55 feat: 打手详情赠送会员 UI,并改善后台错误提示
新增赠送会员送/改期/移除;请求失败优先展示后端 msg;考核官添加防重复提交。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 02:26:09 +08:00

1098 lines
34 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="player-detail">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<el-skeleton :rows="8" animated />
</div>
<div v-else class="detail-content">
<!-- 顶部导航栏 -->
<div class="detail-header">
<div class="header-left">
<el-button type="primary" :icon="ArrowLeft" @click="goBack">返回</el-button>
<span class="page-title">打手详情</span>
</div>
<div class="header-right">
<el-button
v-if="!isEditMode"
type="warning"
:icon="Edit"
@click="toggleEditMode"
:disabled="!playerInfo.yonghuid"
>编辑</el-button>
<template v-else>
<el-button type="success" :icon="Check" @click="saveChanges" :loading="saving">保存</el-button>
<el-button :icon="Close" @click="cancelEdit">取消</el-button>
</template>
</div>
</div>
<!-- 主要内容区域 -->
<el-row :gutter="24">
<!-- 左侧基础信息卡片 + 统计卡片 -->
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
<!-- 基础信息卡片 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span>👤 基础信息</span>
</div>
</template>
<div class="base-info">
<div class="avatar-wrapper">
<el-avatar :size="100" :src="getFullImageUrl(playerInfo.avatar)" fit="cover">
<el-icon><Avatar /></el-icon>
</el-avatar>
<div class="status-badges">
<el-tag
:type="playerInfo.zaixianzhuangtai === 1 ? 'success' : 'info'"
size="small"
effect="dark"
>
{{ playerInfo.zaixianzhuangtai === 1 ? '在线' : '离线' }}
</el-tag>
<el-tag
:type="playerInfo.zhanghaozhuangtai === 1 ? 'success' : 'danger'"
size="small"
effect="dark"
>
{{ playerInfo.zhanghaozhuangtai === 1 ? '正常' : '封禁' }}
</el-tag>
<el-tag
:type="playerInfo.zhuangtai === 1 ? 'success' : 'warning'"
size="small"
effect="dark"
>
{{ playerInfo.zhuangtai === 1 ? '空闲' : '接单中' }}
</el-tag>
</div>
</div>
<el-descriptions :column="1" border>
<el-descriptions-item label="UID">
<span class="copyable" @click="copyText(playerInfo.yonghuid)">
{{ playerInfo.yonghuid || '--' }} <el-icon><CopyDocument /></el-icon>
</span>
</el-descriptions-item>
<el-descriptions-item label="昵称">{{ playerInfo.nicheng || '--' }}</el-descriptions-item>
<el-descriptions-item label="称号">
<div class="chenghao-wrapper">
<span>{{ playerInfo.chenghao || '无' }}</span>
<!-- 金牌打手按钮非编辑模式下显示 -->
<el-button
v-if="!isEditMode"
size="small"
:type="isGoldTitle ? 'danger' : 'primary'"
@click="handleChenghaoChange"
:disabled="!hasChenghaoPermission"
>
{{ isGoldTitle ? '降级为普通打手' : '升级为金牌选手' }}
</el-button>
</div>
</el-descriptions-item>
<el-descriptions-item label="电话">{{ playerInfo.dianhua || '--' }}</el-descriptions-item>
<el-descriptions-item label="微信号">{{ playerInfo.wechat || '--' }}</el-descriptions-item>
<el-descriptions-item label="手机号">{{ playerInfo.phone || '--' }}</el-descriptions-item>
<el-descriptions-item label="注册时间">{{ formatTime(playerInfo.create_time) }}</el-descriptions-item>
<el-descriptions-item label="IP地址">
<span class="copyable" @click="copyText(playerInfo.ip)">
{{ playerInfo.ip || '--' }} <el-icon><CopyDocument /></el-icon>
</span>
</el-descriptions-item>
</el-descriptions>
</div>
</el-card>
<!-- 统计卡片保持不变 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span>📊 数据统计</span>
</div>
</template>
<el-row :gutter="16">
<el-col :span="12">
<div class="stat-item">
<div class="stat-label">接单总量</div>
<div class="stat-value">{{ playerInfo.jiedanzongliang || 0 }}</div>
</div>
</el-col>
<el-col :span="12">
<div class="stat-item">
<div class="stat-label">成交总量</div>
<div class="stat-value success">{{ playerInfo.chengjiaozongliang || 0 }}</div>
</div>
</el-col>
<el-col :span="12">
<div class="stat-item">
<div class="stat-label">退款总量</div>
<div class="stat-value danger">{{ playerInfo.tuikuanliang || 0 }}</div>
</div>
</el-col>
<el-col :span="12">
<div class="stat-item">
<div class="stat-label">今日接单</div>
<div class="stat-value">{{ playerInfo.jinrijiedan || 0 }}</div>
</div>
</el-col>
</el-row>
<el-divider />
<el-row :gutter="16" align="middle">
<el-col :span="12">
<div class="stat-item">
<div class="stat-label">成交率</div>
<el-progress
type="circle"
:percentage="completionRate"
:width="80"
:stroke-width="6"
status="success"
/>
</div>
</el-col>
<el-col :span="12">
<div class="stat-percent">{{ completionRate }}%</div>
</el-col>
</el-row>
<el-row :gutter="16" align="middle">
<el-col :span="12">
<div class="stat-item">
<div class="stat-label">退款率</div>
<el-progress
type="circle"
:percentage="refundRate"
:width="80"
:stroke-width="6"
status="warning"
/>
</div>
</el-col>
<el-col :span="12">
<div class="stat-percent">{{ refundRate }}%</div>
</el-col>
</el-row>
</el-card>
</el-col>
<!-- 右侧详细信息和可编辑字段 -->
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
<!-- 财务信息卡片 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span>💰 财务信息</span>
</div>
</template>
<el-form :model="editData" label-width="120px" :disabled="!isEditMode">
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="可提现余额">
<el-input-number
v-if="isEditMode"
v-model="editData.yue"
:precision="2"
:min="0"
:step="10"
style="width: 100%"
/>
<span v-else>¥{{ formatPrice(playerInfo.yue) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="赚取总额">
<span>¥{{ formatPrice(playerInfo.zonge) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="积分">
<el-input-number
v-if="isEditMode"
v-model="editData.jifen"
:min="0"
:step="10"
style="width: 100%"
/>
<span v-else>{{ playerInfo.jifen || 0 }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="押金">
<el-input-number
v-if="isEditMode"
v-model="editData.yajin"
:precision="2"
:min="0"
:step="10"
style="width: 100%"
/>
<span v-else>¥{{ formatPrice(playerInfo.yajin) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="今日收益">
<span>¥{{ formatPrice(playerInfo.jinrishouyi) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="今月收益">
<span>¥{{ formatPrice(playerInfo.jinyueshouyi) }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 状态信息卡片 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span> 状态管理</span>
</div>
</template>
<el-form :model="isEditMode ? editData : playerInfo" label-width="120px">
<el-row :gutter="24">
<el-col :span="8">
<el-form-item label="在线状态">
<el-select
v-if="isEditMode"
v-model="editData.zaixianzhuangtai"
style="width: 100%"
>
<el-option :value="1" label="在线" />
<el-option :value="0" label="离线" />
</el-select>
<span v-else>{{ playerInfo.zaixianzhuangtai === 1 ? '在线' : '离线' }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="账号状态">
<el-select
v-if="isEditMode"
v-model="editData.zhanghaozhuangtai"
style="width: 100%"
>
<el-option :value="1" label="正常" />
<el-option :value="0" label="封禁" />
</el-select>
<span v-else>{{ playerInfo.zhanghaozhuangtai === 1 ? '正常' : '封禁' }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="工作状态">
<el-select
v-if="isEditMode"
v-model="editData.zhuangtai"
style="width: 100%"
>
<el-option :value="1" label="空闲" />
<el-option :value="0" label="接单中" />
</el-select>
<span v-else>{{ playerInfo.zhuangtai === 1 ? '空闲' : '接单中' }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 会员信息卡片只读+操作按钮 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span>🎫 会员信息</span>
<el-button
type="primary"
size="small"
@click="openMemberDialog"
:disabled="!hasMemberPermission"
>添加会员</el-button>
</div>
<div v-if="!hasMemberPermission" class="member-hint warn">需要 001ff 或超级管理权限</div>
<div v-else class="member-hint">添加/移除会员请在弹窗点确认与顶部保存无关</div>
</template>
<div class="member-list">
<div v-for="member in memberList" :key="member.id" class="member-item">
<el-tag :type="member.is_active ? 'success' : 'info'" effect="plain">
{{ member.huiyuan_name }} 到期: {{ formatTime(member.daoqi_time) }}
</el-tag>
<el-button
type="danger"
size="small"
link
@click="removeMember(member)"
:disabled="!hasMemberPermission"
>移除</el-button>
</div>
<div v-if="memberList.length === 0" class="empty-message">暂无会员点击上方按钮添加</div>
</div>
</el-card>
<!-- 赠送会员免费接单独立表 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span>🎁 赠送会员免费接单</span>
<el-button
type="success"
size="small"
@click="openGiftDialog()"
:disabled="!hasGiftPermission"
>送会员</el-button>
</div>
<div v-if="!hasGiftPermission" class="member-hint warn">需要打手基础管理或加积分权限</div>
<div v-else class="member-hint">与上方购买会员分开仅用于抢单资格不写购买表</div>
</template>
<div class="member-list">
<div v-for="gift in giftMemberList" :key="'g-' + gift.id" class="member-item gift-item">
<el-tag :type="gift.is_active ? 'success' : 'info'" effect="plain">
{{ gift.huiyuan_name }} 到期: {{ gift.daoqi_time || '--' }}
</el-tag>
<span class="gift-meta">赠送人 {{ gift.song_ren || '--' }} · {{ gift.song_time || '--' }}</span>
<el-button
type="warning"
size="small"
link
@click="openGiftDialog(gift)"
:disabled="!hasGiftPermission"
>改期</el-button>
<el-button
type="danger"
size="small"
link
@click="removeGiftMember(gift)"
:disabled="!hasGiftPermission"
>移除</el-button>
</div>
<div v-if="giftMemberList.length === 0" class="empty-message">暂无赠送会员</div>
</div>
</el-card>
<!-- 其他信息卡片 -->
<el-card class="info-card" shadow="hover">
<template #header>
<div class="card-header">
<span>📝 其他信息</span>
</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="邀请人ID">
<span class="copyable" @click="copyText(playerInfo.yaoqingren)">
{{ playerInfo.yaoqingren || '--' }} <el-icon><CopyDocument /></el-icon>
</span>
<el-button
v-if="canChangeInviter"
type="primary"
link
size="small"
style="margin-left: 8px"
@click="openInviterDialog"
>更换邀请人</el-button>
</el-descriptions-item>
<el-descriptions-item label="介绍">
<el-tooltip v-if="playerInfo.jieshao" :content="playerInfo.jieshao" placement="top">
<span class="truncate">{{ playerInfo.jieshao }}</span>
</el-tooltip>
<span v-else>--</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
</el-row>
</div>
<!-- 更换邀请人弹窗 -->
<el-dialog v-model="inviterDialogVisible" title="更换打手邀请人" width="420px">
<el-form label-width="100px">
<el-form-item label="当前邀请人">
<span>{{ playerInfo.yaoqingren || '无' }}</span>
</el-form-item>
<el-form-item label="新邀请人ID" required>
<el-input v-model="newInviterId" placeholder="请输入管事用户ID7位" maxlength="7" />
<div class="form-tip">新邀请人必须是已存在的管事用户</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="inviterDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitChangeInviter" :loading="inviterChanging">确认更换</el-button>
</template>
</el-dialog>
<!-- 添加会员弹窗 -->
<el-dialog v-model="memberDialogVisible" title="添加会员" width="400px">
<el-form label-width="90px">
<el-form-item label="会员类型" required>
<el-select v-model="memberForm.huiyuan_id" placeholder="请选择" filterable style="width: 100%">
<el-option
v-for="m in allMembers"
:key="m.huiyuan_id"
:label="`${m.jieshao} (${m.huiyuan_id})`"
:value="m.huiyuan_id"
/>
</el-select>
</el-form-item>
<el-form-item label="天数" required>
<el-input-number v-model="memberForm.days" :min="1" :max="3650" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="memberDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="memberAdding" @click="addMember">确认</el-button>
</template>
</el-dialog>
<!-- 赠送会员弹窗 -->
<el-dialog
v-model="giftDialogVisible"
:title="giftForm.id ? '修改赠送会员到期时间' : '送会员(免费接单)'"
width="420px"
>
<el-form label-width="100px">
<el-form-item label="会员类型" required>
<el-select
v-model="giftForm.huiyuan_id"
placeholder="请选择真实会员类型"
filterable
style="width: 100%"
:disabled="!!giftForm.id"
>
<el-option
v-for="m in allMembers"
:key="'g-' + m.huiyuan_id"
:label="`${m.jieshao} (${m.huiyuan_id})`"
:value="m.huiyuan_id"
/>
</el-select>
</el-form-item>
<el-form-item label="到期日期" required>
<el-date-picker
v-model="giftForm.daoqi_time"
type="datetime"
placeholder="选择到期时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<div class="member-hint">赠送时若积分不是 10将自动改成 10 并写入操作日志</div>
</el-form>
<template #footer>
<el-button @click="giftDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="giftSaving" @click="submitGiftMember">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeft, Edit, Check, Close, CopyDocument, Avatar } from '@element-plus/icons-vue'
import request from '@/utils/request'
const route = useRoute()
const router = useRouter()
// 基础数据
const loading = ref(false)
const saving = ref(false)
const playerId = ref('')
const username = ref(localStorage.getItem('username') || '')
const playerInfo = ref({})
const originalInfo = ref({})
const memberList = ref([])
const giftMemberList = ref([])
const allMembers = ref([])
const isEditMode = ref(false)
const editData = ref({})
// 会员弹窗
const memberDialogVisible = ref(false)
const memberForm = reactive({ huiyuan_id: '', days: 30 })
const memberAdding = ref(false)
// 赠送会员弹窗
const giftDialogVisible = ref(false)
const giftSaving = ref(false)
const giftForm = reactive({ id: null, huiyuan_id: '', daoqi_time: '' })
// 权限控制
const userPermissions = ref([])
const isGoldTitle = computed(() => playerInfo.value.chenghao === '金牌选手')
const hasMemberPermission = computed(() =>
userPermissions.value.includes('000001') ||
userPermissions.value.includes('001ff') ||
userPermissions.value.includes('001gg')
)
const hasGiftPermission = computed(() =>
userPermissions.value.includes('000001') ||
userPermissions.value.includes('001aa') ||
userPermissions.value.includes('001bb') ||
userPermissions.value.includes('001cc') ||
userPermissions.value.includes('001dd') ||
userPermissions.value.includes('001ee')
)
const hasChenghaoPermission = computed(() => userPermissions.value.includes('001hh'))
const canChangeInviter = computed(() => userPermissions.value.includes('001ee') || userPermissions.value.includes('000001'))
const inviterDialogVisible = ref(false)
const newInviterId = ref('')
const inviterChanging = ref(false)
const openInviterDialog = () => {
newInviterId.value = ''
inviterDialogVisible.value = true
}
const submitChangeInviter = async () => {
const newId = newInviterId.value.trim()
if (!newId) {
ElMessage.warning('请输入新邀请人ID')
return
}
if (newId === playerId.value) {
ElMessage.warning('不能将自己设为邀请人')
return
}
try {
await ElMessageBox.confirm(
`确认将打手 ${playerId.value} 的邀请人更换为 ${newId}?新上级必须是管事。`,
'确认更换',
{ type: 'warning' }
)
} catch { return }
inviterChanging.value = true
try {
const res = await request.post('/houtai/htghyqr', {
username: username.value,
yonghuid: playerId.value,
target_type: 'dashou',
new_yaoqingren: newId
})
if (res.code === 0) {
ElMessage.success('邀请人更换成功')
inviterDialogVisible.value = false
await fetchDetail()
} else {
ElMessage.error(res.msg || '更换失败')
}
} catch (e) {
console.error(e)
ElMessage.error('更换失败')
} finally {
inviterChanging.value = false
}
}
// 辅助函数
const getFullImageUrl = (url) => {
if (!url) return ''
const ossUrl = window.$ossURL || ''
return url.startsWith('http') ? url : ossUrl + url
}
const formatPrice = (val) => parseFloat(val || 0).toFixed(2)
const formatTime = (time) => {
if (!time) return '--'
const date = new Date(time)
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2,'0')}-${date.getDate().toString().padStart(2,'0')} ${date.getHours().toString().padStart(2,'0')}:${date.getMinutes().toString().padStart(2,'0')}`
}
const copyText = (text) => {
if (!text || text === '--') return
navigator.clipboard.writeText(text).then(() => ElMessage.success('复制成功')).catch(() => ElMessage.error('复制失败'))
}
const goBack = () => router.back()
// 统计
const completionRate = computed(() => {
const total = playerInfo.value.jiedanzongliang || 0
const completed = playerInfo.value.chengjiaozongliang || 0
return total === 0 ? 0 : Math.round((completed / total) * 100)
})
const refundRate = computed(() => {
const completed = playerInfo.value.chengjiaozongliang || 0
const refund = playerInfo.value.tuikuanliang || 0
return completed === 0 ? 0 : Math.round((refund / completed) * 100)
})
// 获取详情
const fetchDetail = async () => {
if (!playerId.value) return
loading.value = true
try {
const res = await request.post('/houtai/kefuhqdsxq', {
phone: username.value,
uid: playerId.value
})
if (res.code === 0) {
const data = res.data
playerInfo.value = {
...(data.user_info || {}),
create_time: data.user_info?.create_time || data.user_info?.CreateTime || '',
}
originalInfo.value = JSON.parse(JSON.stringify(playerInfo.value))
memberList.value = data.member_list || []
giftMemberList.value = data.gift_member_list || []
allMembers.value = data.all_members || []
userPermissions.value = data.permissions || []
} else if (res.code === 403) {
ElMessage.error('无权限访问')
router.back()
} else {
ElMessage.error(res.msg || '获取打手详情失败')
}
} catch (error) {
console.error('获取打手详情失败', error)
ElMessage.error('获取打手详情失败')
} finally {
loading.value = false
}
}
// 编辑模式
const toggleEditMode = () => {
editData.value = JSON.parse(JSON.stringify(playerInfo.value))
isEditMode.value = true
}
const cancelEdit = () => {
isEditMode.value = false
editData.value = {}
}
const saveChanges = async () => {
const changes = {}
for (const key in editData.value) {
if (editData.value[key] !== playerInfo.value[key]) {
changes[key] = editData.value[key]
}
}
if (Object.keys(changes).length === 0) {
ElMessage.warning('未做任何修改')
cancelEdit()
return
}
saving.value = true
try {
let success = true
for (const [key, val] of Object.entries(changes)) {
let caozuo = ''
let value = val
switch (key) {
case 'yue':
caozuo = val > playerInfo.value.yue ? 'jia_yue' : 'jian_yue'
value = Math.abs(val - playerInfo.value.yue)
break
case 'jifen':
caozuo = val > playerInfo.value.jifen ? 'jia_jifen' : 'jian_jifen'
value = Math.abs(val - playerInfo.value.jifen)
break
case 'yajin':
caozuo = val > playerInfo.value.yajin ? 'jia_yajin' : 'jian_yajin'
value = Math.abs(val - playerInfo.value.yajin)
break
case 'zhanghaozhuangtai':
caozuo = 'gai_zhanghaozhuangtai'
value = val
break
case 'zaixianzhuangtai':
caozuo = 'gai_zaixianzhuangtai'
value = val
break
case 'zhuangtai':
caozuo = 'gai_zhuangtai'
value = val
break
default:
continue
}
const res = await request.post('/houtai/kefuxgds', {
username: username.value,
dashou_id: playerId.value,
caozuo: caozuo,
value: value
})
if (res.code !== 0) {
success = false
ElMessage.error(`修改 ${key} 失败:${res.msg}`)
break
}
}
if (success) {
ElMessage.success('修改成功')
await fetchDetail()
cancelEdit()
}
} catch (error) {
ElMessage.error('修改失败')
} finally {
saving.value = false
}
}
// 称号修改
const handleChenghaoChange = async () => {
const newTitle = isGoldTitle.value ? '普通打手' : '金牌选手'
try {
const res = await request.post('/houtai/kefuxgds', {
username: username.value,
dashou_id: playerId.value,
caozuo: 'gai_chenghao',
value: newTitle
})
if (res.code === 0) {
ElMessage.success(`称号已修改为“${newTitle}`)
await fetchDetail()
} else {
ElMessage.error(res.msg || '修改称号失败')
}
} catch (error) {
ElMessage.error('修改称号失败')
}
}
// 添加会员
const openMemberDialog = () => {
if (!hasMemberPermission.value) {
ElMessage.warning('无添加会员权限(需要 001ff 或超级管理)')
return
}
if (allMembers.value.length === 0) {
ElMessage.warning('会员类型列表为空,请刷新页面后重试')
}
memberForm.huiyuan_id = ''
memberForm.days = 30
memberDialogVisible.value = true
}
const addMember = async () => {
if (!memberForm.huiyuan_id) {
ElMessage.warning('请选择会员类型')
return
}
memberAdding.value = true
try {
const res = await request.post('/houtai/kefuxgds', {
username: username.value,
dashou_id: playerId.value,
caozuo: 'jia_huiyuan',
huiyuan_id: memberForm.huiyuan_id,
value: memberForm.days
})
if (res.code === 0) {
ElMessage.success('添加会员成功')
memberDialogVisible.value = false
await fetchDetail()
} else {
ElMessage.error(res.msg || '添加失败')
}
} catch (error) {
ElMessage.error('添加失败')
} finally {
memberAdding.value = false
}
}
const removeMember = async (member) => {
ElMessageBox.confirm(`确定移除会员“${member.huiyuan_name}”吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await request.post('/houtai/kefuxgds', {
username: username.value,
dashou_id: playerId.value,
caozuo: 'jian_huiyuan',
huiyuan_id: member.huiyuan_id
})
if (res.code === 0) {
ElMessage.success('移除成功')
await fetchDetail()
} else {
ElMessage.error(res.msg || '移除失败')
}
}).catch(() => {})
}
// 关键:从路由参数中获取 playerId参数名必须为 playerId
const openGiftDialog = (gift = null) => {
if (!hasGiftPermission.value) {
ElMessage.warning('无赠送会员权限')
return
}
if (gift) {
giftForm.id = gift.id
giftForm.huiyuan_id = gift.huiyuan_id
giftForm.daoqi_time = gift.daoqi_time || ''
} else {
giftForm.id = null
giftForm.huiyuan_id = ''
giftForm.daoqi_time = ''
}
giftDialogVisible.value = true
}
const submitGiftMember = async () => {
if (!giftForm.huiyuan_id) {
ElMessage.warning('请选择会员类型')
return
}
if (!giftForm.daoqi_time) {
ElMessage.warning('请选择到期时间')
return
}
giftSaving.value = true
try {
const payload = {
phone: username.value,
dashou_id: playerId.value,
huiyuan_id: giftForm.huiyuan_id,
daoqi_time: giftForm.daoqi_time,
}
if (giftForm.id) payload.id = giftForm.id
const res = await request.post('/houtai/dszsonghy', payload)
if (res.code === 0) {
ElMessage.success(res.msg || '操作成功')
giftDialogVisible.value = false
await fetchDetail()
} else {
ElMessage.error(res.msg || '操作失败')
}
} catch (e) {
ElMessage.error(e?.response?.data?.msg || '操作失败')
} finally {
giftSaving.value = false
}
}
const removeGiftMember = async (gift) => {
try {
await ElMessageBox.confirm(
`确定移除赠送会员“${gift.huiyuan_name}”吗?移除后将无法用该赠送资格抢单。`,
'提示',
{ type: 'warning' }
)
} catch { return }
try {
const res = await request.post('/houtai/dsycsonghy', {
phone: username.value,
dashou_id: playerId.value,
id: gift.id,
huiyuan_id: gift.huiyuan_id,
})
if (res.code === 0) {
ElMessage.success('移除成功')
await fetchDetail()
} else {
ElMessage.error(res.msg || '移除失败')
}
} catch (e) {
ElMessage.error(e?.response?.data?.msg || '移除失败')
}
}
onMounted(() => {
playerId.value = route.params.playerId
if (!playerId.value) {
ElMessage.error('打手ID不存在')
router.back()
return
}
fetchDetail()
})
</script>
<style scoped>
/* 保持原有样式,新增以下 */
.chenghao-wrapper {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.member-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.gift-item {
flex-wrap: wrap;
gap: 6px;
}
.gift-meta {
font-size: 12px;
color: #909399;
margin: 0 4px;
}
.member-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.empty-message {
color: #999;
text-align: center;
padding: 20px;
}
.member-hint {
font-size: 12px;
color: #909399;
margin-bottom: 8px;
}
.member-hint.warn {
color: #e6a23c;
}
/* 其余样式保持不变(从原代码复制) */
.player-detail {
padding: 20px;
background-color: #f5f9fc;
min-height: 100%;
}
.loading-container {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 2px 8px rgba(0,0,0,0.02);
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
background: white;
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.02);
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #1a2634;
}
.header-right {
display: flex;
gap: 12px;
}
.info-card {
margin-bottom: 24px;
border-radius: 12px;
overflow: hidden;
}
.card-header {
font-weight: 600;
color: #1a2634;
}
.base-info {
padding: 8px;
}
.avatar-wrapper {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.status-badges {
margin-top: 12px;
display: flex;
gap: 8px;
justify-content: center;
}
.copyable {
cursor: pointer;
color: #409eff;
display: inline-flex;
align-items: center;
gap: 4px;
}
.copyable:hover {
text-decoration: underline;
}
.stat-item {
text-align: center;
padding: 12px 0;
}
.stat-label {
font-size: 13px;
color: #6b7a88;
margin-bottom: 4px;
}
.stat-value {
font-size: 24px;
font-weight: 600;
color: #1a2634;
}
.stat-value.success {
color: #67c23a;
}
.stat-value.danger {
color: #f56c6c;
}
.stat-percent {
font-size: 24px;
font-weight: 600;
color: #1a2634;
text-align: center;
}
.el-divider {
margin: 16px 0;
}
.member-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.member-tag {
font-size: 13px;
}
.truncate {
max-width: 200px;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 手机适配 */
@media screen and (max-width: 768px) {
.player-detail {
padding: 12px;
}
.detail-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.header-right {
width: 100%;
justify-content: flex-end;
}
.stat-value {
font-size: 20px;
}
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
</style>