1098 lines
34 KiB
Vue
1098 lines
34 KiB
Vue
<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="请输入管事用户ID(7位)" 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> |