494 lines
14 KiB
Vue
494 lines
14 KiB
Vue
<template>
|
||
<div class="employee-list">
|
||
<el-card>
|
||
<template #header>
|
||
<div class="card-header">
|
||
<span>代理列表</span>
|
||
<div class="header-actions">
|
||
<el-button type="primary" @click="$router.push('/admin/employees/add')">
|
||
<el-icon>
|
||
<Plus />
|
||
</el-icon>添加代理
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 搜索条件 -->
|
||
<div class="search-form">
|
||
<el-form :inline="true" :model="queryParams">
|
||
<el-form-item label="状态">
|
||
<el-select v-model="queryParams.status" placeholder="全部状态" clearable style="width: 150px">
|
||
<el-option label="正常" :value="1" />
|
||
<el-option label="禁用" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="搜索">
|
||
<el-input v-model="queryParams.keyword" placeholder="工号/姓名/账号" clearable style="width: 200px"
|
||
:prefix-icon="Search" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">
|
||
<el-icon>
|
||
<Search />
|
||
</el-icon>搜索
|
||
</el-button>
|
||
<el-button @click="resetSearch">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<el-row :gutter="20" class="stat-cards">
|
||
<el-col :span="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="stat-item">
|
||
<div class="stat-label">总代理数</div>
|
||
<div class="stat-value">{{ total }}</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="stat-item">
|
||
<div class="stat-label">正常代理</div>
|
||
<div class="stat-value success">{{ activeCount }}</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="stat-item">
|
||
<div class="stat-label">总积分</div>
|
||
<div class="stat-value warning">{{ totalPoints }}</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="stat-item">
|
||
<div class="stat-label">平均积分</div>
|
||
<div class="stat-value info">{{ averagePoints }}</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 代理列表 -->
|
||
<el-table :data="employeeList" v-loading="loading" border style="width: 100%">
|
||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||
<el-table-column prop="employee_id" label="工号" width="100" align="center" />
|
||
<el-table-column prop="username" label="账号" width="150" align="center" />
|
||
<el-table-column prop="name" label="姓名" width="120" align="center" />
|
||
<el-table-column prop="phone" label="手机号" width="120" align="center" />
|
||
<el-table-column prop="score" label="积分" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<span :class="{ 'points-warning': row.score < 100 }">{{ row.score }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
||
{{ row.status === 1 ? '正常' : '禁用' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="code" label="机械码" width="240" align="center" />
|
||
<el-table-column prop="level_info" label="等级" width="60" align="center" />
|
||
<el-table-column prop="last_login_at" label="最后登录" width="160" align="center">
|
||
<template #default="{ row }">
|
||
{{ row.last_login_at ? formatDate(row.last_login_at) : '-' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="created_at" label="创建时间" width="160" align="center" >
|
||
<template #default="{ row }">
|
||
{{ formatDate(row.created_at) }}
|
||
</template>
|
||
</el-table-column>
|
||
<!-- <el-table-column label="操作" width="320" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" size="small" @click="showTopUp(row)">
|
||
<el-icon><Coin /></el-icon>充值
|
||
</el-button>
|
||
<el-button type="success" size="small" @click="viewAccess(row)">
|
||
<el-icon><DataLine /></el-icon>积分记录
|
||
</el-button>
|
||
<el-button
|
||
v-if="row.status === 1"
|
||
type="warning"
|
||
size="small"
|
||
@click="toggleStatus(row, 'disable')"
|
||
>
|
||
<el-icon><Switch /></el-icon>禁用
|
||
</el-button>
|
||
<el-button
|
||
v-else
|
||
type="success"
|
||
size="small"
|
||
@click="toggleStatus(row, 'enable')"
|
||
>
|
||
<el-icon><Check /></el-icon>启用
|
||
</el-button>
|
||
<el-button type="danger" size="small" @click="showDeduct(row)">
|
||
<el-icon><Coin /></el-icon>扣减
|
||
</el-button>
|
||
</template>
|
||
</el-table-column> -->
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination">
|
||
<el-pagination v-model:current-page="queryParams.page" v-model:page-size="queryParams.page_size" :total="total"
|
||
:page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 充值弹窗 -->
|
||
<el-dialog v-model="topUpVisible" title="积分充值" width="400px">
|
||
<el-form :model="topUpForm" ref="topUpFormRef" :rules="topUpRules" label-width="80px">
|
||
<el-form-item label="代理">
|
||
<span>{{ currentEmployee?.name }} ({{ currentEmployee?.employee_id }})</span>
|
||
</el-form-item>
|
||
<el-form-item label="当前积分">
|
||
<span>{{ currentEmployee?.points }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="充值数量" prop="amount">
|
||
<el-input-number v-model="topUpForm.amount" :min="1" :max="100000" style="width: 200px" />
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="topUpForm.remark" placeholder="选填" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="topUpVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitTopUp" :loading="topUpLoading">确认充值</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
<!-- 扣减弹窗 -->
|
||
<el-dialog v-model="deductVisible" title="积分扣减" width="400px">
|
||
<el-form :model="deductForm" ref="deductFormRef" :rules="deductRules" label-width="80px">
|
||
<el-form-item label="代理">
|
||
<span>{{ currentEmployee?.name }} ({{ currentEmployee?.employee_id }})</span>
|
||
</el-form-item>
|
||
<el-form-item label="当前积分">
|
||
<span>{{ currentEmployee?.points }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="扣减数量" prop="amount" class="deduct-label">
|
||
<el-input-number class="deduct-input" v-model="deductForm.amount" :min="1" :max="currentEmployee?.points || 1"
|
||
style="width: 200px" />
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="deductForm.remark" placeholder="选填" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="deductVisible = false">取消</el-button>
|
||
<el-button type="danger" @click="submitDeduct" :loading="deductLoading">确认扣减</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Plus, Search, Coin, DataLine, Switch, Check } from '@element-plus/icons-vue'
|
||
import request from '@/utils/request'
|
||
|
||
const router = useRouter() // 导入 router
|
||
const loading = ref(false)
|
||
const employeeList = ref([])
|
||
const total = ref(0)
|
||
const topUpVisible = ref(false)
|
||
const topUpLoading = ref(false)
|
||
const deductVisible = ref(false)
|
||
const deductLoading = ref(false)
|
||
const currentEmployee = ref(null)
|
||
|
||
const queryParams = reactive({
|
||
page: 1,
|
||
page_size: 20,
|
||
status: '',
|
||
keyword: ''
|
||
})
|
||
|
||
const topUpForm = reactive({
|
||
amount: 100,
|
||
remark: ''
|
||
})
|
||
const deductForm = reactive({
|
||
amount: 100,
|
||
remark: ''
|
||
})
|
||
|
||
const topUpRules = {
|
||
amount: [
|
||
{ required: true, message: '请输入充值数量', trigger: 'blur' },
|
||
{ type: 'number', min: 1, message: '充值数量必须大于0', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
const deductRules = {
|
||
amount: [
|
||
{ required: true, message: '请输入扣减数量', trigger: 'blur' },
|
||
{ type: 'number', min: 1, message: '扣减数量必须大于0', trigger: 'blur' }
|
||
]
|
||
}
|
||
// 计算统计信息
|
||
const activeCount = computed(() => {
|
||
return employeeList.value.filter(e => e.status === 1).length
|
||
})
|
||
|
||
const totalPoints = computed(() => {
|
||
return employeeList.value.reduce((sum, e) => sum + e.score, 0)
|
||
})
|
||
|
||
const averagePoints = computed(() => {
|
||
if (employeeList.value.length === 0) return 0
|
||
return Math.round(totalPoints.value / employeeList.value.length)
|
||
})
|
||
|
||
// 格式化日期
|
||
const formatDate = (timestamp) => {
|
||
if (!timestamp) return '-'
|
||
const date = new Date(timestamp * 1000)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hour = String(date.getHours()).padStart(2, '0')
|
||
const minute = String(date.getMinutes()).padStart(2, '0')
|
||
return `${year}-${month}-${day} ${hour}:${minute}`
|
||
}
|
||
|
||
// 获取代理列表
|
||
const fetchEmployeeList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
page: queryParams.page,
|
||
page_size: queryParams.page_size
|
||
}
|
||
if (queryParams.status !== '') {
|
||
params.status = queryParams.status
|
||
}
|
||
if (queryParams.keyword) {
|
||
params.keyword = queryParams.keyword
|
||
}
|
||
|
||
const res = await request.get('/admin/employee/list', { params })
|
||
if (res.code === 200) {
|
||
employeeList.value = res.data.list
|
||
total.value = res.data.total
|
||
}
|
||
} catch (error) {
|
||
// console.error('获取代理列表失败:', error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
queryParams.page = 1
|
||
fetchEmployeeList()
|
||
}
|
||
|
||
// 重置搜索
|
||
const resetSearch = () => {
|
||
queryParams.status = ''
|
||
queryParams.keyword = ''
|
||
queryParams.page = 1
|
||
fetchEmployeeList()
|
||
}
|
||
|
||
// 分页大小变化
|
||
const handleSizeChange = (size) => {
|
||
queryParams.page_size = size
|
||
fetchEmployeeList()
|
||
}
|
||
|
||
// 页码变化
|
||
const handleCurrentChange = (page) => {
|
||
queryParams.page = page
|
||
fetchEmployeeList()
|
||
}
|
||
|
||
// 显示充值弹窗
|
||
const showTopUp = (row) => {
|
||
currentEmployee.value = row
|
||
topUpForm.amount = 100
|
||
topUpForm.remark = ''
|
||
topUpVisible.value = true
|
||
}
|
||
|
||
// 提交充值
|
||
const submitTopUp = async () => {
|
||
topUpLoading.value = true
|
||
try {
|
||
const res = await request.post(`/admin/employee/topup/${currentEmployee.value.employee_id}`, {
|
||
amount: topUpForm.amount,
|
||
remark: topUpForm.remark
|
||
})
|
||
if (res.code === 200) {
|
||
ElMessage.success('充值成功')
|
||
topUpVisible.value = false
|
||
fetchEmployeeList() // 刷新列表
|
||
}
|
||
} catch (error) {
|
||
// console.error('充值失败:', error)
|
||
} finally {
|
||
topUpLoading.value = false
|
||
}
|
||
}
|
||
// 显示扣减弹窗
|
||
const showDeduct = (row) => {
|
||
currentEmployee.value = row
|
||
deductForm.amount = 100
|
||
deductForm.remark = ''
|
||
deductVisible.value = true
|
||
}
|
||
|
||
// 提交扣减
|
||
const submitDeduct = async () => {
|
||
deductLoading.value = true
|
||
try {
|
||
const res = await request.post(`/admin/employee/deduct/${currentEmployee.value.employee_id}`, {
|
||
amount: deductForm.amount,
|
||
remark: deductForm.remark
|
||
})
|
||
if (res.code === 200) {
|
||
ElMessage.success('扣减成功')
|
||
deductVisible.value = false
|
||
fetchEmployeeList() // 刷新列表
|
||
}
|
||
} catch (error) {
|
||
// console.error('扣减失败:', error)
|
||
} finally {
|
||
deductLoading.value = false
|
||
}
|
||
}
|
||
// 切换代理状态
|
||
const toggleStatus = async (row, action) => {
|
||
const actionText = action === 'enable' ? '启用' : '禁用'
|
||
try {
|
||
await ElMessageBox.confirm(`确定要${actionText}代理 ${row.name} 吗?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
|
||
const res = await request.post(`/admin/employee/${action}/${row.employee_id}`)
|
||
if (res.code === 200) {
|
||
ElMessage.success(`${actionText}成功`)
|
||
fetchEmployeeList() // 刷新列表
|
||
}
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
// console.error('操作失败:', error)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查看积分记录
|
||
const viewAccess = (row) => {
|
||
// 跳转到积分记录页面,并传递代理ID参数
|
||
router.push({
|
||
path: '/admin/access',
|
||
query: {
|
||
id: row.id,
|
||
}
|
||
})
|
||
}
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
fetchEmployeeList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.employee-list {
|
||
padding: 0;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
/* 可根据需要保留原有的字体、颜色等样式 */
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.search-form {
|
||
margin-bottom: 20px;
|
||
padding: 20px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.stat-cards {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.stat-card {
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-item {
|
||
padding: 10px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #303133;
|
||
}
|
||
|
||
.stat-value.success {
|
||
color: #67c23a;
|
||
}
|
||
|
||
.stat-value.warning {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
.stat-value.info {
|
||
color: #409eff;
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.points-warning {
|
||
color: #f56c6c;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.deduct-input :deep(.el-input-number__decrease):hover,
|
||
.deduct-input :deep(.el-input-number__increase):hover {
|
||
color: #fff;
|
||
background-color: #f56c6c;
|
||
}
|
||
|
||
.deduct-label :deep(.el-form-item__label) {
|
||
color: #f56c6c;
|
||
font-weight: bold;
|
||
}
|
||
</style> |