ソースを参照

履约者模块静态页面接入

Huanyi 1 ヶ月 前
コミット
42f1fa451c

+ 10 - 0
src/api/system/store/types.ts

@@ -87,6 +87,11 @@ export interface StoreVO {
    */
   serviceOrder: number | string;
 
+  /**
+   * 区域编码
+   */
+  areaCode?: string;
+
 }
 
 export interface StoreForm extends BaseEntity {
@@ -180,6 +185,11 @@ export interface StoreForm extends BaseEntity {
    */
   regionId?: number;
 
+  /**
+   * 区域编码
+   */
+  areaCode?: string;
+
 }
 
 export interface StoreQuery extends PageQuery {

+ 5 - 5
src/layout/components/Navbar.vue

@@ -22,11 +22,11 @@
         </el-select>
 
         <search-menu ref="searchMenuRef" />
-        <el-tooltip content="搜索" effect="dark" placement="bottom">
-          <div class="right-menu-item hover-effect" @click="openSearchMenu">
-            <svg-icon class-name="search-icon" icon-class="search" />
-          </div>
-        </el-tooltip>
+<!--        <el-tooltip content="搜索" effect="dark" placement="bottom">-->
+<!--          <div class="right-menu-item hover-effect" @click="openSearchMenu">-->
+<!--            <svg-icon class-name="search-icon" icon-class="search" />-->
+<!--          </div>-->
+<!--        </el-tooltip>-->
 <!--        &lt;!&ndash; 消息 &ndash;&gt;-->
 <!--        <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">-->
 <!--          <div>-->

+ 512 - 0
src/views/fulfiller/anamaly/index.vue

@@ -0,0 +1,512 @@
+<template>
+  <div class="page-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="card-header">
+          <div class="left-panel">
+            <span class="title">异常上报管理</span>
+          </div>
+          <div class="right-panel" style="display: flex; align-items: center; gap: 10px">
+            <el-input v-model="searchKey" placeholder="订单号/履约者姓名" prefix-icon="Search" clearable style="width: 220px" />
+            <el-select v-model="searchType" placeholder="异常类型" clearable style="width: 140px">
+              <el-option label="无法联系用户" value="contact_fail" />
+              <el-option label="地址错误" value="addr_error" />
+              <el-option label="宠物攻击性强" value="pet_aggressive" />
+              <el-option label="天气原因" value="weather_delay" />
+              <el-option label="突发意外" value="accident" />
+            </el-select>
+            <el-select v-model="searchStatus" placeholder="审核状态" clearable style="width: 120px">
+              <el-option label="待审核" :value="0" />
+              <el-option label="已通过" :value="1" />
+              <el-option label="已驳回" :value="2" />
+            </el-select>
+            <el-button type="primary" icon="Plus" @click="handleAdd">新增上报</el-button>
+          </div>
+        </div>
+      </template>
+
+      <el-table :data="filteredTableData" style="width: 100%" v-loading="loading">
+        <el-table-column prop="orderNo" label="关联订单号" width="180">
+          <template #default="scope">
+            <el-link type="primary" :underline="false">{{ scope.row.orderNo }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column prop="type" label="异常类型" width="120">
+          <template #default="scope">
+            <el-tag :type="getExceptionTag(scope.row.type)">{{ getExceptionLabel(scope.row.type) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="fulfillerName" label="履约者" width="150">
+          <template #default="scope">
+            <div>{{ scope.row.fulfillerName }}</div>
+            <div style="font-size: 12px; color: #999">{{ scope.row.fulfillerPhone }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="content" label="上报内容" show-overflow-tooltip />
+        <el-table-column prop="images" label="上报图片" width="120">
+          <template #default="scope">
+            <div v-if="scope.row.images && scope.row.images.length">
+              <el-image
+                :src="scope.row.images[0]"
+                :preview-src-list="scope.row.images"
+                fit="cover"
+                style="width: 40px; height: 40px; border-radius: 4px"
+                preview-teleported
+              >
+                <template #error>
+                  <div class="image-slot">
+                    <el-icon><Picture /></el-icon>
+                  </div>
+                </template>
+              </el-image>
+              <span v-if="scope.row.images.length > 1" style="font-size: 12px; color: #999; margin-left: 5px">+{{ scope.row.images.length }}</span>
+            </div>
+            <span v-else style="color: #ccc">无图</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="提交时间" width="170" sortable />
+        <el-table-column prop="status" label="审核状态" width="100">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === 0" type="warning">待审核</el-tag>
+            <el-tag v-else-if="scope.row.status === 1" type="success">已通过</el-tag>
+            <el-tag v-else type="danger">已驳回</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right" align="center">
+          <template #default="scope">
+            <template v-if="scope.row.status === 0">
+              <el-button link type="primary" size="small" @click="handleOpenDrawer(scope.row, 'audit')">审核</el-button>
+              <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
+              <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+            <template v-else>
+              <el-button link type="primary" size="small" @click="handleOpenDrawer(scope.row, 'view')">详情</el-button>
+              <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+          </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>
+
+    <!-- Audit/Detail Drawer -->
+    <el-drawer v-model="drawerVisible" :title="drawerMode === 'audit' ? '异常上报审核' : '异常详情'" direction="rtl" size="600px">
+      <div class="drawer-content">
+        <!-- 1. Basic Info -->
+        <el-descriptions title="基础信息" :column="2" border>
+          <el-descriptions-item label="订单号">{{ currentItem.orderNo }}</el-descriptions-item>
+          <el-descriptions-item label="提交时间">{{ currentItem.createTime }}</el-descriptions-item>
+          <el-descriptions-item label="履约者">{{ currentItem.fulfillerName }}</el-descriptions-item>
+          <el-descriptions-item label="联系电话">{{ currentItem.fulfillerPhone }}</el-descriptions-item>
+          <el-descriptions-item label="异常类型">
+            <el-tag :type="getExceptionTag(currentItem.type)">{{ getExceptionLabel(currentItem.type) }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="当前状态">
+            <el-tag v-if="currentItem.status === 0" type="warning">待审核</el-tag>
+            <el-tag v-else-if="currentItem.status === 1" type="success">已通过</el-tag>
+            <el-tag v-else type="danger">已驳回</el-tag>
+          </el-descriptions-item>
+        </el-descriptions>
+
+        <!-- 2. Content & Images -->
+        <div class="section-block">
+          <div class="section-title">上报内容</div>
+          <div class="text-content">{{ currentItem.content }}</div>
+          <div class="image-list" v-if="currentItem.images && currentItem.images.length">
+            <el-image
+              v-for="(img, idx) in currentItem.images"
+              :key="idx"
+              :src="img"
+              :preview-src-list="currentItem.images"
+              :initial-index="idx"
+              fit="cover"
+              class="detail-img"
+            />
+          </div>
+        </div>
+
+        <!-- 3. Audit Form (If Audit Mode) -->
+        <div class="section-block audit-form" v-if="drawerMode === 'audit'">
+          <div class="section-title">审核处理</div>
+          <el-form :model="auditForm" label-position="top">
+            <el-form-item label="审核结果" required>
+              <el-radio-group v-model="auditForm.result">
+                <el-radio :label="1" border>通过</el-radio>
+                <el-radio :label="2" border>驳回</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="处理备注">
+              <el-input v-model="auditForm.remark" type="textarea" :rows="3" placeholder="请输入审核意见 (通过或驳回均可选填)" />
+            </el-form-item>
+          </el-form>
+          <div class="drawer-footer-actions">
+            <el-button @click="drawerVisible = false">取消</el-button>
+            <el-button type="primary" @click="submitAudit">提交审核</el-button>
+          </div>
+        </div>
+
+        <!-- 4. Log Record (All Modes) -->
+        <div class="section-block">
+          <div class="section-title">处理记录</div>
+          <el-timeline>
+            <el-timeline-item :timestamp="currentItem.createTime" placement="top" type="primary">
+              <el-card shadow="never" class="log-card">
+                <h4>提交上报</h4>
+                <p>履约者 {{ currentItem.fulfillerName }} 提交了异常上报</p>
+              </el-card>
+            </el-timeline-item>
+            <el-timeline-item
+              v-if="currentItem.status !== 0"
+              :timestamp="currentItem.auditTime || '2026-02-05 18:00:00'"
+              placement="top"
+              :type="currentItem.status === 1 ? 'success' : 'danger'"
+            >
+              <el-card shadow="never" class="log-card">
+                <h4>{{ currentItem.status === 1 ? '审核通过' : '审核驳回' }}</h4>
+                <p>操作人:系统管理员</p>
+                <p>备注:{{ currentItem.auditRemark || '无' }}</p>
+              </el-card>
+            </el-timeline-item>
+          </el-timeline>
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- Edit/Add Dialog (Keep for Edit) -->
+    <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑异常' : '新增异常'" width="600px">
+      <el-form :model="form" label-width="100px">
+        <el-form-item label="关联订单" required>
+          <el-input v-model="form.orderNo" placeholder="请输入订单号" />
+        </el-form-item>
+        <el-form-item label="履约者" required>
+          <el-select v-model="form.fulfillerId" filterable placeholder="请输入姓名/电话检索" style="width: 100%" @change="handleFulfillerSelect">
+            <el-option v-for="item in fulfillerOptions" :key="item.id" :label="item.name + ' - ' + item.phone" :value="item.id" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="异常类型" required>
+          <el-select v-model="form.type" placeholder="请选择类型" style="width: 100%">
+            <el-option label="无法联系用户" value="contact_fail" />
+            <el-option label="地址错误/不存在" value="addr_error" />
+            <el-option label="宠物攻击性强" value="pet_aggressive" />
+            <el-option label="恶劣天气延迟" value="weather_delay" />
+            <el-option label="突发意外" value="accident" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="上报内容" required>
+          <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请详细描述异常情况..." />
+        </el-form-item>
+        <el-form-item label="现场图片">
+          <el-upload
+            action="#"
+            list-type="picture-card"
+            :auto-upload="false"
+            v-model:file-list="fileList"
+            :on-change="handleFileChange"
+            :on-remove="handleFileRemove"
+          >
+            <el-icon><Plus /></el-icon>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="saveData">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+
+const loading = ref(false);
+const searchKey = ref('');
+const searchType = ref('');
+const searchStatus = ref('');
+const currentPage = ref(1);
+const pageSize = ref(10);
+const total = ref(42);
+
+// Dialog State (For Add/Edit)
+const dialogVisible = ref(false);
+const isEdit = ref(false);
+const fileList = ref([]);
+
+// Mock Fulfiller Search Options
+const fulfillerOptions = ref([
+  { id: 101, name: '李建国', phone: '13812341234' },
+  { id: 102, name: '王大力', phone: '13987654321' },
+  { id: 103, name: '张小妹', phone: '13766668888' },
+  { id: 104, name: '刘跑跑', phone: '13611112222' }
+]);
+
+const handleFulfillerSelect = (id) => {
+  const target = fulfillerOptions.value.find((item) => item.id === id);
+  if (target) {
+    form.fulfillerName = target.name;
+    form.fulfillerPhone = target.phone;
+  }
+};
+
+// Drawer State (For Audit/View)
+const drawerVisible = ref(false);
+const drawerMode = ref('view'); // 'audit' or 'view'
+const currentItem = ref({});
+const auditForm = reactive({ result: 1, remark: '' });
+
+const form = reactive({
+  id: '',
+  orderNo: '',
+  fulfillerId: '',
+  fulfillerName: '',
+  fulfillerPhone: '',
+  type: '',
+  content: '',
+  images: [],
+  status: 0,
+  auditRemark: ''
+});
+
+// Mock Data
+const tableData = ref([
+  {
+    id: 1,
+    orderNo: 'ORD202402048805',
+    fulfillerName: '李建国',
+    fulfillerPhone: '13812341234',
+    type: 'contact_fail',
+    content: '到达指定地点后,多次拨打宠主电话无人接听,敲门无响应超过15分钟。',
+    images: [
+      'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
+      'https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png'
+    ],
+    status: 0,
+    createTime: '2026-02-04 14:30:00',
+    auditRemark: '',
+    auditTime: ''
+  },
+  {
+    id: 2,
+    orderNo: 'ORD202402048802',
+    fulfillerName: '王大力',
+    fulfillerPhone: '13987654321',
+    type: 'pet_aggressive',
+    content: '进门时金毛表现出强烈护食行为,试图攻击,无法进行喂食操作。',
+    images: [],
+    status: 1,
+    auditRemark: '已确认,取消本次订单,按此单结算跑腿费。',
+    createTime: '2026-02-04 10:15:00',
+    auditTime: '2026-02-04 11:00:00'
+  }
+]);
+
+const filteredTableData = computed(() => {
+  return tableData.value.filter((item) => {
+    const matchKey = !searchKey.value || item.orderNo.includes(searchKey.value) || item.fulfillerName.includes(searchKey.value);
+    const matchType = !searchType.value || item.type === searchType.value;
+    const matchStatus = searchStatus.value === '' || item.status === searchStatus.value;
+    return matchKey && matchType && matchStatus;
+  });
+});
+
+// Helpers
+const getExceptionLabel = (type) => {
+  const map = {
+    'contact_fail': '无法联系用户',
+    'addr_error': '地址错误',
+    'pet_aggressive': '宠物攻击性强',
+    'weather_delay': '恶劣天气延迟',
+    'accident': '突发意外'
+  };
+  return map[type] || '其他';
+};
+
+const getExceptionTag = (type) => {
+  const map = {
+    'contact_fail': 'warning',
+    'addr_error': 'info',
+    'pet_aggressive': 'danger',
+    'weather_delay': 'primary',
+    'accident': 'danger'
+  };
+  return map[type] || 'info';
+};
+
+// Actions
+const handleAdd = () => {
+  isEdit.value = false;
+  fileList.value = [];
+  Object.assign(form, {
+    id: '',
+    orderNo: '',
+    fulfillerId: '',
+    fulfillerName: '',
+    fulfillerPhone: '',
+    type: '',
+    content: '',
+    images: [],
+    status: 0,
+    auditRemark: ''
+  });
+  dialogVisible.value = true;
+};
+
+const handleEdit = (row) => {
+  isEdit.value = true;
+  Object.assign(form, JSON.parse(JSON.stringify(row)));
+  fileList.value = (row.images || []).map((url, i) => ({ name: `img${i}`, url: url }));
+  dialogVisible.value = true;
+};
+
+const handleDelete = (row) => {
+  ElMessageBox.confirm('确定删除该条记录吗?', '警告', { type: 'warning' }).then(() => {
+    tableData.value = tableData.value.filter((item) => item.id !== row.id);
+    ElMessage.success('删除成功');
+  });
+};
+
+// Open Drawer for Audit or View
+const handleOpenDrawer = (row, mode) => {
+  drawerMode.value = mode;
+  currentItem.value = JSON.parse(JSON.stringify(row));
+  // Reset audit form
+  if (mode === 'audit') {
+    auditForm.result = 1;
+    auditForm.remark = '';
+  }
+  drawerVisible.value = true;
+};
+
+const submitAudit = () => {
+  const target = tableData.value.find((i) => i.id === currentItem.value.id);
+  if (target) {
+    target.status = auditForm.result;
+    target.auditRemark = auditForm.remark;
+    target.auditTime = new Date().toLocaleString();
+    ElMessage.success('审核已提交');
+    drawerVisible.value = false;
+  }
+};
+
+const saveData = () => {
+  if (!form.orderNo || !form.type) return ElMessage.warning('请填写必填项');
+
+  form.images = fileList.value.map((f) => f.url || 'https://placeholder.com/img');
+
+  if (isEdit.value) {
+    const idx = tableData.value.findIndex((i) => i.id === form.id);
+    if (idx !== -1) Object.assign(tableData.value[idx], form);
+  } else {
+    tableData.value.unshift({
+      id: Date.now(),
+      ...form,
+      createTime: new Date().toLocaleString()
+    });
+  }
+  ElMessage.success('保存成功');
+  dialogVisible.value = false;
+};
+
+const handleFileChange = (uploadFile, uploadFiles) => {
+  fileList.value = uploadFiles;
+};
+const handleFileRemove = (uploadFile, uploadFiles) => {
+  fileList.value = uploadFiles;
+};
+const handleSizeChange = (val) => {};
+const handleCurrentChange = (val) => {};
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.title {
+  font-weight: bold;
+}
+.pagination-container {
+  margin-top: 20px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+/* Drawer Styles */
+.drawer-content {
+  padding: 0 10px;
+}
+.section-block {
+  margin-top: 25px;
+}
+.section-title {
+  font-weight: bold;
+  border-left: 4px solid #409eff;
+  padding-left: 10px;
+  margin-bottom: 15px;
+  font-size: 15px;
+  color: #303133;
+}
+.text-content {
+  background: #f8f9fa;
+  padding: 12px;
+  border-radius: 4px;
+  color: #606266;
+  line-height: 1.6;
+}
+.image-list {
+  margin-top: 15px;
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+}
+.detail-img {
+  width: 100px;
+  height: 100px;
+  border-radius: 6px;
+  cursor: pointer;
+}
+.audit-form {
+  background: #f0f9eb;
+  padding: 15px;
+  border-radius: 8px;
+  border: 1px solid #e1f3d8;
+}
+.drawer-footer-actions {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 15px;
+  grid-gap: 10px;
+}
+.log-card {
+  margin-bottom: 5px;
+}
+.log-card h4 {
+  margin: 0 0 5px;
+  font-size: 14px;
+  color: #303133;
+}
+.log-card p {
+  margin: 0;
+  font-size: 12px;
+  color: #909399;
+  line-height: 1.4;
+}
+</style>

+ 604 - 0
src/views/fulfiller/audit/index.vue

@@ -0,0 +1,604 @@
+<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="warning" effect="plain" style="margin-left: 10px">待审核 3 人</el-tag>
+          </div>
+          <div class="right-panel">
+            <el-input v-model="searchKey" placeholder="搜索姓名/手机号" class="search-input" prefix-icon="Search" clearable />
+          </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="pending" />
+          <el-tab-pane label="已通过" name="approved" />
+          <el-tab-pane label="已驳回" name="rejected" />
+        </el-tabs>
+      </template>
+
+      <el-table :data="filteredTableData" style="width: 100%" :header-cell-style="{ background: '#f5f7fa' }">
+        <el-table-column label="申请人信息" width="220">
+          <template #default="scope">
+            <div class="user-info">
+              <el-avatar :size="45">{{ scope.row.name.charAt(0) }}</el-avatar>
+              <div class="text-col">
+                <div class="name-row">
+                  <span class="name">{{ scope.row.name }}</span>
+                  <el-icon v-if="scope.row.gender === 'male'" color="#409eff"><Male /></el-icon>
+                  <el-icon v-else color="#f56c6c"><Female /></el-icon>
+                </div>
+                <div class="sub-text">{{ scope.row.phone }}</div>
+                <div class="sub-text">{{ scope.row.birthday }} ({{ getAge(scope.row.birthday) }}岁)</div>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="工作意向" min-width="180">
+          <template #default="scope">
+            <div class="work-info">
+              <div class="info-item">
+                <span class="label">类型: </span>
+                <el-tag size="small" :type="scope.row.workType === 'full_time' ? 'warning' : 'info'" effect="light">
+                  {{ scope.row.workType === 'full_time' ? '全职专送' : '兼职众包' }}
+                </el-tag>
+              </div>
+              <div class="info-item">
+                <span class="label">城市: </span>
+                <span>{{ scope.row.city }}</span>
+              </div>
+              <div class="info-item">
+                <el-tooltip :content="scope.row.location" placement="top">
+                  <span class="location-text"
+                    ><el-icon><Location /></el-icon> {{ scope.row.location }}</span
+                  >
+                </el-tooltip>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="提交资料" width="220">
+          <template #default="scope">
+            <div class="auth-row">
+              <div class="auth-card active">
+                <el-icon><Postcard /></el-icon> 身份信息
+              </div>
+              <div class="auth-card" :class="{ active: scope.row.qualifications && scope.row.qualifications.length > 0 }">
+                <el-icon><Medal /></el-icon> 专业资质
+              </div>
+            </div>
+            <div class="sub-text" style="margintop: 8px">申请时间: {{ scope.row.applyTime }}</div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="status" label="状态" width="120">
+          <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="150" fixed="right">
+          <template #default="scope">
+            <div class="op-cell">
+              <el-button v-if="scope.row.status === 'pending'" link type="primary" size="small" @click="handleDetail(scope.row)">审核</el-button>
+              <el-button link type="primary" size="small" @click="handleDetail(scope.row)">详情</el-button>
+            </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>
+
+    <!-- 审核详情弹窗 -->
+    <el-dialog v-model="dialogVisible" title="入驻审核详情" width="700px" top="5vh">
+      <div class="audit-content" v-if="currentItem">
+        <!-- 审核结果栏 -->
+        <div v-if="currentItem.status !== 'pending'" class="result-box" :class="currentItem.status">
+          <div class="result-header">
+            <el-icon v-if="currentItem.status === 'approved'"><SuccessFilled /></el-icon>
+            <el-icon v-else><CircleCloseFilled /></el-icon>
+            <span>{{ currentItem.status === 'approved' ? '审核通过' : '审核驳回' }}</span>
+          </div>
+          <div class="result-info">
+            <span>审核人:{{ currentItem.auditInfo?.auditor || 'admin' }}</span>
+            <span>审核时间:{{ currentItem.auditInfo?.time || '2026-02-04 12:00:00' }}</span>
+          </div>
+          <div v-if="currentItem.status === 'rejected'" class="reject-reason">驳回原因:{{ currentItem.auditInfo?.reason }}</div>
+        </div>
+
+        <!-- 基础信息 -->
+        <div class="section-title">基础报名信息</div>
+        <el-descriptions :column="3" border>
+          <el-descriptions-item label="姓名">{{ currentItem.name }}</el-descriptions-item>
+          <el-descriptions-item label="手机号">{{ currentItem.phone }}</el-descriptions-item>
+          <el-descriptions-item label="性别">{{ currentItem.gender === 'male' ? '男' : '女' }}</el-descriptions-item>
+          <el-descriptions-item label="出生日期">{{ currentItem.birthday }}</el-descriptions-item>
+          <el-descriptions-item label="工作类型">
+            <el-tag size="small" :type="currentItem.workType === 'full_time' ? 'warning' : 'info'">
+              {{ currentItem.workType === 'full_time' ? '全职专送' : '兼职众包' }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="意向城市">{{ currentItem.city }}</el-descriptions-item>
+          <el-descriptions-item label="意向地点" :span="3">{{ currentItem.location }}</el-descriptions-item>
+        </el-descriptions>
+
+        <!-- 实名认证信息 -->
+        <div class="section-title" style="margin-top: 20px">实名认证信息</div>
+        <el-descriptions :column="2" border>
+          <el-descriptions-item label="真实姓名">{{ currentItem.realName || currentItem.name }}</el-descriptions-item>
+          <el-descriptions-item label="证件类型">居民身份证</el-descriptions-item>
+          <el-descriptions-item label="身份证号">{{ currentItem.idNo }}</el-descriptions-item>
+          <el-descriptions-item label="有效期至">{{ currentItem.idValidDate }}</el-descriptions-item>
+        </el-descriptions>
+        <div class="img-row" style="margin-top: 15px">
+          <div class="img-box">
+            <el-image
+              style="width: 200px; height: 120px; border-radius: 4px; border: 1px solid #eee"
+              :src="currentItem.idCardFront"
+              :preview-src-list="[currentItem.idCardFront, currentItem.idCardBack]"
+              fit="cover"
+            >
+              <template #error>
+                <div class="image-slot">
+                  <el-icon><Picture /></el-icon>
+                </div>
+              </template>
+            </el-image>
+            <span class="label">身份证人像面</span>
+          </div>
+          <div class="img-box">
+            <el-image
+              style="width: 200px; height: 120px; border-radius: 4px; border: 1px solid #eee"
+              :src="currentItem.idCardBack"
+              :preview-src-list="[currentItem.idCardFront, currentItem.idCardBack]"
+              fit="cover"
+            >
+              <template #error>
+                <div class="image-slot">
+                  <el-icon><Picture /></el-icon>
+                </div>
+              </template>
+            </el-image>
+            <span class="label">身份证国徽面</span>
+          </div>
+        </div>
+
+        <!-- 资质信息 -->
+        <div class="section-title" style="margin-top: 20px">专业资质证明</div>
+        <div class="img-row" v-if="currentItem.qualifications && currentItem.qualifications.length">
+          <div class="img-box" v-for="(img, index) in currentItem.qualifications" :key="index">
+            <el-image
+              style="width: 120px; height: 120px; border-radius: 4px; border: 1px solid #eee"
+              :src="img"
+              :preview-src-list="currentItem.qualifications"
+              fit="cover"
+            />
+            <span class="label">资质材料 {{ index + 1 }}</span>
+          </div>
+        </div>
+        <div v-else style="color: #999; font-size: 13px; padding-left: 10px">未上传其他资质材料</div>
+      </div>
+      <template #footer>
+        <span class="dialog-footer" v-if="currentItem?.status === 'pending'">
+          <el-button type="danger" plain @click="rejectDialogVisible = true">驳回申请</el-button>
+          <el-button type="primary" @click="handlePass">通过审核</el-button>
+        </span>
+        <span class="dialog-footer" v-else>
+          <el-button @click="dialogVisible = false">关闭</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- 驳回原因弹窗 -->
+    <el-dialog v-model="rejectDialogVisible" title="驳回申请" width="400px">
+      <el-input v-model="rejectReason" type="textarea" :rows="4" placeholder="请输入驳回原因,将通知给申请人..." />
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="rejectDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="confirmReject">确认驳回</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+
+const searchKey = ref('');
+const activeTab = ref('all');
+const currentPage = ref(1);
+const pageSize = ref(10);
+const total = ref(3);
+
+const handleSizeChange = (val) => {
+  console.log(`每页 ${val} 条`);
+};
+const handleCurrentChange = (val) => {
+  console.log(`当前页: ${val}`);
+};
+
+const dialogVisible = ref(false);
+const rejectDialogVisible = ref(false);
+const rejectReason = ref('');
+const currentItem = ref(null);
+
+const tableData = ref([
+  {
+    id: 1,
+    name: '张三哥',
+    gender: 'male',
+    birthday: '1998-05-12',
+    phone: '13612345678',
+    workType: 'full_time',
+    city: '广东省 深圳市 龙华区',
+    location: '大润发(嘉熙业广场店)',
+    applyTime: '2026-02-04 10:00',
+    status: 'pending',
+    realName: '张三',
+    idNo: '44030619980512****',
+    idValidDate: '2036-05-12',
+    idCardFront: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
+    idCardBack: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
+    qualifications: ['https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg']
+  },
+  {
+    id: 2,
+    name: '李小妹',
+    gender: 'female',
+    birthday: '2000-11-20',
+    phone: '13933334444',
+    workType: 'part_time',
+    city: '北京市 朝阳区',
+    location: '三里屯SOHO',
+    applyTime: '2026-02-04 11:30',
+    status: 'pending',
+    realName: '李小妹',
+    idNo: '11010520001120****',
+    idValidDate: '2040-11-20',
+    idCardFront: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
+    idCardBack: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
+    qualifications: []
+  },
+  {
+    id: 3,
+    name: '周大华',
+    gender: 'male',
+    birthday: '1985-03-15',
+    phone: '13755556666',
+    workType: 'full_time',
+    city: '上海市 浦东新区',
+    location: '世纪广场',
+    applyTime: '2026-02-03 16:20',
+    status: 'rejected',
+    realName: '周大华',
+    idNo: '31011519850315****',
+    idValidDate: '2025-03-15',
+    idCardFront: '',
+    idCardBack: '',
+    qualifications: [],
+    auditInfo: {
+      auditor: 'admin',
+      time: '2026-02-03 17:00',
+      reason: '身份证照片模糊,无法识别'
+    }
+  }
+]);
+
+const getAge = (birthday) => {
+  if (!birthday) return 0;
+  const birthDate = new Date(birthday);
+  const today = new Date();
+  let age = today.getFullYear() - birthDate.getFullYear();
+  const m = today.getMonth() - birthDate.getMonth();
+  if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
+    age--;
+  }
+  return age;
+};
+
+const getStatusText = (status) => {
+  const map = { pending: '待审核', approved: '已通过', rejected: '已驳回' };
+  return map[status] || '未知';
+};
+
+const handleTabClick = (tab) => {
+  // Filter logic is handled by computed property
+};
+
+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));
+  }
+  return data;
+});
+
+const handleDetail = (row) => {
+  currentItem.value = JSON.parse(JSON.stringify(row));
+  dialogVisible.value = true;
+};
+
+const handlePass = () => {
+  ElMessage.success('已通过该申请');
+  const idx = tableData.value.findIndex((item) => item.id === currentItem.value.id);
+  if (idx !== -1) {
+    tableData.value[idx].status = 'approved';
+    tableData.value[idx].auditInfo = {
+      auditor: 'admin',
+      time: new Date().toLocaleString(),
+      result: 'pass'
+    };
+  }
+  dialogVisible.value = false;
+};
+
+const confirmReject = () => {
+  if (!rejectReason.value) {
+    ElMessage.warning('请输入驳回原因');
+    return;
+  }
+  ElMessage.warning('已驳回该申请');
+  const idx = tableData.value.findIndex((item) => item.id === currentItem.value.id);
+  if (idx !== -1) {
+    tableData.value[idx].status = 'rejected';
+    tableData.value[idx].auditInfo = {
+      auditor: 'admin',
+      time: new Date().toLocaleString(),
+      reason: rejectReason.value
+    };
+  }
+  rejectDialogVisible.value = false;
+  dialogVisible.value = false;
+  rejectReason.value = '';
+};
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 5px;
+}
+.title {
+  font-weight: bold;
+  font-size: 16px;
+  color: #333;
+}
+.right-panel {
+  display: flex;
+  align-items: center;
+}
+.search-input {
+  width: 250px;
+}
+.status-tabs {
+  margin-bottom: -10px;
+}
+
+/* User Info Styles */
+.user-info {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+}
+.text-col {
+  display: flex;
+  flex-direction: column;
+}
+.name-row {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  font-weight: bold;
+  font-size: 14px;
+  color: #333;
+}
+.sub-text {
+  font-size: 12px;
+  color: #999;
+  margin-top: 3px;
+}
+
+/* Work Info Styles */
+.work-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  font-size: 13px;
+  color: #606266;
+}
+.info-item {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+.info-item .label {
+  color: #909399;
+  width: 40px;
+}
+.location-text {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 200px;
+}
+
+/* Auth Card Styles */
+.auth-row {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+}
+.auth-card {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 2px 8px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  background-color: #f4f4f5;
+  color: #909399;
+  font-size: 12px;
+  cursor: default;
+}
+.auth-card.active {
+  border-color: #e1f3d8;
+  background-color: #f0f9eb;
+  color: #67c23a;
+}
+
+/* Status Styles */
+.status-cell {
+  display: flex;
+  align-items: center;
+  height: 100%;
+}
+.status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: 8px;
+}
+.status-dot.pending {
+  background-color: #e6a23c;
+}
+.status-dot.approved {
+  background-color: #67c23a;
+}
+.status-dot.rejected {
+  background-color: #f56c6c;
+}
+.status-text {
+  font-size: 13px;
+  color: #606266;
+}
+
+/* Operation Styles */
+.op-cell {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.el-dropdown-link {
+  font-size: 12px;
+  color: #409eff;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+}
+
+/* Dialog Styles */
+.section-title {
+  font-weight: bold;
+  margin-bottom: 12px;
+  border-left: 3px solid #409eff;
+  padding-left: 8px;
+  font-size: 14px;
+}
+.img-row {
+  display: flex;
+  gap: 15px;
+  flex-wrap: wrap;
+}
+.img-box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+}
+.img-box .label {
+  font-size: 12px;
+  color: #666;
+}
+.image-slot {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background: #f5f7fa;
+  color: #909399;
+  font-size: 30px;
+}
+
+/* Result Box */
+.result-box {
+  padding: 15px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
+.result-box.approved {
+  background-color: #f0f9eb;
+  border: 1px solid #e1f3d8;
+}
+.result-box.rejected {
+  background-color: #fef0f0;
+  border: 1px solid #fde2e2;
+}
+.result-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: bold;
+  font-size: 16px;
+  margin-bottom: 8px;
+}
+.result-box.approved .result-header {
+  color: #67c23a;
+}
+.result-box.rejected .result-header {
+  color: #f56c6c;
+}
+.result-info {
+  font-size: 13px;
+  color: #666;
+  display: flex;
+  gap: 20px;
+}
+.reject-reason {
+  margin-top: 8px;
+  font-size: 13px;
+  color: #f56c6c;
+  font-weight: bold;
+}
+
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+</style>

+ 506 - 0
src/views/fulfiller/level/index.vue

@@ -0,0 +1,506 @@
+<template>
+  <div class="page-container">
+    <el-tabs v-model="activeTab" type="card" class="level-tabs">
+      <el-tab-pane label="等级配置" name="levels">
+        <el-card shadow="never" class="content-card">
+          <template #header>
+            <div class="card-header">
+              <span class="title">等级体系列表</span>
+              <el-button type="primary" icon="Plus" @click="handleEditLevel(null)">新增等级</el-button>
+            </div>
+          </template>
+
+          <el-table :data="levelList" style="width: 100%" stripe>
+            <el-table-column prop="level" label="等级" width="100">
+              <template #default="{ row }">Lv.{{ row.level }}</template>
+            </el-table-column>
+            <el-table-column label="等级名称" width="150">
+              <template #default="{ row }">
+                <div class="level-name-col">
+                  <el-avatar shape="square" :size="30" :src="row.icon || 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'" />
+                  <span>{{ row.name }}</span>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="积分区间" width="200">
+              <template #default="{ row }"> {{ row.minPoints }} - {{ row.maxPoints === -1 ? '无限' : row.maxPoints }} </template>
+            </el-table-column>
+            <el-table-column label="节假日加成" width="150">
+              <template #default="{ row }">
+                <span style="color: #ff9900; font-weight: bold">{{ row.holidayMultiplier }}倍</span> 积分
+              </template>
+            </el-table-column>
+            <el-table-column label="包含权益" min-width="300">
+              <template #default="{ row }">
+                <el-tag v-for="right in row.rights" :key="right.id" size="small" class="right-tag" type="info">{{ right.name }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" width="180">
+              <template #default="{ scope }">
+                <el-button link type="primary" @click="handleEditLevel(scope.row)">配置</el-button>
+                <el-button link type="danger" @click="handleDeleteLevel(scope.row)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-tab-pane>
+
+      <el-tab-pane label="权益库管理" name="rights">
+        <el-card shadow="never" class="content-card">
+          <template #header>
+            <div class="card-header">
+              <span class="title">等级权益库</span>
+              <el-button type="primary" icon="Plus" @click="handleEditRight(null)">新增权益</el-button>
+            </div>
+          </template>
+          <el-table :data="rightsList" style="width: 100%">
+            <el-table-column prop="name" label="权益名称" width="200">
+              <template #default="{ row }">
+                <div class="level-name-col">
+                  <el-image
+                    style="width: 30px; height: 30px; border-radius: 4px"
+                    :src="row.icon || 'https://cube.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'"
+                    fit="cover"
+                  />
+                  <span>{{ row.name }}</span>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="status" label="状态" width="100">
+              <template #default="{ row }">
+                <el-switch v-model="row.status" size="small" @change="handleRightStatusChange(row)" />
+              </template>
+            </el-table-column>
+            <el-table-column prop="desc" label="权益说明" show-overflow-tooltip />
+            <el-table-column label="操作" width="150">
+              <template #default="{ row }">
+                <el-button link type="primary" @click="handleEditRight(row)">编辑</el-button>
+                <el-button link type="danger">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 等级编辑弹窗 -->
+    <el-dialog v-model="levelDialog.visible" :title="levelDialog.isEdit ? '编辑等级' : '新增等级'" width="800px" top="5vh">
+      <el-form :model="levelDialog.form" label-width="120px">
+        <el-tabs type="border-card">
+          <el-tab-pane label="基础设置">
+            <el-form-item label="等级数值(Lv)" required>
+              <el-input-number v-model="levelDialog.form.level" :min="1" />
+            </el-form-item>
+            <el-form-item label="等级名称" required>
+              <el-input v-model="levelDialog.form.name" placeholder="如:青铜骑士" />
+            </el-form-item>
+            <el-form-item label="等级图标">
+              <el-upload
+                class="avatar-uploader"
+                action="#"
+                :show-file-list="false"
+                :auto-upload="false"
+                :on-change="(file) => handleLevelIconChange(file, 'icon')"
+              >
+                <img v-if="levelDialog.form.icon" :src="levelDialog.form.icon" class="avatar" />
+                <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+              </el-upload>
+              <div class="tips">建议尺寸 100x100px</div>
+            </el-form-item>
+            <el-form-item label="背景图片">
+              <el-upload
+                class="bg-uploader"
+                action="#"
+                :show-file-list="false"
+                :auto-upload="false"
+                :on-change="(file) => handleLevelIconChange(file, 'bg')"
+              >
+                <img v-if="levelDialog.form.bg" :src="levelDialog.form.bg" class="bg-img" />
+                <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+              </el-upload>
+              <div class="tips">建议尺寸 600x300px</div>
+            </el-form-item>
+
+            <el-divider content-position="left">积分规则</el-divider>
+
+            <el-row>
+              <el-col :span="12">
+                <el-form-item label="最低积分" required>
+                  <el-input-number v-model="levelDialog.form.minPoints" :min="0" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="最高积分">
+                  <el-input-number v-model="levelDialog.form.maxPoints" :min="-1" placeholder="-1为无限" />
+                  <div class="tips">设置-1代表无上限</div>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="节假日倍数" required>
+              <el-input-number v-model="levelDialog.form.holidayMultiplier" :min="1" :precision="1" :step="0.1" />
+              <div class="tips">法定节假日接单获得的积分倍率</div>
+            </el-form-item>
+
+            <el-divider content-position="left">权益配置</el-divider>
+
+            <el-form-item label="权益关联">
+              <el-checkbox-group v-model="levelDialog.form.rightsIds">
+                <el-checkbox v-for="r in rightsList" :key="r.id" :label="r.id" :disabled="!r.status">
+                  {{ r.name }}
+                  <span v-if="!r.status" style="color: #999; font-size: 12px">(已停用)</span>
+                </el-checkbox>
+              </el-checkbox-group>
+            </el-form-item>
+          </el-tab-pane>
+
+          <el-tab-pane label="升级规则">
+            <div class="rule-group">
+              <div class="rule-title">自动升级条件 (需全部满足)</div>
+              <el-form-item label="考核周期">
+                最近 <el-input-number v-model="levelDialog.form.rules.periodDays" :min="1" size="small" style="width: 100px" /> 天
+              </el-form-item>
+              <el-form-item label="完成单量">
+                ≥ <el-input-number v-model="levelDialog.form.rules.minOrders" :min="0" size="small" style="width: 100px" /> 单
+              </el-form-item>
+              <el-form-item label="上门准时率">
+                ≥ <el-input-number v-model="levelDialog.form.rules.onTimeRate" :min="0" :max="100" size="small" style="width: 100px" /> %
+              </el-form-item>
+              <el-form-item label="投诉率">
+                ≤ <el-input-number v-model="levelDialog.form.rules.complaintRate" :min="0" :max="100" size="small" style="width: 100px" /> %
+              </el-form-item>
+              <el-form-item label="违规次数">
+                ≤ <el-input-number v-model="levelDialog.form.rules.maxViolations" :min="0" size="small" style="width: 100px" /> 次
+              </el-form-item>
+              <el-form-item label="积分门槛">
+                累计获得积分 ≥ <el-input-number v-model="levelDialog.form.rules.accumulatedPoints" :min="0" size="small" style="width: 120px" />
+              </el-form-item>
+            </div>
+          </el-tab-pane>
+
+          <el-tab-pane label="惩罚/降级">
+            <el-form-item label="降级规则">
+              <el-radio-group v-model="levelDialog.form.downgradeType">
+                <el-radio label="points">仅积分不足时降级</el-radio>
+                <el-radio label="strict">不满足升级条件即降级</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <div class="rule-group" style="margin-top: 10px">
+              <div class="rule-title">扣分配置</div>
+              <el-form-item label="准时率过低">
+                每低于1% 扣 <el-input-number v-model="levelDialog.form.deductions.late" :min="0" size="small" /> 分
+              </el-form-item>
+              <el-form-item label="一般违规">
+                每次 扣 <el-input-number v-model="levelDialog.form.deductions.violation" :min="0" size="small" /> 分
+              </el-form-item>
+              <el-form-item label="严重投诉">
+                每次 扣 <el-input-number v-model="levelDialog.form.deductions.complaint" :min="0" size="small" /> 分
+              </el-form-item>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </el-form>
+      <template #footer>
+        <el-button @click="levelDialog.visible = false">取消</el-button>
+        <el-button type="primary" @click="saveLevel">保存配置</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 权益编辑弹窗 -->
+    <el-dialog v-model="rightDialog.visible" :title="rightDialog.isEdit ? '编辑权益' : '新增权益'" width="500px">
+      <el-form :model="rightDialog.form" label-width="100px">
+        <el-form-item label="权益名称" required>
+          <el-input v-model="rightDialog.form.name" />
+        </el-form-item>
+        <el-form-item label="权益图标">
+          <el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false" :on-change="handleRightIconChange">
+            <img v-if="rightDialog.form.icon" :src="rightDialog.form.icon" class="avatar" />
+            <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
+          </el-upload>
+        </el-form-item>
+        <el-form-item label="权益状态">
+          <el-switch v-model="rightDialog.form.status" active-text="启用" inactive-text="停用" />
+        </el-form-item>
+        <el-form-item label="权益描述">
+          <el-input v-model="rightDialog.form.desc" type="textarea" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="rightDialog.visible = false">取消</el-button>
+        <el-button type="primary" @click="saveRight">保存</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+
+const activeTab = ref('levels');
+
+// Data: Rights
+const rightsList = ref([
+  {
+    id: 1,
+    name: '节假日双倍积分',
+    icon: 'https://cube.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
+    status: true,
+    desc: '在法定节假日接单,获得积分翻倍'
+  },
+  {
+    id: 2,
+    name: '专属人工客服',
+    icon: 'https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png',
+    status: true,
+    desc: '拥有专属客服经理,问题优先处理'
+  },
+  {
+    id: 3,
+    name: '提现免手续费',
+    icon: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
+    status: true,
+    desc: '每月前3笔提现免除平台手续费'
+  }
+]);
+
+// Data: Levels
+const levelList = ref([
+  {
+    id: 1,
+    level: 1,
+    name: '青铜骑士',
+    icon: '',
+    minPoints: 0,
+    maxPoints: 1000,
+    holidayMultiplier: 1.0,
+    rights: [rightsList.value[1]],
+    rules: { periodDays: 30, minOrders: 10, onTimeRate: 90, complaintRate: 5, maxViolations: 0, accumulatedPoints: 100 },
+    deductions: { late: 5, violation: 20, complaint: 50 },
+    downgradeType: 'points'
+  },
+  {
+    id: 2,
+    level: 2,
+    name: '白银卫士',
+    icon: '',
+    minPoints: 1001,
+    maxPoints: 5000,
+    holidayMultiplier: 1.2,
+    rights: [rightsList.value[1], rightsList.value[2]],
+    rules: { periodDays: 30, minOrders: 30, onTimeRate: 95, complaintRate: 2, maxViolations: 0, accumulatedPoints: 1000 },
+    deductions: { late: 10, violation: 50, complaint: 100 },
+    downgradeType: 'points'
+  },
+  {
+    id: 3,
+    level: 3,
+    name: '黄金领主',
+    icon: '',
+    minPoints: 5001,
+    maxPoints: -1,
+    holidayMultiplier: 1.5,
+    rights: [rightsList.value[0], rightsList.value[1], rightsList.value[2]],
+    rules: { periodDays: 30, minOrders: 100, onTimeRate: 98, complaintRate: 0, maxViolations: 0, accumulatedPoints: 5000 },
+    deductions: { late: 20, violation: 100, complaint: 200 },
+    downgradeType: 'points'
+  }
+]);
+
+// Dialogs
+const levelDialog = reactive({
+  visible: false,
+  isEdit: false,
+  form: {
+    level: 1,
+    name: '',
+    icon: '',
+    bg: '',
+    minPoints: 0,
+    maxPoints: -1,
+    holidayMultiplier: 1.0,
+    rightsIds: [],
+    rules: { periodDays: 30, minOrders: 0, onTimeRate: 95, complaintRate: 0, maxViolations: 0, accumulatedPoints: 0 },
+    deductions: { late: 10, violation: 50, complaint: 100 },
+    downgradeType: 'points'
+  }
+});
+
+const rightDialog = reactive({
+  visible: false,
+  isEdit: false,
+  form: { name: '', icon: '', status: true, desc: '' }
+});
+
+// Methods
+const handleEditLevel = (row) => {
+  levelDialog.isEdit = !!row;
+  levelDialog.visible = true;
+  if (row) {
+    // Deep copy to break ref
+    const rightsIds = row.rights.map((r) => r.id);
+    levelDialog.form = JSON.parse(JSON.stringify({ ...row, rightsIds }));
+  } else {
+    // Reset
+    levelDialog.form = {
+      level: levelList.value.length + 1,
+      name: '',
+      icon: '',
+      bg: '',
+      minPoints: 0,
+      maxPoints: -1,
+      holidayMultiplier: 1.0,
+      rightsIds: [],
+      rules: { periodDays: 30, minOrders: 0, onTimeRate: 95, complaintRate: 0, maxViolations: 0, accumulatedPoints: 0 },
+      deductions: { late: 10, violation: 50, complaint: 100 },
+      downgradeType: 'points'
+    };
+  }
+};
+
+const handleLevelIconChange = (file, type) => {
+  // 模拟上传,实际应调用后端接口,这里直接读取本地文件展示
+  const url = URL.createObjectURL(file.raw);
+  if (type === 'icon') {
+    levelDialog.form.icon = url;
+  } else {
+    levelDialog.form.bg = url;
+  }
+};
+
+const saveLevel = () => {
+  // Map rightsIds back to objects
+  const selectedRights = rightsList.value.filter((r) => levelDialog.form.rightsIds.includes(r.id));
+  const newLevel = { ...levelDialog.form, rights: selectedRights };
+
+  if (levelDialog.isEdit) {
+    const idx = levelList.value.findIndex((l) => l.id === newLevel.id);
+    if (idx !== -1) levelList.value[idx] = newLevel;
+  } else {
+    newLevel.id = Date.now();
+    levelList.value.push(newLevel);
+  }
+
+  levelList.value.sort((a, b) => a.level - b.level);
+  levelDialog.visible = false;
+  ElMessage.success('等级配置保存成功');
+};
+
+const handleDeleteLevel = () => {
+  ElMessage.warning('演示模式:暂不执行删除');
+};
+
+const handleEditRight = (row) => {
+  rightDialog.isEdit = !!row;
+  rightDialog.visible = true;
+  if (row) {
+    rightDialog.form = { ...row };
+  } else {
+    rightDialog.form = { name: '', icon: '', status: true, desc: '' };
+  }
+};
+
+const handleRightIconChange = (file) => {
+  rightDialog.form.icon = URL.createObjectURL(file.raw);
+};
+
+const saveRight = () => {
+  if (rightDialog.isEdit) {
+    const idx = rightsList.value.findIndex((r) => r.id === rightDialog.form.id);
+    if (idx !== -1) rightsList.value[idx] = { ...rightsList.value[idx], ...rightDialog.form };
+  } else {
+    // Default icon if empty for demo
+    if (!rightDialog.form.icon) rightDialog.form.icon = 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png';
+    rightsList.value.push({ ...rightDialog.form, id: Date.now() });
+  }
+  rightDialog.visible = false;
+  ElMessage.success('权益保存成功');
+};
+
+const handleRightStatusChange = (row) => {
+  ElMessage.success(`权益 "${row.name}" 已${row.status ? '启用' : '停用'}`);
+};
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.title {
+  font-weight: bold;
+  font-size: 16px;
+}
+
+.level-name-col {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: bold;
+}
+.right-tag {
+  margin-right: 5px;
+  margin-bottom: 5px;
+}
+
+.avatar-uploader,
+.bg-uploader {
+  width: 80px;
+  height: 80px;
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.bg-uploader {
+  width: 160px;
+  height: 80px;
+}
+.avatar-uploader:hover,
+.bg-uploader:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+}
+.avatar {
+  width: 80px;
+  height: 80px;
+  display: block;
+  object-fit: cover;
+}
+.bg-img {
+  width: 160px;
+  height: 80px;
+  display: block;
+  object-fit: cover;
+}
+
+.tips {
+  font-size: 12px;
+  color: #999;
+  margin-left: 10px;
+  display: inline-block;
+}
+
+.rule-group {
+  background: #f8fcfb;
+  padding: 15px;
+  border-radius: 4px;
+  border: 1px solid #ebf5f2;
+}
+.rule-title {
+  font-weight: bold;
+  margin-bottom: 12px;
+  font-size: 14px;
+  color: #333;
+}
+</style>

+ 1105 - 0
src/views/fulfiller/pool/index.vue

@@ -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>

+ 137 - 0
src/views/fulfiller/tag/index.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="page-container">
+    <el-card shadow="never">
+      <template #header>
+        <div class="card-header">
+          <span class="title">履约者标签管理</span>
+          <el-button type="primary" icon="Plus" @click="handleAdd">新增标签</el-button>
+        </div>
+      </template>
+
+      <el-table :data="tableData" border style="width: 100%">
+        <el-table-column prop="name" label="标签名称" width="200">
+          <template #default="scope">
+            <el-tag :type="scope.row.colorType">{{ scope.row.name }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="desc" label="说明" />
+        <el-table-column prop="status" label="状态" width="100">
+          <template #default="scope">
+            <el-switch v-model="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="150">
+          <template #default="scope">
+            <el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+            <el-button link type="danger">删除</el-button>
+          </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>
+
+    <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑标签' : '新增标签'" width="400px">
+      <el-form :model="form" label-width="80px">
+        <el-form-item label="标签名称" required>
+          <el-input v-model="form.name" placeholder="如:非常有耐心" />
+        </el-form-item>
+        <el-form-item label="标签颜色">
+          <el-select v-model="form.colorType" style="width: 100%">
+            <el-option label="默认蓝" value="" />
+            <el-option label="成功绿" value="success" />
+            <el-option label="警告橙" value="warning" />
+            <el-option label="危险红" value="danger" />
+            <el-option label="信息灰" value="info" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="描述说明">
+          <el-input v-model="form.desc" type="textarea" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="saveTag">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+
+const dialogVisible = ref(false);
+const isEdit = ref(false);
+
+const currentPage = ref(1);
+const pageSize = ref(10);
+const total = ref(100);
+
+const handleSizeChange = (val) => {
+  console.log(`每页 ${val} 条`);
+};
+const handleCurrentChange = (val) => {
+  console.log(`当前页: ${val}`);
+};
+
+const tableData = ref([
+  { id: 1, name: '接单王', colorType: 'danger', desc: '累计接单数量TOP10', status: true },
+  { id: 2, name: '零差评', colorType: 'success', desc: '近半年无差评记录', status: true },
+  { id: 3, name: '车技娴熟', colorType: 'primary', desc: '驾驶技术评价高', status: true },
+  { id: 4, name: '五星金牌', colorType: 'warning', desc: '系统评定最高等级', status: true }
+]);
+
+const form = reactive({
+  name: '',
+  colorType: '',
+  desc: ''
+});
+
+const handleAdd = () => {
+  isEdit.value = false;
+  Object.assign(form, { name: '', colorType: '', desc: '' });
+  dialogVisible.value = true;
+};
+
+const handleEdit = (row) => {
+  isEdit.value = true;
+  Object.assign(form, row);
+  dialogVisible.value = true;
+};
+
+const saveTag = () => {
+  ElMessage.success('保存成功');
+  dialogVisible.value = false;
+};
+</script>
+
+<style scoped>
+.page-container {
+  padding: 20px;
+}
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.title {
+  font-weight: bold;
+}
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20px;
+}
+</style>

+ 14 - 9
src/views/system/store/index.vue

@@ -389,6 +389,7 @@ const initFormData: StoreForm = {
   tenantId: undefined,
   services: [],
   regionId: undefined,
+  areaCode: undefined,
 }
 const data = reactive<PageData<StoreForm, SysStorePageBo>>({
   form: { ...initFormData },
@@ -508,6 +509,15 @@ const handleUpdate = async (row?: StoreVO) => {
   const _id = row?.id || ids.value[0]
   const res = await getStore(_id);
   Object.assign(form.value, res.data);
+  
+  if (res.data.areaCode) {
+    if (Array.isArray(res.data.areaCode)) {
+      addressCascaderValue.value = res.data.areaCode;
+    } else if (typeof res.data.areaCode === 'string') {
+      addressCascaderValue.value = res.data.areaCode.split(',');
+    }
+  }
+  
   dialog.visible = true;
   dialog.title = "修改门店管理";
 }
@@ -715,19 +725,14 @@ const handleBrandSelectVisibleChange = (visible: boolean) => {
   }
 };
 
-// 监听省市区选择变化,自动追加到详细地址
+// 监听省市区选择变化,不追加到详细地址,直接存储到区域编码中
 watch(
   addressCascaderValue,
   (newValue) => {
     if (newValue && newValue.length > 0) {
-      // 将选中的省市区文本追加到详细地址
-      const addressText = newValue.map(code => codeToText[code]).join('');
-      if (form.value.detailAddress) {
-        // 如果已有详细地址,将省市区放在前面
-        form.value.detailAddress = addressText + form.value.detailAddress;
-      } else {
-        form.value.detailAddress = addressText;
-      }
+      form.value.areaCode = newValue.join(',');
+    } else {
+      form.value.areaCode = undefined;
     }
   },
   { deep: true }