RiderList.vue 11.4 KB
<template>
  <div>
    <a-card title="骑手管理" :bordered="false" class="list-table-card">
      <div class="list-toolbar">
        <div class="list-toolbar-left">
          <a-select v-model:value="filterStatus" placeholder="审核状态" allowClear class="list-filter" @change="loadList">
            <a-select-option :value="2">待审核</a-select-option>
            <a-select-option :value="1">已通过</a-select-option>
            <a-select-option :value="0">已拒绝</a-select-option>
          </a-select>
          <a-input-search v-model:value="keyword" placeholder="搜索姓名/手机" @search="loadList" class="list-search" />
        </div>
        <div class="list-toolbar-right">
          <a-button type="primary" @click="openAdd">新增骑手</a-button>
        </div>
      </div>
      <a-table :dataSource="list" :columns="columns" :loading="loading" rowKey="id" :pagination="false">
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'userStatus'">
            <a-tag :color="record.userStatus === 1 ? 'green' : record.userStatus === 0 ? 'red' : 'orange'">
              {{ statusMap[record.userStatus] }}
            </a-tag>
          </template>
          <template v-if="column.key === 'cityId'">
            {{ getCityName(record.cityId) }}
          </template>
          <template v-if="column.key === 'levelName'">
            {{ record.levelName || '默认等级' }}
          </template>
          <template v-if="column.key === 'accountStatus'">
            <a-tag :color="getAccountStatus(record) === 1 ? 'green' : 'red'">
              {{ getAccountStatus(record) === 1 ? '正常' : '禁用' }}
            </a-tag>
          </template>
          <template v-if="column.key === 'workStatus'">
            <a-tag :color="getWorkStatus(record) === 0 ? 'green' : 'orange'">
              {{ getWorkStatus(record) === 0 ? '在线' : '休息' }}
            </a-tag>
          </template>
          <template v-if="column.key === 'type'">
            <a-tag>{{ record.type === 1 ? '兼职' : '全职' }}</a-tag>
          </template>
          <template v-if="column.key === 'holdOrderLimit'">
            {{ formatHoldOrderLimit(record.holdOrderLimit) }}
          </template>
          <template v-if="column.key === 'action'">
            <a-space>
              <a @click="openSetLevel(record)">设置等级</a>
              <a @click="openEditHoldLimit(record)">设置持单上限</a>
              <template v-if="record.userStatus === 2">
                <a-popconfirm title="确认通过审核?" @confirm="setStatus(record.id, 1)">
                  <a style="color:green">通过</a>
                </a-popconfirm>
                <a-popconfirm title="确认拒绝?" @confirm="setStatus(record.id, 0)">
                  <a style="color:red">拒绝</a>
                </a-popconfirm>
              </template>
              <template v-else>
                <a-popconfirm
                  :title="getAccountStatus(record) === 1 ? '确认禁用该骑手?' : '确认启用该骑手?'"
                  @confirm="setEnableStatus(record.id, getAccountStatus(record) === 1 ? 0 : 1)">
                  <a :style="getAccountStatus(record) === 1 ? 'color:red' : ''">
                    {{ getAccountStatus(record) === 1 ? '禁用' : '启用' }}
                  </a>
                </a-popconfirm>
                <a-popconfirm
                  :title="record.type === 1 ? '切换为全职?' : '切换为兼职?'"
                  @confirm="setType(record.id, record.type === 1 ? 2 : 1)">
                  <a>切换{{ record.type === 1 ? '全职' : '兼职' }}</a>
                </a-popconfirm>
              </template>
            </a-space>
          </template>
        </template>
      </a-table>
    </a-card>

    <a-modal v-model:open="modalVisible" title="新增骑手" @ok="handleAdd" :confirmLoading="saving">
      <div class="soft-page-stack">
        <div class="soft-note-card">
          <strong>骑手创建说明</strong>
          <p>当前入口用于后台手动新增骑手账号。管理员需要先选择租户,新增后可继续设置等级、启用状态和全职/兼职类型。</p>
        </div>
        <a-form :model="form" layout="vertical">
          <a-form-item v-if="isAdmin" label="租户">
            <a-select v-model:value="form.cityId" placeholder="选择租户">
              <a-select-option v-for="c in cityList" :key="c.id" :value="c.id">{{ c.name }}</a-select-option>
            </a-select>
          </a-form-item>
          <a-form-item label="昵称">
            <a-input v-model:value="form.userNickname" placeholder="请输入骑手昵称" />
          </a-form-item>
          <a-form-item label="手机号">
            <a-input v-model:value="form.mobile" placeholder="请输入手机号" />
          </a-form-item>
          <a-form-item label="身份证号">
            <a-input v-model:value="form.idNo" placeholder="请输入身份证号(非必填)" />
          </a-form-item>
          <a-form-item label="密码">
            <a-input-password v-model:value="form.password" placeholder="请输入登录密码" />
          </a-form-item>
        </a-form>
      </div>
    </a-modal>

    <a-modal v-model:open="levelVisible" title="设置骑手等级" @ok="handleSetLevel" :confirmLoading="levelSaving">
      <div class="soft-page-stack">
        <div class="soft-note-card">
          <strong>等级设置说明</strong>
          <p>骑手等级会影响骑手收入规则。未单独指定时,将自动使用当前租户的默认等级。</p>
        </div>
        <a-form layout="vertical">
          <a-form-item label="骑手">
            <a-input :value="levelTargetName" disabled />
          </a-form-item>
          <a-form-item label="等级">
            <a-select v-model:value="selectedLevelId" placeholder="请选择等级">
              <a-select-option :value="0">使用默认等级</a-select-option>
              <a-select-option v-for="item in levelOptions" :key="item.id" :value="item.id">
                {{ item.name }}{{ item.isDefault === 1 ? '(默认)' : '' }}
              </a-select-option>
            </a-select>
          </a-form-item>
        </a-form>
      </div>
    </a-modal>

    <a-modal v-model:open="editVisible" title="编辑骑手" @ok="handleSetHoldLimit" :confirmLoading="editSaving">
      <div class="soft-page-stack">
        <div class="soft-note-card">
          <strong>持单上限说明</strong>
          <p>个人持单上限会统一作用于抢单、自动派单、后台指派和转单后再次接单。填写 0 表示不限制。</p>
        </div>
        <a-form layout="vertical">
          <a-form-item label="骑手">
            <a-input :value="editTargetName" disabled />
          </a-form-item>
          <a-form-item label="个人持单上限">
            <a-input-number v-model:value="editHoldOrderLimit" :min="0" style="width:100%" />
          </a-form-item>
        </a-form>
      </div>
    </a-modal>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { riderApi, riderLevelApi } from '@/api'
import { useRoleCityList } from '@/composables/useRoleCityList'

const loading = ref(false)
const saving = ref(false)
const list = ref<any[]>([])
const levelOptions = ref<any[]>([])
const filterStatus = ref<number | undefined>()
const keyword = ref('')
const modalVisible = ref(false)
const levelVisible = ref(false)
const editVisible = ref(false)
const form = reactive({
  cityId: undefined as number | undefined,
  userNickname: '',
  mobile: '',
  idNo: '',
  password: '',
})
const levelSaving = ref(false)
const levelTargetId = ref<number>(0)
const levelTargetName = ref('')
const selectedLevelId = ref<number>(0)
const editSaving = ref(false)
const editTargetId = ref<number>(0)
const editTargetName = ref('')
const editHoldOrderLimit = ref<number>(0)
const { isAdmin, cityList, loadCities, getCityName } = useRoleCityList()

const statusMap: Record<number, string> = { 0: '已拒绝', 1: '已通过', 2: '待审核' }

const columns = [
  { title: 'ID', dataIndex: 'id', width: 80 },
  { title: '昵称', dataIndex: 'userNickname' },
  { title: '手机', dataIndex: 'mobile' },
  { title: '租户', key: 'cityId' },
  { title: '等级', key: 'levelName' },
  { title: '类型', key: 'type' },
  { title: '个人持单上限', key: 'holdOrderLimit' },
  { title: '审核状态', key: 'userStatus' },
  { title: '账号状态', key: 'accountStatus' },
  { title: '骑手状态', key: 'workStatus' },
  { title: '余额', dataIndex: 'balance' },
  { title: '操作', key: 'action' },
]

function getAccountStatus(record: any) {
  return record.status === 0 ? 0 : 1
}

function getWorkStatus(record: any) {
  return record.isRest === 1 ? 1 : 0
}

function formatHoldOrderLimit(value?: number) {
  return value && value > 0 ? `${value} 单` : '无限制'
}

async function loadList() {
  loading.value = true
  try {
    const res: any = await riderApi.list({
      keyword: keyword.value,
      userStatus: filterStatus.value,
    }).catch(() => ({ data: [] }))
    list.value = Array.isArray(res?.data) ? res.data : []
  } finally { loading.value = false }
}

function openAdd() {
  Object.assign(form, {
    cityId: undefined,
    userNickname: '',
    mobile: '',
    idNo: '',
    password: '',
  })
  modalVisible.value = true
}

async function handleAdd() {
  if (!form.userNickname || !form.mobile || !form.password) {
    message.error('请填写完整骑手信息')
    return
  }
  if (isAdmin.value && !form.cityId) {
    message.error('请选择租户')
    return
  }

  saving.value = true
  try {
    await riderApi.add({ ...form })
    message.success('新增成功')
    modalVisible.value = false
    loadList()
  } finally {
    saving.value = false
  }
}

async function openSetLevel(record: any) {
  const res: any = await riderLevelApi.list(record.cityId)
  levelOptions.value = Array.isArray(res?.data) ? res.data : []
  levelTargetId.value = record.id
  levelTargetName.value = record.userNickname || record.mobile
  selectedLevelId.value = record.levelId || 0
  levelVisible.value = true
}

async function handleSetLevel() {
  levelSaving.value = true
  try {
    await riderApi.setLevel(levelTargetId.value, selectedLevelId.value || undefined)
    message.success('设置成功')
    levelVisible.value = false
    loadList()
  } finally {
    levelSaving.value = false
  }
}

function openEditHoldLimit(record: any) {
  editTargetId.value = record.id
  editTargetName.value = record.userNickname || record.mobile
  editHoldOrderLimit.value = Number(record.holdOrderLimit || 0)
  editVisible.value = true
}

async function handleSetHoldLimit() {
  if (editHoldOrderLimit.value < 0) {
    message.error('个人持单上限不能小于0')
    return
  }
  editSaving.value = true
  try {
    await riderApi.setHoldOrderLimit(editTargetId.value, editHoldOrderLimit.value)
    message.success('设置成功')
    editVisible.value = false
    loadList()
  } finally {
    editSaving.value = false
  }
}

async function setStatus(riderId: number, status: number) {
  await riderApi.setStatus(riderId, status)
  message.success('操作成功')
  loadList()
}

async function setEnableStatus(riderId: number, status: number) {
  await riderApi.setEnableStatus(riderId, status)
  message.success('操作成功')
  loadList()
}

async function setType(riderId: number, type: number) {
  await riderApi.setType(riderId, type)
  message.success('操作成功')
  loadList()
}

onMounted(() => {
  loadList()
  loadCities()
})
</script>