Переглянути джерело

Merge branch 'refs/heads/cfclub-jiapu' into cfclub

# Conflicts:
#	cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentAppointmentService.java
#	cfc-frontend/pages.json
jiapu 3 місяців тому
батько
коміт
4ff6813e88

+ 5 - 1
cfc-backend/AGENTS.md

@@ -211,4 +211,8 @@ mvn test                 # 运行测试 (目前只有1个测试类)
 - 街道数据:使用静态种子数据,街道匹配有回退机制
 - API冲突已修复:检查所有Controller确保无重复路径映射
 - sfms 源库连接信息在 `application.yml` 的 `sfms.datasource.*`
-- 数据迁移 POST `/api/migration/run`
+- 数据迁移 POST `/api/migration/run`
+
+## 后端接口规范
+
+- 后台/api的接口规范都用post方式

+ 35 - 3
cfc-backend/src/main/java/com/etotem/cfc/controller/DanAssessmentController.java

@@ -32,6 +32,8 @@ public class DanAssessmentController {
     private AssessmentPermissionService permissionService;
     @Resource
     private com.etotem.cfc.mapper.DanAssessmentResultMapper danAssessmentResultMapper;
+    @Resource
+    private com.etotem.cfc.mapper.UserMapper userMapper;
 
     @Resource
     private DanReportParseService danReportParseService;
@@ -204,10 +206,40 @@ public class DanAssessmentController {
         return ok ? Result.success("取消成功") : Result.error("取消失败");
     }
 
-@Operation(summary = "获取用户的预约列表")
+    @Operation(summary = "获取用户的预约列表")
     @PostMapping("/appointment/user")
-    public Result<List<AssessmentAppointment>> getUserAppointments(@RequestParam Long userId) {
-        return Result.success(appointmentService.getByUserId(userId));
+    public Result<List<Map<String, Object>>> getUserAppointments(@RequestBody Map<String, Object> body) {
+        Long userId = body.get("userId") != null ? Long.valueOf(body.get("userId").toString()) : null;
+        if (userId == null) return Result.error("userId 不能为空");
+        List<AssessmentAppointment> list = appointmentService.getByUserId(userId);
+        List<Map<String, Object>> result = new java.util.ArrayList<>();
+        for (AssessmentAppointment app : list) {
+            Map<String, Object> item = new java.util.LinkedHashMap<>();
+            item.put("id", app.getId());
+            item.put("userId", app.getUserId());
+            item.put("familyId", app.getFamilyId());
+            item.put("childId", app.getChildId());
+            item.put("guideId", app.getGuideId());
+            item.put("packageId", app.getPackageId());
+            item.put("appointmentDate", app.getAppointmentDate());
+            item.put("appointmentTime", app.getAppointmentTime());
+            item.put("durationMinutes", app.getDurationMinutes());
+            item.put("status", app.getStatus());
+            item.put("appointmentType", app.getAppointmentType());
+            item.put("notes", app.getNotes());
+            item.put("contactPhone", app.getContactPhone());
+            item.put("meetLink", app.getMeetLink());
+            item.put("createdAt", app.getCreatedAt());
+            // 关联查询规划师名称
+            if (app.getGuideId() != null) {
+                com.etotem.cfc.entity.User guide = userMapper.selectById(app.getGuideId());
+                item.put("guideName", guide != null ? guide.getNickname() : "");
+            } else {
+                item.put("guideName", "");
+            }
+            result.add(item);
+        }
+        return Result.success(result);
     }
 
     @Operation(summary = "获取规划师的预约列表")

+ 123 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java

@@ -66,6 +66,9 @@ public class AdminController {
     @Resource
     private AssessmentAppointmentMapper assessmentAppointmentMapper;
 
+    @Resource
+    private AssessmentOrderMapper assessmentOrderMapper;
+
     // 用户管理
     @PostMapping("/users")
     public Result<Page<User>> getUsers(@RequestBody Map<String, Object> params) {
@@ -1053,6 +1056,126 @@ public class AdminController {
         return Result.success(list);
     }
 
+    // ========== 测评订单管理(基于预约表) ==========
+
+    @PostMapping("/assessment/orders")
+    public Result<Map<String, Object>> getAssessmentOrderList(@RequestBody Map<String, Object> params) {
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
+        Page<AssessmentAppointment> pageParam = new Page<>(page, size);
+
+        LambdaQueryWrapper<AssessmentAppointment> wrapper = new LambdaQueryWrapper<>();
+        // 按状态筛选
+        if (params.containsKey("status") && params.get("status") != null && !params.get("status").toString().isEmpty()) {
+            wrapper.eq(AssessmentAppointment::getStatus, Integer.parseInt(params.get("status").toString()));
+        }
+        // 按关键词搜索(用户昵称/规划师昵称)
+        if (params.containsKey("keyword") && params.get("keyword") != null && !params.get("keyword").toString().isEmpty()) {
+            // 先查匹配的用户ID和规划师ID
+            String kw = params.get("keyword").toString();
+            LambdaQueryWrapper<User> userWrapper = new LambdaQueryWrapper<>();
+            userWrapper.like(User::getNickname, kw).select(User::getId);
+            java.util.List<User> matchedUsers = userMapper.selectList(userWrapper);
+            java.util.List<Long> matchedIds = new java.util.ArrayList<>();
+            for (User u : matchedUsers) { matchedIds.add(u.getId()); }
+            if (matchedIds.isEmpty()) {
+                matchedIds.add(-1L); // 无匹配时返回空
+            }
+            wrapper.and(w -> w.in(AssessmentAppointment::getUserId, matchedIds)
+                    .or().in(AssessmentAppointment::getGuideId, matchedIds));
+        }
+        wrapper.orderByDesc(AssessmentAppointment::getCreatedAt);
+
+        Page<AssessmentAppointment> result = assessmentAppointmentMapper.selectPage(pageParam, wrapper);
+
+        java.util.List<Map<String, Object>> list = new java.util.ArrayList<>();
+        for (AssessmentAppointment app : result.getRecords()) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", app.getId());
+            item.put("familyId", app.getFamilyId());
+            item.put("userId", app.getUserId());
+            item.put("childId", app.getChildId());
+            item.put("guideId", app.getGuideId());
+            item.put("teacherId", app.getTeacherId());
+            item.put("packageId", app.getPackageId());
+            item.put("appointmentDate", app.getAppointmentDate());
+            item.put("appointmentTime", app.getAppointmentTime());
+            item.put("durationMinutes", app.getDurationMinutes());
+            item.put("appointmentType", app.getAppointmentType());
+            item.put("notes", app.getNotes());
+            item.put("contactPhone", app.getContactPhone());
+            item.put("status", app.getStatus());
+            item.put("createdAt", app.getCreatedAt());
+
+            // 关联查询用户昵称
+            User user = userMapper.selectById(app.getUserId());
+            item.put("userName", user != null ? user.getNickname() : "未知用户");
+
+            // 关联查询孩子昵称
+            if (app.getChildId() != null) {
+                FamilyMember child = familyMemberMapper.selectById(app.getChildId());
+                item.put("childName", child != null ? child.getNickname() : "未知");
+            } else {
+                item.put("childName", "");
+            }
+
+            // 关联查询规划师昵称
+            Long guideId = app.getGuideId() != null ? app.getGuideId() : app.getTeacherId();
+            if (guideId != null) {
+                User guide = userMapper.selectById(guideId);
+                item.put("guideName", guide != null ? guide.getNickname() : "未知规划师");
+            } else {
+                item.put("guideName", "");
+            }
+
+            // 关联查询方案名称
+            if (app.getPackageId() != null) {
+                GuidePackage pkg = guidePackageMapper.selectById(app.getPackageId());
+                item.put("packageName", pkg != null ? pkg.getName() : "未知方案");
+            } else {
+                item.put("packageName", "");
+            }
+
+            list.add(item);
+        }
+
+        Map<String, Object> resultMap = new HashMap<>();
+        resultMap.put("records", list);
+        resultMap.put("total", result.getTotal());
+        resultMap.put("current", result.getCurrent());
+        resultMap.put("size", result.getSize());
+        return Result.success(resultMap);
+    }
+
+    @PostMapping("/assessment/change-guide")
+    public Result<Void> changeAssessmentGuide(@RequestBody Map<String, Object> body) {
+        Long appointmentId = body.get("orderId") != null ? Long.valueOf(body.get("orderId").toString()) : null;
+        Long guideId = body.get("guideId") != null ? Long.valueOf(body.get("guideId").toString()) : null;
+        if (appointmentId == null || guideId == null) {
+            return Result.error("appointmentId 和 guideId 不能为空");
+        }
+
+        AssessmentAppointment appointment = assessmentAppointmentMapper.selectById(appointmentId);
+        if (appointment == null) {
+            return Result.error("预约记录不存在");
+        }
+
+        User guide = userMapper.selectById(guideId);
+        if (guide == null || !"teacher".equals(guide.getRole())) {
+            return Result.error("规划师不存在或角色不正确");
+        }
+
+        appointment.setGuideId(guideId);
+        appointment.setTeacherId(guideId);
+        if (appointment.getStatus() != null && appointment.getStatus() == 0) {
+            appointment.setStatus(1); // 待确认 → 已确认
+        }
+        appointment.setUpdatedAt(new Date());
+        assessmentAppointmentMapper.updateById(appointment);
+
+        return Result.success(null);
+    }
+
     @PostMapping("/assessment/assign-guide")
     public Result<Void> assignGuide(@RequestBody Map<String, Object> body) {
         Long appointmentId = body.get("appointmentId") != null ? Long.valueOf(body.get("appointmentId").toString()) : null;

+ 26 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentOrderService.java

@@ -5,7 +5,9 @@ import com.etotem.cfc.service.api.AssessmentOrderServiceInterface;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.etotem.cfc.entity.AssessmentOrder;
+import com.etotem.cfc.entity.AssessmentAppointment;
 import com.etotem.cfc.mapper.AssessmentOrderMapper;
+import com.etotem.cfc.mapper.AssessmentAppointmentMapper;
 import com.etotem.cfc.service.CommissionService;
 import org.springframework.stereotype.Service;
 
@@ -15,12 +17,18 @@ import java.util.Date;
 import java.util.List;
 import java.util.Random;
 
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @Service
 public class AssessmentOrderService extends ServiceImpl<AssessmentOrderMapper, AssessmentOrder> {
 
     @Resource
     private AssessmentOrderMapper assessmentOrderMapper;
 
+    @Resource
+    private AssessmentAppointmentMapper assessmentAppointmentMapper;
+
     @Resource
     private CommissionService commissionService;
 
@@ -147,6 +155,23 @@ public class AssessmentOrderService extends ServiceImpl<AssessmentOrderMapper, A
         order.setPayTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
         order.setPayType(payType);
         order.setUpdatedAt(new Date());
-        return this.updateById(order);
+        boolean updated = this.updateById(order);
+
+        // 支付成功后自动确认关联预约
+        if (updated && order.getAppointmentId() != null) {
+            try {
+                AssessmentAppointment appointment = assessmentAppointmentMapper.selectById(order.getAppointmentId());
+                if (appointment != null && appointment.getStatus() != null && appointment.getStatus() == 0) {
+                    appointment.setStatus(1); // 待确认 → 已确认
+                    appointment.setUpdatedAt(new Date());
+                    assessmentAppointmentMapper.updateById(appointment);
+                    log.info("订单支付成功自动确认预约: orderNo={}, appointmentId={}", orderNo, order.getAppointmentId());
+                }
+            } catch (Exception e) {
+                log.warn("订单支付后确认预约失败: orderNo={}, error={}", orderNo, e.getMessage());
+            }
+        }
+
+        return updated;
     }
 }

+ 24 - 0
cfc-frontend/pages.json

@@ -159,6 +159,30 @@
             "navigationBarTitleText": "任务模板详情"
           }
         },
+        {
+          "path": "plans/index",
+          "style": {
+            "navigationBarTitleText": "我的计划"
+          }
+        },
+        {
+          "path": "plans/detail",
+          "style": {
+            "navigationBarTitleText": "计划详情"
+          }
+        },
+        {
+          "path": "wishes/index",
+          "style": {
+            "navigationBarTitleText": "心愿管理"
+          }
+        },
+        {
+          "path": "appointments",
+          "style": {
+            "navigationBarTitleText": "我的预约"
+          }
+        },
         {
           "path": "child-detail",
           "style": {

+ 4 - 0
cfc-frontend/pages/assessment/results.vue

@@ -3,6 +3,7 @@
     <view class="header">
       <text class="title">测评报告</text>
       <text class="subtitle">查看下级成长规划师的测评报告</text>
+      <text class="appointments-link" @click="goToAppointments">查看我的预约 ›</text>
     </view>
 
     <!-- 标签页 -->
@@ -220,6 +221,9 @@ export default {
       if (!dateStr) return ''
       const date = new Date(dateStr)
       return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
+    },
+    goToAppointments() {
+      uni.navigateTo({ url: '/pages/parent/appointments' })
     }
   }
 }

+ 9 - 0
cfc-frontend/pages/parent/appointments.vue

@@ -28,6 +28,14 @@
             <text class="label">备注</text>
             <text class="value">{{ item.notes }}</text>
           </view>
+          <view class="info-row" v-if="item.guideName">
+            <text class="label">规划师</text>
+            <text class="value guide-name">{{ item.guideName }}</text>
+          </view>
+          <view class="info-row" v-if="item.status === 1 && !item.guideName">
+            <text class="label">规划师</text>
+            <text class="value">待分配</text>
+          </view>
           <view class="info-row">
             <text class="label">创建时间</text>
             <text class="value">{{ formatTime(item.createdAt) }}</text>
@@ -189,6 +197,7 @@ export default {
 
 .label { color: #999; }
 .value { color: #333; }
+.guide-name { color: #F97316; font-weight: bold; }
 
 .card-footer {
   padding-top: 16rpx;

+ 7 - 0
cfc-frontend/pages/profile/components/ProfileMenu.vue

@@ -8,6 +8,10 @@
           <text>👨‍👩‍👧‍👦 家庭成员</text>
           <text class="arrow">›</text>
         </view>
+        <view class="menu-item" v-if="role === 'parent'" @click="goToAppointments">
+          <text>📅 测评预约</text>
+          <text class="arrow">›</text>
+        </view>
         <view class="menu-item" @click="goToDailyTasks">
           <text>📋 每日任务</text>
           <text class="arrow">›</text>
@@ -137,6 +141,9 @@ export default {
     goToFamilyMembers() {
       uni.navigateTo({ url: '/pages/profile-extra/family-members' })
     },
+    goToAppointments() {
+      uni.navigateTo({ url: '/pages/parent/appointments' })
+    },
     goToDailyTasks() {
       uni.navigateTo({ url: '/pages/tasks/daily-tasks' })
     },

+ 11 - 0
cfc-frontend/utils/api.js

@@ -2110,4 +2110,15 @@ export const queryFoodKnowledge = (name) => {
 // 获取KB v3章节数据
 export const getKnowledgeSection = (section) => {
   return request('/api/health/knowledge/v3/section', 'POST', { section: section || '' })
+}
+
+// 获取我的测评预约列表
+export const getMyAppointments = () => {
+  var userId = uni.getStorageSync('userId')
+  return request('/api/dan-assessment/appointment/user', 'POST', { userId: userId })
+}
+
+// 取消测评预约
+export const cancelAppointment = (id) => {
+  return request('/api/dan-assessment/appointment/cancel', 'POST', { id: id })
 }

+ 18 - 1
cfc-web/src/api/admin.js

@@ -1334,7 +1334,7 @@ export function reviewSupplyHierarchyChange(data) {
         method: 'post',
         data
     })
-}
+}
 
 // ========== 家庭会员订阅管理 API ==========
 
@@ -1596,3 +1596,20 @@ export function assignGuide(appointmentId, guideId) {
     data: { appointmentId, guideId }
   })
 }
+
+// ===== 测评订单管理 =====
+export function getAssessmentOrderList(params) {
+  return request({
+    url: '/api/admin/assessment/orders',
+    method: 'post',
+    data: params || {}
+  })
+}
+
+export function changeAssessmentGuide(orderId, guideId) {
+  return request({
+    url: '/api/admin/assessment/change-guide',
+    method: 'post',
+    data: { orderId, guideId }
+  })
+}

+ 8 - 2
cfc-web/src/router/index.js

@@ -192,6 +192,12 @@ const routes = [
         component: () => import('@/views/admin/AssessmentAssign.vue'),
         meta: { title: '待分配规划师', perm: 'assessment:dan' }
       },
+      {
+        path: 'assessment-orders',
+        name: 'AssessmentOrders',
+        component: () => import('@/views/admin/AssessmentOrders.vue'),
+        meta: { title: '测评订单', perm: 'assessment:dan' }
+      },
       {
         path: 'product-manage',
         name: 'ProductManage',
@@ -327,7 +333,7 @@ const routes = [
     component: () => import('@/views/admin/SupplierProductManage.vue'),
     meta: { title: '商品管理', perm: 'commerce:product' }
   },
-  {
+  {
             path: 'supply-system/:id/edit',
             name: 'SupplySystemEdit',
             component: () => import('@/views/admin/supply-system/Form.vue'),
@@ -339,7 +345,7 @@ const routes = [
             name: 'SupplyHierarchyChangeRequests',
             component: () => import('@/views/admin/supply-hierarchy/ChangeRequest.vue'),
             meta: { title: '更换上级审核', perm: 'system:supply' }
-        },
+        },
       // ========== 供应商管理 (supplier_admin) ==========
       {
         path: 'supply-manage',

+ 1 - 0
cfc-web/src/views/Layout.vue

@@ -294,6 +294,7 @@ export default {
             { title: '测评管理', icon: 'el-icon-edit', perm: 'assessment:dan',
               children: [
                 { path: '/assessment-admin', label: '测评管理', icon: 'el-icon-edit', perm: 'assessment:dan' },
+                { path: '/assessment-orders', label: '测评订单', icon: 'el-icon-s-order', perm: 'assessment:dan' },
                 { path: '/assessment-assign', label: '待分配规划师', icon: 'el-icon-user', perm: 'assessment:dan' },
               ]},
             // --- 虚拟团队 ---

+ 258 - 0
cfc-web/src/views/admin/AssessmentOrders.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="assessment-orders admin-page">
+    <el-card>
+      <div slot="header" class="admin-page-header">
+        <span class="admin-page-title">测评预约管理</span>
+        <div class="filter-bar">
+          <el-input
+            v-model="keyword"
+            placeholder="搜索用户/规划师..."
+            prefix-icon="el-icon-search"
+            clearable
+            style="width: 200px; margin-right: 10px;"
+            @keyup.enter.native="handleSearch"
+            @clear="handleSearch"
+          />
+          <el-select v-model="statusFilter" placeholder="预约状态" clearable @change="handleSearch" style="width: 140px; margin-right: 10px;">
+            <el-option label="全部状态" value="" />
+            <el-option label="待确认" :value="0" />
+            <el-option label="已确认" :value="1" />
+            <el-option label="已完成" :value="2" />
+            <el-option label="已取消" :value="3" />
+            <el-option label="未到" :value="4" />
+          </el-select>
+          <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
+        </div>
+      </div>
+
+      <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe empty-text="暂无测评预约">
+        <el-table-column label="用户" width="110">
+          <template slot-scope="{ row }">{{ row.userName || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="孩子" width="100">
+          <template slot-scope="{ row }">{{ row.childName || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="测评方案" min-width="140" show-overflow-tooltip>
+          <template slot-scope="{ row }">{{ row.packageName || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="当前规划师" width="120">
+          <template slot-scope="{ row }">
+            <span v-if="row.guideName">{{ row.guideName }}</span>
+            <el-tag v-else size="mini" type="info">未分配</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约日期" width="120">
+          <template slot-scope="{ row }">{{ row.appointmentDate || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="预约时间" width="100">
+          <template slot-scope="{ row }">{{ row.appointmentTime || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="预约类型" width="100">
+          <template slot-scope="{ row }">{{ row.appointmentType || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="状态" width="100">
+          <template slot-scope="{ row }">
+            <el-tag :type="statusType(row.status)" size="mini">{{ statusLabel(row.status) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" width="155">
+          <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
+        </el-table-column>
+        <el-table-column label="操作" width="130" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button
+              size="mini"
+              type="primary"
+              plain
+              @click="openChangeGuideDialog(row)"
+              :disabled="row.status === 3"
+            >修改规划师</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="page"
+        :page-sizes="[10, 20, 50]"
+        :page-size="size"
+        :total="total"
+        layout="total, sizes, prev, pager, next, jumper"
+        class="pagination-wrap"
+      />
+    </el-card>
+
+    <!-- 修改规划师 Dialog -->
+    <el-dialog title="修改规划师" :visible.sync="changeGuideDialogVisible" width="500px">
+      <div v-if="currentOrder" v-loading="guidesLoading">
+        <el-descriptions :column="1" border size="small" style="margin-bottom: 20px;">
+          <el-descriptions-item label="用户">{{ currentOrder.userName }}</el-descriptions-item>
+          <el-descriptions-item label="孩子">{{ currentOrder.childName }}</el-descriptions-item>
+          <el-descriptions-item label="测评方案">{{ currentOrder.packageName || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="预约日期">{{ currentOrder.appointmentDate || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="当前规划师">
+            <span v-if="currentOrder.guideName">{{ currentOrder.guideName }}</span>
+            <span v-else style="color: #999;">未分配</span>
+          </el-descriptions-item>
+        </el-descriptions>
+
+        <el-form label-width="100px">
+          <el-form-item label="新规划师">
+            <el-select
+              v-model="selectedGuideId"
+              placeholder="请选择规划师"
+              style="width: 100%;"
+              filterable
+            >
+              <el-option
+                v-for="g in guides"
+                :key="g.id"
+                :label="g.nickname + (g.realName ? ' (' + g.realName + ')' : '') + (g.teacherNo ? ' - ' + g.teacherNo : '')"
+                :value="g.id"
+              />
+            </el-select>
+          </el-form-item>
+        </el-form>
+      </div>
+      <div slot="footer">
+        <el-button @click="changeGuideDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmChangeGuide" :loading="changing">确认修改</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getAssessmentOrderList, changeAssessmentGuide, getAvailableGuides } from '@/api/admin'
+
+export default {
+  name: 'AssessmentOrders',
+  computed: {
+    tableHeight() {
+      return window.innerHeight - 320
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      list: [],
+      page: 1,
+      size: 20,
+      total: 0,
+      keyword: '',
+      statusFilter: '',
+      // 修改规划师
+      changeGuideDialogVisible: false,
+      currentOrder: null,
+      selectedGuideId: null,
+      guides: [],
+      guidesLoading: false,
+      changing: false
+    }
+  },
+  mounted() {
+    this.loadList()
+  },
+  methods: {
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
+    async loadList() {
+      this.loading = true
+      try {
+        var params = { page: this.page, size: this.size }
+        if (this.statusFilter) {
+          params.status = this.statusFilter
+        }
+        if (this.keyword) {
+          params.keyword = this.keyword
+        }
+        var res = await getAssessmentOrderList(params)
+        this.list = res.data.records || []
+        this.total = res.data.total || 0
+      } catch (e) {
+        this.$message.error(e.message || '加载测评订单失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handleSizeChange(val) {
+      this.size = val
+      this.page = 1
+      this.loadList()
+    },
+    handleCurrentChange(val) {
+      this.page = val
+      this.loadList()
+    },
+    async openChangeGuideDialog(row) {
+      this.currentOrder = row
+      this.selectedGuideId = row.guideId || null
+      this.changeGuideDialogVisible = true
+
+      this.guidesLoading = true
+      try {
+        var res = await getAvailableGuides()
+        this.guides = res.data || []
+      } catch (e) {
+        this.$message.error(e.message || '加载规划师列表失败')
+      } finally {
+        this.guidesLoading = false
+      }
+    },
+    async confirmChangeGuide() {
+      if (!this.selectedGuideId) {
+        this.$message.warning('请选择规划师')
+        return
+      }
+      if (this.currentOrder.guideId === this.selectedGuideId) {
+        this.$message.warning('新规划师与当前规划师相同')
+        return
+      }
+      this.changing = true
+      try {
+        await changeAssessmentGuide(this.currentOrder.id, this.selectedGuideId)
+        this.$message.success('规划师修改成功')
+        this.changeGuideDialogVisible = false
+        this.loadList()
+      } catch (e) {
+        this.$message.error(e.message || '修改规划师失败')
+      } finally {
+        this.changing = false
+      }
+    },
+    statusType(status) {
+      var map = { 0: 'warning', 1: 'primary', 2: 'success', 3: 'info', 4: 'danger' }
+      return map[status] !== undefined ? map[status] : 'info'
+    },
+    statusLabel(status) {
+      var map = { 0: '待确认', 1: '已确认', 2: '已完成', 3: '已取消', 4: '未到' }
+      return map[status] !== undefined ? map[status] : (status !== null && status !== undefined ? status : '-')
+    },
+    formatTime(t) {
+      if (!t) return '-'
+      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
+    }
+  }
+}
+</script>
+
+<style scoped>
+.assessment-orders .admin-page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+.filter-bar {
+  display: flex;
+  align-items: center;
+}
+.pagination-wrap {
+  margin-top: 20px;
+  text-align: right;
+}
+</style>