|
|
@@ -0,0 +1,1105 @@
|
|
|
+<template>
|
|
|
+ <div class="page-container">
|
|
|
+ <el-card shadow="never" class="table-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="left-panel">
|
|
|
+ <span class="title">履约者池</span>
|
|
|
+ <el-tag type="info" effect="plain" style="margin-left: 10px;">共 1205 人</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="right-panel">
|
|
|
+ <el-button type="primary" icon="Plus" style="margin-right: 15px" @click="handleCreate">新增履约者</el-button>
|
|
|
+ <el-input
|
|
|
+ v-model="searchKey"
|
|
|
+ placeholder="搜索姓名/手机号/身份证"
|
|
|
+ class="search-input"
|
|
|
+ prefix-icon="Search"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ <el-select v-model="filterCity" placeholder="所属城市" style="width: 120px; margin-left: 10px;">
|
|
|
+ <el-option label="所有城市" value="" />
|
|
|
+ <el-option label="北京市" value="beijing" />
|
|
|
+ <el-option label="上海市" value="shanghai" />
|
|
|
+ <el-option label="深圳市" value="shenzhen" />
|
|
|
+ </el-select>
|
|
|
+ <el-select v-model="filterStation" placeholder="所属站点" style="width: 150px; margin-left: 10px;">
|
|
|
+ <el-option label="所有站点" value="" />
|
|
|
+ <el-option label="北京朝阳一站" value="bj-cy-01" />
|
|
|
+ <el-option label="上海浦东一站" value="sh-pd-01" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tab切换 (无图标) -->
|
|
|
+ <el-tabs v-model="activeTab" class="status-tabs" @tab-click="handleTabClick">
|
|
|
+ <el-tab-pane label="全部" name="all" />
|
|
|
+ <el-tab-pane label="休息" name="resting" />
|
|
|
+ <el-tab-pane label="接单中" name="busy" />
|
|
|
+ <el-tab-pane label="禁用" name="disabled" />
|
|
|
+ </el-tabs>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-table :data="filteredTableData" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }">
|
|
|
+ <el-table-column label="基本信息" width="280">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="user-info">
|
|
|
+ <el-avatar :size="45" :src="scope.row.avatar">{{ scope.row.name.charAt(0) }}</el-avatar>
|
|
|
+ <div class="text-col">
|
|
|
+ <div class="name-row">
|
|
|
+ <span class="name">{{ scope.row.name }}</span>
|
|
|
+ <span class="gender-tag">
|
|
|
+ <el-icon v-if="scope.row.gender === 'male'" color="#409eff"><Male /></el-icon>
|
|
|
+ <el-icon v-else color="#f56c6c"><Female /></el-icon>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="tags-row" style="margin: 3px 0">
|
|
|
+ <!-- work type -->
|
|
|
+ <el-tag size="small" :type="scope.row.workType === 'full_time' ? 'warning' : 'info'" effect="light" style="margin-right: 5px">
|
|
|
+ {{ scope.row.workType === 'full_time' ? '全职专送' : '兼职众包' }}
|
|
|
+ </el-tag>
|
|
|
+ <!-- 等级展示 -->
|
|
|
+ <el-tag size="small" :type="getLevelType(scope.row.level)" effect="plain" class="level-tag">
|
|
|
+ {{ getLevelText(scope.row.level) }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="sub-text">{{ scope.row.age }}岁 | {{ scope.row.phone }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="资质信息" width="220">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="auth-row">
|
|
|
+ <div class="auth-card" :class="{ active: scope.row.authId }">
|
|
|
+ <el-icon><Postcard /></el-icon> 身份证
|
|
|
+ </div>
|
|
|
+ <div class="auth-card" :class="{ active: scope.row.authQual }">
|
|
|
+ <el-icon><Medal /></el-icon> 资质证
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="sub-text" style="margin-top:5px;">ID: {{ scope.row.idNo }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="服务区域" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="text-col">
|
|
|
+ <span style="font-size: 13px; color: #333;">{{ scope.row.city }}</span>
|
|
|
+ <span style="font-size: 12px; color: #999;">{{ scope.row.station }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="技能标签" min-width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag
|
|
|
+ v-for="tag in scope.row.tags"
|
|
|
+ :key="tag.name"
|
|
|
+ :type="tag.type"
|
|
|
+ size="small"
|
|
|
+ class="skill-tag"
|
|
|
+ effect="plain"
|
|
|
+ >
|
|
|
+ {{ tag.name }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="订单数据" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="finance-item">服务单: <span class="num">{{ scope.row.orderCount }}</span></div>
|
|
|
+ <div class="finance-item">拒/转单: <span class="num error">{{ scope.row.rejectCount }}</span></div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="账户资产" width="160">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="finance-item">积分: <span class="num">{{ scope.row.points }}</span></div>
|
|
|
+ <div class="finance-item">余额: <span class="num">¥{{ scope.row.balance }}</span></div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="status" label="状态" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="status-cell">
|
|
|
+ <div class="status-dot" :class="scope.row.status"></div>
|
|
|
+ <span class="status-text">{{ getStatusText(scope.row.status) }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="操作" width="240" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="op-cell">
|
|
|
+ <el-button link type="primary" size="small" @click="handleDetail(scope.row)">详情</el-button>
|
|
|
+ <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
|
|
+ <el-button link type="warning" size="small" @click="handleReward(scope.row)">奖惩</el-button>
|
|
|
+ <el-dropdown trigger="click" @command="(cmd) => handleCommand(cmd, scope.row)">
|
|
|
+ <el-button link type="primary">更多<el-icon class="el-icon--right"><arrow-down /></el-icon></el-button>
|
|
|
+ <template #dropdown>
|
|
|
+ <el-dropdown-menu>
|
|
|
+ <el-dropdown-item command="adjustPoints">修改积分</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="adjustBalance">余额增减</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-if="scope.row.status !== 'disabled'" command="disable" divided style="color: #f56c6c">禁用账号</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-else command="enable" divided style="color: #67c23a">启用账号</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="resetPwd">重置密码</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </template>
|
|
|
+ </el-dropdown>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="pagination-container">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="currentPage"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ :total="total"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 详情侧边栏 Drawer -->
|
|
|
+ <el-drawer
|
|
|
+ v-model="detailVisible"
|
|
|
+ title="履约者档案详情"
|
|
|
+ size="750px"
|
|
|
+ direction="rtl"
|
|
|
+ custom-class="detail-drawer"
|
|
|
+ >
|
|
|
+ <div class="drawer-content" v-if="currentItem">
|
|
|
+ <!-- 头部概览 -->
|
|
|
+ <div class="user-header-card">
|
|
|
+ <el-avatar :size="70" :src="currentItem.avatar" class="header-avatar">{{ currentItem.name?.charAt(0) }}</el-avatar>
|
|
|
+ <div class="header-info">
|
|
|
+ <div class="top-row">
|
|
|
+ <span class="user-name">{{ currentItem.name }}</span>
|
|
|
+ <el-tag size="small" :type="currentItem.gender === 'male' ? '' : 'danger'" effect="plain" round style="margin-left: 8px;">
|
|
|
+ {{ currentItem.gender === 'male' ? '男' : '女' }} {{ currentItem.age }}岁
|
|
|
+ </el-tag>
|
|
|
+ <span class="status-badge" :class="currentItem.status">{{ getStatusText(currentItem.status) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="sub-row">
|
|
|
+ <span class="info-item"><el-icon><Iphone /></el-icon> {{ currentItem.phone }}</span>
|
|
|
+ <span class="divider">|</span>
|
|
|
+ <span class="info-item"><el-icon><Location /></el-icon> {{ currentItem.city }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="tags-row">
|
|
|
+ <el-tag size="small" :type="getLevelType(currentItem.level)" effect="dark">{{ getLevelText(currentItem.level) }}</el-tag>
|
|
|
+ <el-tag size="small" type="warning" effect="plain" v-if="currentItem.workType === 'full_time'" style="margin-left:5px">全职专送</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 核心数据指标 -->
|
|
|
+ <div class="data-metrics-row">
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="val text-primary">{{ currentItem.points }}</div>
|
|
|
+ <div class="lbl">当前积分</div>
|
|
|
+ </div>
|
|
|
+ <div class="divider-v"></div>
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="val text-danger">¥{{ currentItem.balance }}</div>
|
|
|
+ <div class="lbl">账户余额</div>
|
|
|
+ </div>
|
|
|
+ <div class="divider-v"></div>
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="val">{{ currentItem.orderCount }}</div>
|
|
|
+ <div class="lbl">服务单量</div>
|
|
|
+ </div>
|
|
|
+ <div class="divider-v"></div>
|
|
|
+ <div class="metric-item">
|
|
|
+ <div class="val text-warning">{{ currentItem.rejectCount || 0 }}</div>
|
|
|
+ <div class="lbl">拒绝单量</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-tabs v-model="activeDetailTab" class="detail-tabs">
|
|
|
+ <el-tab-pane label="档案概览" name="info">
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
+ <div class="section-block">
|
|
|
+ <div class="section-title">基础信息</div>
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
+ <el-descriptions-item label="身份证号">{{ currentItem.idNo }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="真实姓名">{{ currentItem.realName || currentItem.name }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="归属站点">{{ currentItem.station }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="证件有效期">{{ currentItem.idExpiry || '2030-01-01' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="入驻时间">2024-05-12</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="工作性质">{{ currentItem.workType === 'full_time' ? '全职' : '兼职' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section-block">
|
|
|
+ <div class="section-title">实名认证</div>
|
|
|
+ <div class="cert-row">
|
|
|
+ <div class="cert-item" @click="handleViewImage(currentItem.idCardFront)">
|
|
|
+ <el-image :src="currentItem.idCardFront || ''" fit="cover" class="cert-img">
|
|
|
+ <template #error><div class="img-slot"><el-icon><Picture /></el-icon></div></template>
|
|
|
+ </el-image>
|
|
|
+ <div class="cert-name">身份证人像面</div>
|
|
|
+ </div>
|
|
|
+ <div class="cert-item" @click="handleViewImage(currentItem.idCardBack)">
|
|
|
+ <el-image :src="currentItem.idCardBack || ''" fit="cover" class="cert-img">
|
|
|
+ <template #error><div class="img-slot"><el-icon><Picture /></el-icon></div></template>
|
|
|
+ </el-image>
|
|
|
+ <div class="cert-name">身份证国徽面</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section-block">
|
|
|
+ <div class="section-title">资质认证</div>
|
|
|
+ <div class="cert-row" v-if="currentItem.qualImages && currentItem.qualImages.length">
|
|
|
+ <div class="cert-item" v-for="(img, index) in currentItem.qualImages" :key="index" @click="handleViewImage(img)">
|
|
|
+ <el-image :src="img" fit="cover" class="cert-img">
|
|
|
+ <template #error><div class="img-slot"><el-icon><Picture /></el-icon></div></template>
|
|
|
+ </el-image>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="empty-text">暂无资质图片</div>
|
|
|
+ </div>
|
|
|
+ <div class="section-block">
|
|
|
+ <div class="section-title">技能标签</div>
|
|
|
+ <div class="tag-list">
|
|
|
+ <el-tag v-for="tag in currentItem.tags" :key="tag.name" :type="tag.type" size="large" style="margin-right: 12px; margin-bottom: 8px;">{{ tag.name }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="服务订单" name="orders">
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
+ <el-table :data="mockOrders" stripe style="width: 100%" :header-cell-style="{background:'#f5f7fa', color:'#606266'}">
|
|
|
+ <el-table-column prop="orderNo" label="订单号" width="160" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="serviceName" label="服务项目" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="serviceFee" label="收入" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span style="color: #67c23a; font-weight: bold; font-size: 15px;">+{{ row.serviceFee }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="time" label="时间" width="160" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="status" label="状态" width="90">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag v-if="row.status==='completed'" type="success" size="small">完成</el-tag>
|
|
|
+ <el-tag v-else type="info" size="small">取消</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="积分记录" name="pointLogs">
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
+ <el-table :data="mockPointLogs" stripe style="width: 100%" :header-cell-style="{background:'#f5f7fa', color:'#606266'}">
|
|
|
+ <el-table-column prop="time" label="变动时间" width="180" />
|
|
|
+ <el-table-column prop="bizType" label="业务类型" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getBizTypeTag(row.bizType)" size="small" effect="plain">{{ row.bizTypeName }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="amount" label="变动数值" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :style="{ color: row.amount > 0 ? '#67c23a' : '#f56c6c', fontWeight: 'bold', fontSize: '15px' }">
|
|
|
+ {{ row.amount > 0 ? '+' : '' }}{{ row.amount }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="reason" label="变动原因" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="operator" label="操作人" width="120" />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="余额变动" name="balanceLogs">
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
+ <el-table :data="mockBalanceLogs" stripe style="width: 100%" :header-cell-style="{background:'#f5f7fa', color:'#606266'}">
|
|
|
+ <el-table-column prop="time" label="变动时间" width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="subType" label="资金类型" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="row.amount > 0 ? 'success' : 'danger'" size="small" effect="plain">{{ row.subTypeName }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="amount" label="变动金额" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :style="{ color: row.amount > 0 ? '#67c23a' : '#f56c6c', fontWeight: 'bold', fontSize: '15px' }">
|
|
|
+ {{ row.amount > 0 ? '+' : '' }}{{ row.amount }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="balanceAfter" label="变动后余额" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>¥{{ row.balanceAfter }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="reason" label="备注说明" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="operator" label="操作人" width="100" />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <el-tab-pane label="奖惩记录" name="rewards">
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
+ <el-table :data="mockRewards" stripe style="width: 100%" :header-cell-style="{background:'#f5f7fa', color:'#606266'}">
|
|
|
+ <el-table-column prop="time" label="操作时间" width="180" />
|
|
|
+ <el-table-column prop="type" label="奖惩类型" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="row.type==='reward' ? 'success' : 'danger'" size="small">{{ row.type === 'reward' ? '奖励' : '惩罚' }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="target" label="关联项目" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag type="info" size="small" effect="plain">{{ row.target === 'points' ? '积分' : '余额' }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="val" label="涉及数值" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :style="{ color: row.type==='reward' ? '#67c23a' : '#f56c6c', fontWeight: 'bold' }">
|
|
|
+ {{ row.type === 'reward' ? '+' : '-' }}{{ row.val }} {{ row.target === 'points' ? '分' : '元' }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="reason" label="奖惩原因" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="operator" label="操作人" width="100" />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <!-- 编辑弹窗 -->
|
|
|
+ <el-dialog v-model="editDialog.visible" title="编辑履约者" width="600px" top="5vh">
|
|
|
+ <el-form :model="editDialog.form" label-width="90px">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="姓名" required>
|
|
|
+ <el-input v-model="editDialog.form.name" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="手机号" required>
|
|
|
+ <el-input v-model="editDialog.form.phone" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-form-item label="登录密码">
|
|
|
+ <el-input v-model="editDialog.form.password" type="password" placeholder="不修改请留空" show-password />
|
|
|
+ </el-form-item>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="性别">
|
|
|
+ <el-radio-group v-model="editDialog.form.gender">
|
|
|
+ <el-radio label="male">男</el-radio>
|
|
|
+ <el-radio label="female">女</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="身份证号">
|
|
|
+ <el-input v-model="editDialog.form.idNo" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="服务城市">
|
|
|
+ <el-select v-model="editDialog.form.city" style="width: 100%">
|
|
|
+ <el-option label="北京市" value="北京市" />
|
|
|
+ <el-option label="上海市" value="上海市" />
|
|
|
+ <el-option label="深圳市" value="深圳市" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="归属站点">
|
|
|
+ <el-select v-model="editDialog.form.station" style="width: 100%">
|
|
|
+ <el-option label="北京朝阳一站" value="北京朝阳一站" />
|
|
|
+ <el-option label="上海浦东一站" value="上海浦东一站" />
|
|
|
+ <el-option label="北京海淀二站" value="北京海淀二站" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="等级">
|
|
|
+ <el-select v-model="editDialog.form.level" style="width: 100%">
|
|
|
+ <el-option label="金牌" value="gold" />
|
|
|
+ <el-option label="银牌" value="silver" />
|
|
|
+ <el-option label="铜牌" value="bronze" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="当前状态">
|
|
|
+ <el-select v-model="editDialog.form.status" style="width: 100%">
|
|
|
+ <el-option label="接单中" value="busy" />
|
|
|
+ <el-option label="休息" value="resting" />
|
|
|
+ <el-option label="禁用" value="disabled" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-form-item label="资质状态">
|
|
|
+ <el-checkbox v-model="editDialog.form.authId">身份证认证</el-checkbox>
|
|
|
+ <el-checkbox v-model="editDialog.form.authQual">专业资质认证</el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="技能标签">
|
|
|
+ <el-checkbox-group v-model="editDialog.form.tags">
|
|
|
+ <el-checkbox label="行为矫正" value="行为矫正" />
|
|
|
+ <el-checkbox label="驾驶" value="驾驶" />
|
|
|
+ <el-checkbox label="摄影" value="摄影" />
|
|
|
+ <el-checkbox label="洗护护理" value="洗护护理" />
|
|
|
+ <el-checkbox label="精细美容" value="精细美容" />
|
|
|
+ <el-checkbox label="基础喂遛" value="基础喂遛" />
|
|
|
+ </el-checkbox-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="editDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="saveEdit">保存变更</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 奖惩弹窗 -->
|
|
|
+ <el-dialog v-model="rewardDialog.visible" title="人工奖惩操作" width="450px">
|
|
|
+ <div class="user-preview-box">
|
|
|
+ 当前操作对象:<b>{{ rewardDialog.userName }}</b>
|
|
|
+ </div>
|
|
|
+ <el-form :model="rewardDialog.form" label-width="80px" style="margin-top: 20px;">
|
|
|
+ <el-form-item label="操作类型">
|
|
|
+ <el-radio-group v-model="rewardDialog.form.type">
|
|
|
+ <el-radio label="reward">奖励 (增加)</el-radio>
|
|
|
+ <el-radio label="punish">惩罚 (扣除)</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调整项目">
|
|
|
+ <el-radio-group v-model="rewardDialog.form.target">
|
|
|
+ <el-radio label="points">积分</el-radio>
|
|
|
+ <el-radio label="balance">金额 (元)</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="数额" required>
|
|
|
+ <el-input-number v-model="rewardDialog.form.amount" :min="1" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="原因备注" required>
|
|
|
+ <el-input v-model="rewardDialog.form.reason" type="textarea" placeholder="请输入奖惩原因..." />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="rewardDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitReward">确认执行</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 新增履约者弹窗 -->
|
|
|
+ <el-dialog v-model="createDialog.visible" title="新增履约者" width="500px">
|
|
|
+ <el-form :model="createDialog.form" label-width="80px">
|
|
|
+ <el-form-item label="姓名" required>
|
|
|
+ <el-input v-model="createDialog.form.name" placeholder="请输入真实姓名" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="手机号" required>
|
|
|
+ <el-input v-model="createDialog.form.phone" placeholder="作为登录账号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="登录密码" required>
|
|
|
+ <el-input v-model="createDialog.form.password" show-password placeholder="设置初始密码" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="性别">
|
|
|
+ <el-radio-group v-model="createDialog.form.gender">
|
|
|
+ <el-radio label="male">男</el-radio>
|
|
|
+ <el-radio label="female">女</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="服务城市">
|
|
|
+ <el-select v-model="createDialog.form.city" style="width: 100%">
|
|
|
+ <el-option label="北京市" value="北京市" />
|
|
|
+ <el-option label="上海市" value="上海市" />
|
|
|
+ <el-option label="深圳市" value="深圳市" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="归属站点">
|
|
|
+ <el-select v-model="createDialog.form.station" style="width: 100%">
|
|
|
+ <el-option label="北京朝阳一站" value="北京朝阳一站" />
|
|
|
+ <el-option label="上海浦东一站" value="上海浦东一站" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="createDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitCreate">确认创建</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 积分调整弹窗 -->
|
|
|
+ <el-dialog v-model="pointsDialog.visible" title="修改积分" width="400px">
|
|
|
+ <el-form :model="pointsDialog.form" label-width="80px">
|
|
|
+ <el-form-item label="当前积分">
|
|
|
+ <strong>{{ pointsDialog.currentRow?.points }}</strong>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调整方式">
|
|
|
+ <el-radio-group v-model="pointsDialog.form.type">
|
|
|
+ <el-radio label="add">增加</el-radio>
|
|
|
+ <el-radio label="reduce">扣除</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调整数值" required>
|
|
|
+ <el-input-number v-model="pointsDialog.form.amount" :min="1" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调整原因" required>
|
|
|
+ <el-input v-model="pointsDialog.form.reason" type="textarea" placeholder="请输入备注说明" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="pointsDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitPointsAdjust">确认调整</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 余额调整弹窗 -->
|
|
|
+ <el-dialog v-model="balanceDialog.visible" title="余额增减" width="450px">
|
|
|
+ <el-form :model="balanceDialog.form" label-width="80px">
|
|
|
+ <el-form-item label="当前余额">
|
|
|
+ <span style="color: #f56c6c; font-weight: bold">¥{{ balanceDialog.currentRow?.balance.toFixed(2) }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="扣减类型">
|
|
|
+ <el-radio-group v-model="balanceDialog.form.type" @change="balanceDialog.form.subType = balanceDialog.form.type === 'add' ? 'reward' : 'punish'">
|
|
|
+ <el-radio label="add">增加</el-radio>
|
|
|
+ <el-radio label="reduce">减少</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="调整类型">
|
|
|
+ <el-radio-group v-model="balanceDialog.form.subType">
|
|
|
+ <template v-if="balanceDialog.form.type === 'add'">
|
|
|
+ <el-radio label="reward">奖励</el-radio>
|
|
|
+ <el-radio label="other">其他</el-radio>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-radio label="punish">惩罚</el-radio>
|
|
|
+ <el-radio label="salary">工资发放</el-radio>
|
|
|
+ <el-radio label="other">其他</el-radio>
|
|
|
+ </template>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="金额" required>
|
|
|
+ <el-input-number v-model="balanceDialog.form.amount" :min="0.01" :precision="2" :step="10" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注说明" required>
|
|
|
+ <el-input v-model="balanceDialog.form.reason" type="textarea" placeholder="请输入资金变动说明" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="balanceDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitBalanceAdjust">确认执行</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+
|
|
|
+const searchKey = ref('')
|
|
|
+const filterCity = ref('')
|
|
|
+const filterStation = ref('')
|
|
|
+const activeTab = ref('all')
|
|
|
+
|
|
|
+const currentPage = ref(1)
|
|
|
+const pageSize = ref(10)
|
|
|
+const total = ref(1205)
|
|
|
+
|
|
|
+const handleSizeChange = (val) => { console.log(`每页 ${val} 条`) }
|
|
|
+const handleCurrentChange = (val) => { console.log(`当前页: ${val}`) }
|
|
|
+
|
|
|
+// Drawer State instead of Dialog
|
|
|
+const detailVisible = ref(false)
|
|
|
+const activeDetailTab = ref('info')
|
|
|
+const currentItem = ref(null)
|
|
|
+
|
|
|
+const tableData = ref([
|
|
|
+ {
|
|
|
+ id: 101,
|
|
|
+ name: '王大力',
|
|
|
+ gender: 'male',
|
|
|
+ age: 28,
|
|
|
+ workType: 'full_time',
|
|
|
+ phone: '13566668888',
|
|
|
+ level: 'gold',
|
|
|
+ authId: true,
|
|
|
+ authQual: true,
|
|
|
+ idNo: '1101************12',
|
|
|
+ city: '北京市',
|
|
|
+ station: '北京朝阳一站',
|
|
|
+ idExpiry: '2028-12-31',
|
|
|
+ realName: '王大力',
|
|
|
+ tags: [
|
|
|
+ { name: '行为矫正', type: 'warning' },
|
|
|
+ { name: '驾驶', type: 'info' },
|
|
|
+ { name: '摄影', type: 'success' }
|
|
|
+ ],
|
|
|
+ points: 2450,
|
|
|
+ balance: 1280.50,
|
|
|
+ orderCount: 1240,
|
|
|
+ rejectCount: 2,
|
|
|
+ status: 'busy',
|
|
|
+ avatar: '',
|
|
|
+ idCardFront: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
|
|
|
+ idCardBack: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
|
|
|
+ qualImages: [
|
|
|
+ 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
|
|
|
+ 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg'
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 102,
|
|
|
+ name: '张小美',
|
|
|
+ gender: 'female',
|
|
|
+ age: 24,
|
|
|
+ workType: 'part_time',
|
|
|
+ phone: '13612345678',
|
|
|
+ level: 'silver',
|
|
|
+ authId: true,
|
|
|
+ authQual: false,
|
|
|
+ idNo: '3101************34',
|
|
|
+ city: '上海市',
|
|
|
+ station: '上海浦东一站',
|
|
|
+ tags: [
|
|
|
+ { name: '洗护护理', type: 'primary' },
|
|
|
+ { name: '精细美容', type: 'danger' }
|
|
|
+ ],
|
|
|
+ points: 890,
|
|
|
+ balance: 320.00,
|
|
|
+ orderCount: 450,
|
|
|
+ rejectCount: 0,
|
|
|
+ status: 'resting',
|
|
|
+ avatar: '',
|
|
|
+ idCardFront: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 103,
|
|
|
+ name: '李建国',
|
|
|
+ gender: 'male',
|
|
|
+ age: 35,
|
|
|
+ workType: 'full_time',
|
|
|
+ phone: '13987654321',
|
|
|
+ level: 'bronze',
|
|
|
+ authId: true,
|
|
|
+ authQual: true,
|
|
|
+ idNo: '1101************99',
|
|
|
+ city: '北京市',
|
|
|
+ station: '北京海淀二站',
|
|
|
+ tags: [
|
|
|
+ { name: '基础喂遛', type: '' }
|
|
|
+ ],
|
|
|
+ points: 120,
|
|
|
+ balance: 50.00,
|
|
|
+ orderCount: 56,
|
|
|
+ rejectCount: 5,
|
|
|
+ status: 'disabled',
|
|
|
+ avatar: ''
|
|
|
+ }
|
|
|
+])
|
|
|
+
|
|
|
+// Mock Data for Detail Tabs
|
|
|
+const mockOrders = ref([
|
|
|
+ { orderNo: 'ORD20240204001', serviceName: '上门洗澡-中型犬', amount: 88, serviceFee: 66, time: '2024-02-04 10:00', status: 'completed' },
|
|
|
+ { orderNo: 'ORD20240204002', serviceName: '家庭寄养-3天', amount: 240, serviceFee: 190, time: '2024-02-03 14:30', status: 'completed' },
|
|
|
+ { orderNo: 'ORD20240203009', serviceName: '遛狗服务-1小时', amount: 45, serviceFee: 0, time: '2024-02-03 09:00', status: 'cancelled' },
|
|
|
+])
|
|
|
+
|
|
|
+const mockRewards = ref([
|
|
|
+ { time: '2024-02-01 10:00:00', type: 'reward', target: 'points', val: 20, reason: '月度全勤奖励', operator: '系统' },
|
|
|
+ { time: '2024-01-25 15:30:12', type: 'punish', target: 'balance', val: 50.00, reason: '用户投诉服务态度差', operator: 'admin' },
|
|
|
+])
|
|
|
+
|
|
|
+const mockBalanceLogs = ref([
|
|
|
+ { time: '2024-02-04 11:30:00', subType: 'settle', subTypeName: '服务结算', amount: 66.00, balanceAfter: 1280.50, reason: '订单ORD20240204001结算', operator: '系统' },
|
|
|
+ { time: '2024-02-04 10:00:00', subType: 'withdraw', subTypeName: '提现', amount: -200.00, balanceAfter: 1214.50, reason: '用户提现申请', operator: '系统' },
|
|
|
+ { time: '2024-02-03 15:00:00', subType: 'salary', subTypeName: '工资发放', amount: 3500.00, balanceAfter: 4714.50, reason: '2024年1月工资发放', operator: '财务' },
|
|
|
+ { time: '2024-01-25 15:30:12', subType: 'punish', subTypeName: '惩罚', amount: -50.00, balanceAfter: 1224.50, reason: '用户投诉服务态度差', operator: 'admin' },
|
|
|
+ { time: '2024-01-20 10:00:00', subType: 'reward', subTypeName: '奖励', amount: 100.00, balanceAfter: 1274.50, reason: '季度优秀员工奖励', operator: 'admin' },
|
|
|
+])
|
|
|
+
|
|
|
+const mockPointLogs = ref([
|
|
|
+ { time: '2024-02-04 10:00:00', bizType: 'order', bizTypeName: '订单完成', amount: 50, reason: '完成订单 ORD20240204001', operator: '系统' },
|
|
|
+ { time: '2024-02-03 12:00:00', bizType: 'reward', bizTypeName: '奖励', amount: 20, reason: '获得用户5星好评', operator: '系统' },
|
|
|
+ { time: '2024-02-01 10:00:00', bizType: 'other', bizTypeName: '其他', amount: 100, reason: '系统补偿', operator: '系统' },
|
|
|
+ { time: '2024-01-20 15:30:00', bizType: 'punish', bizTypeName: '惩罚', amount: -10, reason: '接单后无故取消', operator: 'admin' }
|
|
|
+])
|
|
|
+
|
|
|
+const getBizTypeTag = (type) => {
|
|
|
+ // Points: order, reward, punish, other
|
|
|
+ // Balance: settle, withdraw, salary, reward, punish, other
|
|
|
+ const map = {
|
|
|
+ order: '',
|
|
|
+ reward: 'success',
|
|
|
+ salary: 'success',
|
|
|
+ settle: 'success',
|
|
|
+ punish: 'danger',
|
|
|
+ withdraw: 'warning',
|
|
|
+ other: 'info'
|
|
|
+ }
|
|
|
+ return map[type] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+const rewardDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ userName: '',
|
|
|
+ form: {
|
|
|
+ type: 'punish',
|
|
|
+ target: 'points',
|
|
|
+ amount: 10,
|
|
|
+ reason: ''
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const editDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ form: {
|
|
|
+ id: null,
|
|
|
+ name: '',
|
|
|
+ phone: '',
|
|
|
+ gender: '',
|
|
|
+ idNo: '',
|
|
|
+ city: '',
|
|
|
+ station: '',
|
|
|
+ level: '',
|
|
|
+ status: '',
|
|
|
+ authId: false,
|
|
|
+ authQual: false,
|
|
|
+ tags: []
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const createDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ form: {
|
|
|
+ name: '',
|
|
|
+ phone: '',
|
|
|
+ password: '',
|
|
|
+ city: '北京市',
|
|
|
+ station: '',
|
|
|
+ gender: 'male'
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const pointsDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ currentRow: null,
|
|
|
+ form: { type: 'add', amount: 0, reason: '' }
|
|
|
+})
|
|
|
+
|
|
|
+const balanceDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ currentRow: null,
|
|
|
+ form: { type: 'add', subType: 'reward', amount: 0, reason: '' }
|
|
|
+})
|
|
|
+
|
|
|
+const getStatusText = (status) => {
|
|
|
+ const map = { busy: '接单中', resting: '休息', disabled: '禁用', frozen: '冻结' }
|
|
|
+ return map[status] || '未知'
|
|
|
+}
|
|
|
+
|
|
|
+const getLevelText = (level) => {
|
|
|
+ const map = { gold: 'Lv.3 金牌', silver: 'Lv.2 银牌', bronze: 'Lv.1 铜牌' }
|
|
|
+ return map[level] || 'Lv.0 普通'
|
|
|
+}
|
|
|
+
|
|
|
+const getLevelType = (level) => {
|
|
|
+ const map = { gold: 'warning', silver: 'info', bronze: 'danger' }
|
|
|
+ return map[level] || 'info'
|
|
|
+}
|
|
|
+
|
|
|
+const handleTabClick = (tab) => {
|
|
|
+ console.log('Tab switch:', tab.props.name)
|
|
|
+}
|
|
|
+
|
|
|
+const filteredTableData = computed(() => {
|
|
|
+ let data = tableData.value
|
|
|
+ // Status Filter
|
|
|
+ if (activeTab.value !== 'all') {
|
|
|
+ data = data.filter(item => item.status === activeTab.value)
|
|
|
+ }
|
|
|
+ // Search Filter
|
|
|
+ if (searchKey.value) {
|
|
|
+ const key = searchKey.value.toLowerCase()
|
|
|
+ data = data.filter(item =>
|
|
|
+ item.name.toLowerCase().includes(key) ||
|
|
|
+ item.phone.includes(key) ||
|
|
|
+ (item.idNo && item.idNo.includes(key))
|
|
|
+ )
|
|
|
+ }
|
|
|
+ // City & Station Filter
|
|
|
+ if (filterCity.value) {
|
|
|
+ // Simple mapping for demo, usually would match exact city code/name
|
|
|
+ if(filterCity.value === 'beijing') data = data.filter(item => item.city.includes('北京'))
|
|
|
+ if(filterCity.value === 'shanghai') data = data.filter(item => item.city.includes('上海'))
|
|
|
+ if(filterCity.value === 'shenzhen') data = data.filter(item => item.city.includes('深圳'))
|
|
|
+ }
|
|
|
+ if (filterStation.value) {
|
|
|
+ if(filterStation.value === 'bj-cy-01') data = data.filter(item => item.station.includes('北京朝阳一站'))
|
|
|
+ if(filterStation.value === 'sh-pd-01') data = data.filter(item => item.station.includes('上海浦东一站'))
|
|
|
+ }
|
|
|
+ return data
|
|
|
+})
|
|
|
+
|
|
|
+const handleDetail = (row) => {
|
|
|
+ currentItem.value = row
|
|
|
+ activeDetailTab.value = 'info'
|
|
|
+ detailVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const handleEdit = (row) => {
|
|
|
+ editDialog.form = {
|
|
|
+ id: row.id,
|
|
|
+ name: row.name,
|
|
|
+ phone: row.phone,
|
|
|
+ password: '', // Reset password field
|
|
|
+ gender: row.gender,
|
|
|
+ idNo: row.idNo,
|
|
|
+ city: row.city,
|
|
|
+ station: row.station,
|
|
|
+ level: row.level,
|
|
|
+ status: row.status,
|
|
|
+ authId: row.authId,
|
|
|
+ authQual: row.authQual,
|
|
|
+ tags: row.tags ? row.tags.map(t => t.name) : []
|
|
|
+ }
|
|
|
+ editDialog.visible = true
|
|
|
+}
|
|
|
+
|
|
|
+const handleCreate = () => {
|
|
|
+ createDialog.form = { name: '', phone: '', password: '', city: '北京市', station: '', gender: 'male' }
|
|
|
+ createDialog.visible = true
|
|
|
+}
|
|
|
+
|
|
|
+const submitCreate = () => {
|
|
|
+ if(!createDialog.form.name || !createDialog.form.phone || !createDialog.form.password) {
|
|
|
+ ElMessage.warning('请填写完整信息')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // Simulate creation
|
|
|
+ tableData.value.unshift({
|
|
|
+ id: Date.now(),
|
|
|
+ ...createDialog.form,
|
|
|
+ level: 'bronze',
|
|
|
+ status: 'online',
|
|
|
+ points: 0,
|
|
|
+ balance: 0,
|
|
|
+ orderCount: 0,
|
|
|
+ rejectCount: 0,
|
|
|
+ status: 'online',
|
|
|
+ avatar: '',
|
|
|
+ authId: false,
|
|
|
+ authQual: false,
|
|
|
+ tags: []
|
|
|
+ })
|
|
|
+ createDialog.visible = false
|
|
|
+ ElMessage.success('创建成功')
|
|
|
+}
|
|
|
+
|
|
|
+const saveEdit = () => {
|
|
|
+ const idx = tableData.value.findIndex(item => item.id === editDialog.form.id)
|
|
|
+ if(idx !== -1) {
|
|
|
+ // update basic info
|
|
|
+ const target = tableData.value[idx]
|
|
|
+ target.name = editDialog.form.name
|
|
|
+ target.phone = editDialog.form.phone
|
|
|
+ target.gender = editDialog.form.gender
|
|
|
+ target.idNo = editDialog.form.idNo
|
|
|
+ target.city = editDialog.form.city
|
|
|
+ target.station = editDialog.form.station
|
|
|
+ target.level = editDialog.form.level
|
|
|
+ target.status = editDialog.form.status
|
|
|
+ target.authId = editDialog.form.authId
|
|
|
+ target.authQual = editDialog.form.authQual
|
|
|
+
|
|
|
+ // update tags
|
|
|
+ // simplistic approach: find predefined colors or default 'info'
|
|
|
+ const colorMap = { '行为矫正': 'warning', '驾驶': 'info', '摄影': 'success', '洗护护理': 'primary', '精细美容': 'danger' }
|
|
|
+ target.tags = editDialog.form.tags.map(t => ({ name: t, type: colorMap[t] || 'info' }))
|
|
|
+
|
|
|
+ ElMessage.success('更新成功')
|
|
|
+ }
|
|
|
+ editDialog.visible = false
|
|
|
+}
|
|
|
+
|
|
|
+const handleReward = (row) => {
|
|
|
+ rewardDialog.userName = row.name
|
|
|
+ rewardDialog.form = { type: 'reward', target: 'points', amount: 10, reason: '' }
|
|
|
+ rewardDialog.visible = true
|
|
|
+}
|
|
|
+const submitReward = () => {
|
|
|
+ ElMessage.success('操作成功')
|
|
|
+ rewardDialog.visible = false
|
|
|
+}
|
|
|
+
|
|
|
+const handleCommand = (cmd, row) => {
|
|
|
+ if(cmd === 'adjustPoints') {
|
|
|
+ pointsDialog.currentRow = row
|
|
|
+ pointsDialog.form = { type: 'add', amount: 0, reason: '' }
|
|
|
+ pointsDialog.visible = true
|
|
|
+ } else if(cmd === 'adjustBalance') {
|
|
|
+ balanceDialog.currentRow = row
|
|
|
+ balanceDialog.form = { type: 'add', subType: 'reward', amount: 0, reason: '' }
|
|
|
+ balanceDialog.visible = true
|
|
|
+ } else if(cmd === 'disable') {
|
|
|
+ ElMessageBox.confirm(`确定禁用履约者【${row.name}】吗?禁用后将无法接单。`, '提示', { type: 'warning' })
|
|
|
+ .then(() => {
|
|
|
+ row.status = 'disabled'
|
|
|
+ ElMessage.success('账号已禁用')
|
|
|
+ })
|
|
|
+ } else if(cmd === 'enable') {
|
|
|
+ ElMessageBox.confirm(`确定启用履约者【${row.name}】吗?`, '提示', { type: 'success' })
|
|
|
+ .then(() => {
|
|
|
+ row.status = 'resting' // Default to resting when enabled
|
|
|
+ ElMessage.success('账号已启用')
|
|
|
+ })
|
|
|
+ } else if(cmd === 'resetPwd') {
|
|
|
+ ElMessageBox.confirm('确定重置密码为默认密码 [123456] 吗?', '提示', { type: 'info' })
|
|
|
+ .then(() => { ElMessage.success('密码重置成功') })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const submitPointsAdjust = () => {
|
|
|
+ if(pointsDialog.currentRow) {
|
|
|
+ if(pointsDialog.form.type === 'add') pointsDialog.currentRow.points += pointsDialog.form.amount
|
|
|
+ else pointsDialog.currentRow.points -= pointsDialog.form.amount
|
|
|
+ ElMessage.success('积分调整成功')
|
|
|
+ }
|
|
|
+ pointsDialog.visible = false
|
|
|
+}
|
|
|
+
|
|
|
+const submitBalanceAdjust = () => {
|
|
|
+ if(balanceDialog.currentRow) {
|
|
|
+ let amt = balanceDialog.form.amount
|
|
|
+ if(balanceDialog.form.type === 'reduce') amt = -amt
|
|
|
+ balanceDialog.currentRow.balance += amt
|
|
|
+ // keep 2 decimals
|
|
|
+ balanceDialog.currentRow.balance = Math.round(balanceDialog.currentRow.balance * 100) / 100
|
|
|
+ ElMessage.success('余额调整成功')
|
|
|
+ }
|
|
|
+ balanceDialog.visible = false
|
|
|
+}
|
|
|
+
|
|
|
+const handleViewImage = (url) => {
|
|
|
+ // Already handled by el-image preview
|
|
|
+}
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.page-container { padding: 20px; }
|
|
|
+.table-card { border-radius: 8px; border: none; }
|
|
|
+.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
|
|
+
|
|
|
+.status-tabs { margin-top: -15px; }
|
|
|
+:deep(.el-tabs__header) { margin-bottom: 0; }
|
|
|
+:deep(.el-tabs__nav-wrap::after) { height: 1px; background-color: #f0f2f5; }
|
|
|
+
|
|
|
+.title { font-size: 18px; font-weight: bold; color: #303133; }
|
|
|
+.right-panel { display: flex; align-items: center; }
|
|
|
+.search-input { width: 240px; }
|
|
|
+
|
|
|
+/* Table Content Styles */
|
|
|
+.user-info { display: flex; align-items: center; }
|
|
|
+.text-col { margin-left: 10px; display: flex; flex-direction: column; justify-content: center; }
|
|
|
+.name-row { font-weight: bold; font-size: 14px; color: #333; display: flex; align-items: center; }
|
|
|
+.gender-tag { margin-left: 5px; display: flex; align-items: center; }
|
|
|
+.sub-text { font-size: 12px; color: #999; margin-top: 2px; }
|
|
|
+
|
|
|
+.auth-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
|
+.auth-card {
|
|
|
+ font-size: 12px; padding: 2px 6px; border-radius: 4px; background: #f4f4f5; color: #909399;
|
|
|
+ display: flex; align-items: center; gap: 4px;
|
|
|
+}
|
|
|
+.auth-card.active { background: #ecf5ff; color: #409eff; }
|
|
|
+
|
|
|
+.finance-item { font-size: 13px; color: #606266; line-height: 1.6; }
|
|
|
+.num { font-weight: bold; font-family: DIN, sans-serif; margin-left: 5px; color: #303133; }
|
|
|
+.num.error { color: #f56c6c; }
|
|
|
+
|
|
|
+.status-cell { display: flex; align-items: center; }
|
|
|
+.status-dot { width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
|
|
|
+.status-dot.resting { background: #e6a23c; }
|
|
|
+.status-dot.busy { background: #409eff; }
|
|
|
+.status-dot.disabled { background: #f56c6c; }
|
|
|
+.status-dot.frozen { background: #909399; }
|
|
|
+
|
|
|
+.op-cell { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
|
|
|
+
|
|
|
+.pagination-container { display: flex; justify-content: flex-end; margin-top: 20px; }
|
|
|
+
|
|
|
+/* Drawer Styles */
|
|
|
+.user-header-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px;
|
|
|
+ background: linear-gradient(135deg, #f5f7fa 0%, #eef1f6 100%);
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 25px;
|
|
|
+}
|
|
|
+.header-info { margin-left: 20px; flex: 1; }
|
|
|
+.top-row { display: flex; align-items: center; margin-bottom: 8px; }
|
|
|
+.user-name { font-size: 20px; font-weight: bold; color: #303133; }
|
|
|
+.status-badge {
|
|
|
+ margin-left: auto;
|
|
|
+ font-size: 12px; padding: 4px 10px; border-radius: 12px;
|
|
|
+ background: #e1f3d8; color: #67c23a;
|
|
|
+}
|
|
|
+.status-badge.resting { background: #faecd8; color: #e6a23c; }
|
|
|
+.status-badge.disabled { background: #fde2e2; color: #f56c6c; }
|
|
|
+.status-badge.busy { background: #d9ecff; color: #409eff; }
|
|
|
+.status-badge.frozen { background: #f0f9eb; color: #909399; }
|
|
|
+
|
|
|
+.sub-row { display: flex; align-items: center; font-size: 13px; color: #606266; margin-bottom: 8px; }
|
|
|
+.info-item { display: flex; align-items: center; gap: 4px; }
|
|
|
+.divider { margin: 0 10px; color: #dcdfe6; }
|
|
|
+
|
|
|
+.tags-row { display: flex; align-items: center; gap: 5px; }
|
|
|
+
|
|
|
+.data-metrics-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ padding: 15px 0;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ background: #fff;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+.metric-item { text-align: center; flex: 1; }
|
|
|
+.val { font-size: 20px; font-weight: bold; color: #303133; font-family: DIN, sans-serif; margin-bottom: 4px; }
|
|
|
+.lbl { font-size: 12px; color: #909399; }
|
|
|
+.text-primary { color: #409eff; }
|
|
|
+.text-danger { color: #f56c6c; }
|
|
|
+.text-warning { color: #e6a23c; }
|
|
|
+.divider-v { width: 1px; background: #e0e0e0; height: 30px; align-self: center; }
|
|
|
+
|
|
|
+.detail-tabs { margin-top: 0; }
|
|
|
+.section-block { margin-bottom: 25px; }
|
|
|
+.section-title { font-size: 15px; font-weight: bold; margin-bottom: 15px; border-left: 4px solid #409eff; padding-left: 10px; }
|
|
|
+
|
|
|
+.cert-row { display: flex; gap: 15px; }
|
|
|
+.cert-item { text-align: center; cursor: pointer; }
|
|
|
+.cert-img { width: 120px; height: 80px; border-radius: 6px; border: 1px solid #dcdfe6; background: #f5f7fa; }
|
|
|
+.img-slot { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; color: #909399; font-size: 24px; }
|
|
|
+.cert-name { font-size: 12px; color: #606266; margin-top: 5px; }
|
|
|
+
|
|
|
+.tag-list { display: flex; flex-wrap: wrap; }
|
|
|
+.tab-content-wrapper { padding: 10px 0; }
|
|
|
+:deep(.el-table .el-table__cell) { padding: 12px 0; }
|
|
|
+</style>
|