1223 行
35 KiB
HTML
1223 行
35 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<title>骑手管理</title>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/css/menu.css?v=1.0.5"/>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/css/minimal-admin.css?v=1.0.0"/>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/Retail/css/myscroll.css?v=1.0.2"/>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/layui-v2.4.3/layui/css/layui.css"/>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/Retail/css/home.css"/>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/Retail/css/goodsList.css?v=1.0.2"/>
|
|
<link rel="stylesheet" type="text/css" href="__PUBLIC__/vue/element-ui.css?v=2.13.2">
|
|
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/vue/js/vue2.5.16.js"></script>
|
|
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/vue/element-ui.js?v=2.13.2"></script>
|
|
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/js/common.js?v=1.0.2"></script>
|
|
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/jquery/js/jquery-3.2.1.min.js"></script>
|
|
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/layui-v2.4.3/layui/layui.js"></script>
|
|
</head>
|
|
<body>
|
|
<div id="RiderManagement" v-cloak>
|
|
<div class="goodsBox">
|
|
<div class="goods_title">
|
|
<div class="goods_title_inner">
|
|
<i class="fas fa-motorcycle" style="margin-right: 8px; color: #409EFF;"></i>
|
|
骑手管理
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 统计卡片区域 -->
|
|
<div class="rider-stats-container" style="padding: 20px; background: #f5f7fa;">
|
|
<el-row :gutter="20">
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
|
<i class="fas fa-users"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-number">{{ statistics.totalRiders }}</div>
|
|
<div class="stat-label">总骑手数</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
|
<i class="fas fa-user-check"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-number">{{ statistics.onlineRiders }}</div>
|
|
<div class="stat-label">在线骑手</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
|
<i class="fas fa-clipboard-list"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-number">{{ statistics.todayOrders }}</div>
|
|
<div class="stat-label">今日订单</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<div class="stat-card">
|
|
<div class="stat-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
|
|
<i class="fas fa-dollar-sign"></i>
|
|
</div>
|
|
<div class="stat-content">
|
|
<div class="stat-number">¥{{ statistics.todayRevenue }}</div>
|
|
<div class="stat-label">今日收入</div>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
|
|
<!-- 主要内容区域 -->
|
|
<div style="padding: 20px; background: white; margin: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);">
|
|
<!-- 工具栏 -->
|
|
<div class="toolbar" style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
|
<div class="toolbar-left">
|
|
<el-input
|
|
v-model="searchKeyword"
|
|
placeholder="搜索骑手姓名或手机号"
|
|
prefix-icon="el-icon-search"
|
|
style="width: 300px; margin-right: 15px;"
|
|
@input="handleSearch">
|
|
</el-input>
|
|
<el-select v-model="statusFilter" placeholder="状态筛选" style="width: 120px; margin-right: 15px;" @change="handleFilter">
|
|
<el-option label="全部状态" value=""></el-option>
|
|
<el-option label="在线" value="1"></el-option>
|
|
<el-option label="离线" value="0"></el-option>
|
|
<el-option label="忙碌" value="2"></el-option>
|
|
</el-select>
|
|
<el-select v-model="certificationFilter" placeholder="认证状态" style="width: 120px; margin-right: 15px;" @change="handleFilter">
|
|
<el-option label="全部认证" value=""></el-option>
|
|
<el-option label="认证失败" value="3"></el-option>
|
|
<el-option label="已认证" value="2"></el-option>
|
|
<el-option label="未认证" value="0"></el-option>
|
|
<el-option label="待审核" value="1"></el-option>
|
|
</el-select>
|
|
<el-select v-model="storeFilter" placeholder="店铺筛选" style="width: 150px; margin-right: 15px;" clearable filterable @change="handleFilter">
|
|
<el-option label="全部店铺" value=""></el-option>
|
|
<el-option label="不限制店铺" value="0"></el-option>
|
|
<el-option
|
|
v-for="store in storeList"
|
|
:key="store.id"
|
|
:label="store.name"
|
|
:value="store.id.toString()">
|
|
</el-option>
|
|
</el-select>
|
|
</div>
|
|
<div class="toolbar-right">
|
|
<el-button type="primary" icon="el-icon-plus" @click="showAddRiderDialog">添加骑手</el-button>
|
|
<el-button type="success" icon="el-icon-refresh" @click="refreshData">刷新</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 骑手列表表格 -->
|
|
<el-table
|
|
:data="riders"
|
|
v-loading="loading"
|
|
element-loading-text="加载中..."
|
|
style="width: 100%"
|
|
:header-cell-style="{background:'#f5f7fa',color:'#606266'}"
|
|
@selection-change="handleSelectionChange">
|
|
|
|
<el-table-column type="selection" width="55"></el-table-column>
|
|
|
|
<el-table-column label="头像" width="80" align="center">
|
|
<template slot-scope="scope">
|
|
<el-avatar :size="40" :src="scope.row.rider_avatar" icon="el-icon-user-solid"></el-avatar>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="rider_name" label="姓名" width="120"></el-table-column>
|
|
|
|
<el-table-column prop="rider_phone" label="手机号" width="130"></el-table-column>
|
|
|
|
<el-table-column label="状态" width="100" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getStatusTagType(scope.row.work_status)" size="small">
|
|
{{ getStatusText(scope.row.work_status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="认证状态" width="120" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getCertificationTagType(scope.row.auth_status)" size="small">
|
|
{{ getCertificationText(scope.row.auth_status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="绑定店铺" width="150" align="center">
|
|
<template slot-scope="scope">
|
|
<span v-if="scope.row.store_id && scope.row.store_id > 0" style="color: #409EFF;">
|
|
{{ scope.row.store_name || '店铺ID:' + scope.row.store_id }}
|
|
</span>
|
|
<span v-else style="color: #909399;">不限制</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="today_orders" label="今日订单" width="100" align="center"></el-table-column>
|
|
|
|
<el-table-column label="今日收入" width="120" align="center">
|
|
<template slot-scope="scope">
|
|
<span style="color: #67C23A; font-weight: bold;">¥{{ scope.row.today_income }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="rating" label="评分" width="150" align="center">
|
|
<template slot-scope="scope">
|
|
<el-rate v-model="+scope.row.rating" disabled text-color="#ff9900"></el-rate>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="注册时间" width="120">
|
|
<template slot-scope="scope">
|
|
{{ formatDate(scope.row.add_time) }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="操作" width="320" align="center" fixed="right">
|
|
<template slot-scope="scope">
|
|
<el-link icon="el-icon-view" @click="viewRiderDetail(scope.row)">详情</el-link>
|
|
<el-link icon="el-icon-edit" @click="editRider(scope.row)">编辑</el-link>
|
|
<el-link icon="el-icon-check" @click="openAuthReviewDialog(scope.row)">认证</el-link>
|
|
<el-link icon="el-icon-key" @click="resetPassword(scope.row)">重置密码</el-link>
|
|
<el-link icon="el-icon-delete" @click="deleteRider(scope.row)">删除</el-link>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<div style="margin-top: 20px; text-align: center;">
|
|
<el-pagination
|
|
@size-change="handleSizeChange"
|
|
@current-change="handleCurrentChange"
|
|
:current-page="currentPage"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
:page-size="pageSize"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
:total="+totalCount">
|
|
</el-pagination>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 添加/编辑骑手对话框 -->
|
|
|
|
</div>
|
|
|
|
<style>
|
|
/* 表单分组样式 */
|
|
.form-section {
|
|
margin-bottom: 30px;
|
|
padding: 20px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
border-left: 4px solid #409EFF;
|
|
}
|
|
|
|
.section-title {
|
|
margin: 0 0 20px 0;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.section-title::before {
|
|
content: '';
|
|
width: 4px;
|
|
height: 16px;
|
|
background: #409EFF;
|
|
margin-right: 8px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.el-form-item {
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.el-input-number {
|
|
width: 100%;
|
|
}
|
|
|
|
/* 统计卡片样式 */
|
|
.stat-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
display: flex;
|
|
align-items: center;
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 15px;
|
|
}
|
|
|
|
.stat-icon i {
|
|
font-size: 24px;
|
|
color: white;
|
|
}
|
|
|
|
.stat-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #2c3e50;
|
|
line-height: 1;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 14px;
|
|
color: #7f8c8d;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
/* 头像上传样式 */
|
|
.avatar-uploader .el-upload {
|
|
border: 1px dashed #d9d9d9;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.avatar-uploader .el-upload:hover {
|
|
border-color: #409EFF;
|
|
}
|
|
|
|
.avatar-uploader-icon {
|
|
font-size: 28px;
|
|
color: #8c939d;
|
|
width: 100px;
|
|
height: 100px;
|
|
line-height: 100px;
|
|
text-align: center;
|
|
}
|
|
|
|
.avatar {
|
|
width: 100px;
|
|
height: 100px;
|
|
display: block;
|
|
}
|
|
|
|
/* 骑手详情样式 */
|
|
.detail-avatar {
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.detail-avatar h3 {
|
|
margin: 15px 0 10px 0;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.detail-info {
|
|
padding: 20px;
|
|
}
|
|
|
|
/* 骑手详情对话框样式 */
|
|
.rider-detail-dialog .el-dialog {
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.rider-detail-dialog .el-dialog__header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 20px 24px;
|
|
margin: 0;
|
|
}
|
|
|
|
.rider-detail-dialog .el-dialog__title {
|
|
color: white;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.rider-detail-dialog .el-dialog__headerbtn .el-dialog__close {
|
|
color: white;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.rider-detail-dialog .el-dialog__body {
|
|
padding: 0;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.rider-detail-container {
|
|
padding: 24px;
|
|
max-height: 70vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* 头部信息卡片 */
|
|
.rider-header-card {
|
|
background: white;
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 24px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.rider-header-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
|
}
|
|
|
|
.rider-avatar-section {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.avatar-wrapper {
|
|
position: relative;
|
|
display: inline-block;
|
|
}
|
|
|
|
.rider-avatar {
|
|
border: 4px solid #fff;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.rider-avatar:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.avatar-status {
|
|
position: absolute;
|
|
bottom: -5px;
|
|
right: -5px;
|
|
background: white;
|
|
border-radius: 20px;
|
|
padding: 2px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.rider-basic-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.rider-name {
|
|
margin: 0 0 16px 0;
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: #2c3e50;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.rider-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.meta-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: #64748b;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.meta-item i {
|
|
color: #667eea;
|
|
width: 16px;
|
|
}
|
|
|
|
.certification-status .el-tag {
|
|
border-radius: 20px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.certification-status .el-tag i {
|
|
margin-right: 4px;
|
|
}
|
|
|
|
/* 统计数据卡片 */
|
|
.rider-stats-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
gap: 3px;
|
|
}
|
|
|
|
.stat-item {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
|
transition: all 0.3s ease;
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.stat-item:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.stat-item .stat-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.stat-item .stat-icon.today-orders {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
|
|
.stat-item .stat-icon.today-income {
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
}
|
|
|
|
.stat-item .stat-icon.total-orders {
|
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
}
|
|
|
|
.stat-item .stat-icon.total-income {
|
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
}
|
|
|
|
.stat-item .stat-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.stat-item .stat-number {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: #2c3e50;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.stat-item .stat-label {
|
|
font-size: 13px;
|
|
color: #64748b;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* 健康证信息卡片 */
|
|
.health-cert-section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.section-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.section-title i {
|
|
color: #667eea;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.health-cert-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
.cert-image {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.no-cert {
|
|
width: 200px;
|
|
height: 120px;
|
|
border: 2px dashed #cbd5e1;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #94a3b8;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.no-cert i {
|
|
font-size: 32px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.cert-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.cert-expire {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.cert-expire label {
|
|
font-weight: 500;
|
|
color: #475569;
|
|
}
|
|
|
|
.expire-date {
|
|
color: #059669;
|
|
font-weight: 600;
|
|
background: #d1fae5;
|
|
padding: 4px 12px;
|
|
border-radius: 16px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.no-expire {
|
|
color: #dc2626;
|
|
font-weight: 500;
|
|
background: #fee2e2;
|
|
padding: 4px 12px;
|
|
border-radius: 16px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* 评分信息 */
|
|
.rating-section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.rating-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 16px;
|
|
background: #fef3c7;
|
|
border-radius: 8px;
|
|
border: 1px solid #fbbf24;
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
.rider-stats-container .el-col {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.toolbar {
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.toolbar-left, .toolbar-right {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* 骑手详情对话框移动端适配 */
|
|
.rider-detail-dialog .el-dialog {
|
|
width: 95% !important;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.rider-detail-container {
|
|
padding: 16px;
|
|
max-height: 80vh;
|
|
}
|
|
|
|
.rider-header-card {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 16px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.rider-name {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 12px;
|
|
}
|
|
|
|
.stat-item {
|
|
padding: 16px;
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.stat-item .stat-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.stat-item .stat-number {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.health-cert-content {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.cert-image {
|
|
width: 100%;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.no-cert {
|
|
width: 100%;
|
|
max-width: 200px;
|
|
}
|
|
|
|
.cert-info {
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.rider-detail-dialog .el-dialog {
|
|
width: 98% !important;
|
|
}
|
|
|
|
.rider-detail-container {
|
|
padding: 12px;
|
|
}
|
|
|
|
.rider-header-card {
|
|
padding: 16px;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.stat-item .stat-number {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.stat-item .stat-label {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.health-cert-section,
|
|
.rating-section {
|
|
padding: 16px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
new Vue({
|
|
el: '#RiderManagement',
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
saving: false,
|
|
searchKeyword: '',
|
|
statusFilter: '',
|
|
certificationFilter: '',
|
|
storeFilter: '', // 店铺筛选
|
|
storeList: [], // 店铺列表
|
|
currentPage: 1,
|
|
pageSize: 20,
|
|
totalCount: 0,
|
|
selectedRiders: [],
|
|
selectedRider: null,
|
|
|
|
// 统计数据
|
|
statistics: {
|
|
totalRiders: 0,
|
|
onlineRiders: 0,
|
|
todayOrders: 0,
|
|
todayRevenue: '0'
|
|
},
|
|
|
|
// 骑手列表数据
|
|
riders: []
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
filteredRiders() {
|
|
let result = this.riders;
|
|
|
|
// 关键词搜索
|
|
if (this.searchKeyword) {
|
|
result = result.filter(rider =>
|
|
rider.rider_name.includes(this.searchKeyword) ||
|
|
rider.rider_phone.includes(this.searchKeyword)
|
|
);
|
|
}
|
|
|
|
// 状态筛选
|
|
if (this.statusFilter) {
|
|
result = result.filter(rider => rider.work_status == this.statusFilter);
|
|
}
|
|
|
|
// 认证状态筛选
|
|
if (this.certificationFilter) {
|
|
result = result.filter(rider => rider.auth_status == this.certificationFilter);
|
|
}
|
|
|
|
this.totalCount = result.length;
|
|
return result;
|
|
},
|
|
|
|
totalPages() {
|
|
return Math.ceil(this.totalCount / this.pageSize);
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
// API接口方法
|
|
async loadStatistics() {
|
|
try {
|
|
const response = await $.ajax({
|
|
url: '/Retail/Rider/getStatistics',
|
|
type: 'GET',
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
this.statistics = response.data;
|
|
} else {
|
|
console.error('获取统计数据失败:', response.message);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取统计数据异常:', error);
|
|
}
|
|
},
|
|
|
|
async loadRiders() {
|
|
this.loading = true;
|
|
try {
|
|
const response = await $.ajax({
|
|
url: '/Retail/Rider/getRiderList',
|
|
type: 'GET',
|
|
data: {
|
|
page: this.currentPage,
|
|
limit: this.pageSize,
|
|
keyword: this.searchKeyword,
|
|
status: this.statusFilter,
|
|
certification: this.certificationFilter,
|
|
store_id: this.storeFilter // 添加店铺筛选参数
|
|
},
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
this.riders = response.data.list || [];
|
|
this.totalCount = response.data.total || 0;
|
|
} else {
|
|
this.$message.error(response.message || '获取骑手列表失败');
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('网络错误,请重试');
|
|
console.error('获取骑手列表异常:', error);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async loadStoreList() {
|
|
try {
|
|
const response = await $.ajax({
|
|
url: '/Retail/Store/getStoreList',
|
|
type: 'POST',
|
|
data: { page: 1, limit: 1000 }, // 获取所有店铺
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
this.storeList = response.data.list || [];
|
|
} else {
|
|
console.error('获取店铺列表失败:', response.message);
|
|
}
|
|
} catch (error) {
|
|
console.error('获取店铺列表异常:', error);
|
|
}
|
|
},
|
|
|
|
async saveRiderData() {
|
|
this.saving = true;
|
|
try {
|
|
const url = this.riderForm.id ? '/Retail/Rider/updateRider' : '/Retail/Rider/addRider';
|
|
const response = await $.ajax({
|
|
url: url,
|
|
type: 'POST',
|
|
data: this.riderForm,
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
this.$message.success(this.riderForm.id ? '更新成功' : '添加成功');
|
|
this.showRiderDialog = false;
|
|
this.loadRiders();
|
|
this.loadStatistics();
|
|
} else {
|
|
this.$message.error(response.message || '保存失败');
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('网络错误,请重试');
|
|
console.error('保存骑手异常:', error);
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
async deleteRiderData(riderId) {
|
|
try {
|
|
const response = await $.ajax({
|
|
url: '/Retail/Rider/deleteRider',
|
|
type: 'POST',
|
|
data: { id: riderId },
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
this.$message.success('删除成功');
|
|
this.loadRiders();
|
|
this.loadStatistics();
|
|
} else {
|
|
this.$message.error(response.message || '删除失败');
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('网络错误,请重试');
|
|
console.error('删除骑手异常:', error);
|
|
}
|
|
},
|
|
|
|
async getRiderDetail(riderId) {
|
|
try {
|
|
const response = await $.ajax({
|
|
url: '/Retail/Rider/getRiderDetail',
|
|
type: 'GET',
|
|
data: { id: riderId },
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
return response.data;
|
|
} else {
|
|
this.$message.error(response.message || '获取详情失败');
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('网络错误,请重试');
|
|
console.error('获取骑手详情异常:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
async updateAuthStatus(riderId, authStatus, remark) {
|
|
this.saving = true;
|
|
try {
|
|
const response = await $.ajax({
|
|
url: '/Retail/Rider/updateAuthStatus',
|
|
type: 'POST',
|
|
data: {
|
|
id: riderId,
|
|
auth_status: authStatus,
|
|
auth_remark: remark
|
|
},
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1) {
|
|
this.$message.success('认证状态更新成功');
|
|
this.showAuthReviewDialog = false;
|
|
this.loadRiders();
|
|
} else {
|
|
this.$message.error(response.message || '更新失败');
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('网络错误,请重试');
|
|
console.error('更新认证状态异常:', error);
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
async exportRiderData() {
|
|
try {
|
|
this.$message.info('正在导出数据...');
|
|
|
|
const response = await $.ajax({
|
|
url: '/Retail/Rider/exportRiders',
|
|
type: 'GET',
|
|
data: {
|
|
keyword: this.searchKeyword,
|
|
status: this.statusFilter,
|
|
certification: this.certificationFilter
|
|
},
|
|
dataType: 'json'
|
|
});
|
|
|
|
if (response.code === 1 && response.data.url) {
|
|
// 创建下载链接
|
|
const link = document.createElement('a');
|
|
link.href = response.data.url;
|
|
link.download = response.data.filename || '骑手数据.xlsx';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
this.$message.success('导出成功');
|
|
} else {
|
|
this.$message.error(response.message || '导出失败');
|
|
}
|
|
} catch (error) {
|
|
this.$message.error('网络错误,请重试');
|
|
console.error('导出数据异常:', error);
|
|
}
|
|
},
|
|
|
|
// 状态相关方法
|
|
getStatusTagType(status) {
|
|
const types = {
|
|
'1': 'success', // 在线
|
|
'0': 'info', // 离线
|
|
'2': 'warning' // 忙碌
|
|
};
|
|
return types[status] || 'info';
|
|
},
|
|
|
|
getStatusText(status) {
|
|
const texts = {
|
|
'1': '在线',
|
|
'0': '离线',
|
|
'2': '忙碌'
|
|
};
|
|
return texts[status] || '未知';
|
|
},
|
|
|
|
getCertificationTagType(certification) {
|
|
const types = {
|
|
'2': 'success',
|
|
'0': 'danger',
|
|
'3': 'danger',
|
|
'1': 'warning'
|
|
};
|
|
return types[certification] || 'info';
|
|
},
|
|
|
|
getCertificationText(certification) {
|
|
const texts = {
|
|
'2': '已认证',
|
|
'0': '未认证',
|
|
'1': '待审核',
|
|
'3': '认证失败'
|
|
};
|
|
return texts[certification] || '未知';
|
|
},
|
|
|
|
// 格式化日期
|
|
formatDate(date) {
|
|
if (!date) return '';
|
|
return new Date(date).toLocaleDateString();
|
|
},
|
|
|
|
// 搜索和筛选
|
|
handleSearch() {
|
|
this.currentPage = 1;
|
|
this.loadRiders();
|
|
},
|
|
|
|
handleFilter() {
|
|
this.currentPage = 1;
|
|
this.loadRiders();
|
|
},
|
|
|
|
// 分页
|
|
handleSizeChange(val) {
|
|
this.pageSize = val;
|
|
this.currentPage = 1;
|
|
this.loadRiders();
|
|
},
|
|
|
|
handleCurrentChange(val) {
|
|
this.currentPage = val;
|
|
this.loadRiders();
|
|
},
|
|
|
|
// 选择
|
|
handleSelectionChange(val) {
|
|
this.selectedRiders = val;
|
|
},
|
|
|
|
// 骑手操作
|
|
showAddRiderDialog() {
|
|
layer.open({
|
|
type: 2,
|
|
title: '添加骑手',
|
|
content: '/Retail/Rider/edit.html',
|
|
area: ['calc(100% - 240px)', 'calc(100% - 66px)'],
|
|
offset: 'rb'
|
|
});
|
|
},
|
|
|
|
editRider(rider) {
|
|
layer.open({
|
|
type: 2,
|
|
title: '编辑骑手',
|
|
content: '/Retail/Rider/edit.html?id=' + rider.id,
|
|
area: ['calc(100% - 240px)', 'calc(100% - 66px)'],
|
|
offset: 'rb'
|
|
});
|
|
},
|
|
|
|
async viewRiderDetail(rider) {
|
|
layer.open({
|
|
type: 2,
|
|
title: '骑手详情',
|
|
content: '/Retail/Rider/detail.html?id=' + rider.id,
|
|
area: ['calc(100% - 240px)', 'calc(100% - 66px)'],
|
|
offset: 'rb'
|
|
});
|
|
},
|
|
|
|
deleteRider(rider) {
|
|
this.$confirm('确定要删除骑手 ' + rider.rider_name + ' 吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
this.deleteRiderData(rider.id);
|
|
}).catch(() => {
|
|
this.$message.info('已取消删除');
|
|
});
|
|
},
|
|
|
|
// 头像上传
|
|
handleAvatarSuccess(res, file) {
|
|
if (res.code === 1) {
|
|
this.riderForm.avatar = res.data.url;
|
|
this.$message.success('头像上传成功');
|
|
} else {
|
|
this.$message.error(res.message || '上传失败');
|
|
}
|
|
},
|
|
|
|
beforeAvatarUpload(file) {
|
|
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
|
|
const isLt2M = file.size / 1024 / 1024 < 2;
|
|
|
|
if (!isJPG) {
|
|
this.$message.error('上传头像图片只能是 JPG/PNG 格式!');
|
|
}
|
|
if (!isLt2M) {
|
|
this.$message.error('上传头像图片大小不能超过 2MB!');
|
|
}
|
|
return isJPG && isLt2M;
|
|
},
|
|
|
|
// 重置密码
|
|
resetPassword(rider) {
|
|
layer.open({
|
|
type: 2,
|
|
title: '重置密码',
|
|
content: '/Retail/Rider/password_reset.html?id=' + rider.id,
|
|
area: ['calc(100% - 240px)', 'calc(100% - 66px)'],
|
|
offset: 'rb'
|
|
});
|
|
},
|
|
|
|
// 实名认证
|
|
openAuthReviewDialog(rider) {
|
|
layer.open({
|
|
type: 2,
|
|
title: '实名认证审核',
|
|
content: '/Retail/Rider/auth_review.html?id=' + rider.id,
|
|
area: ['calc(100% - 240px)', 'calc(100% - 66px)'],
|
|
offset: 'rb'
|
|
});
|
|
},
|
|
|
|
refreshData() {
|
|
this.loadStatistics();
|
|
this.loadRiders();
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.loadStoreList(); // 加载店铺列表
|
|
this.refreshData();
|
|
// 将Vue实例挂载到window对象,供子窗口调用
|
|
window.vm = this;
|
|
|
|
// 监听来自子窗口的消息
|
|
window.addEventListener('message', (event) => {
|
|
if (event.data && event.data.action === 'refreshRiderData') {
|
|
console.log('收到子窗口刷新请求');
|
|
this.refreshData();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |