Explorar o código

分配规划师

jiapu hai 2 meses
pai
achega
94b730ecae

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

@@ -63,6 +63,9 @@ public class AdminController {
     @Resource
     private MemberUpgradeRecordMapper memberUpgradeRecordMapper;
 
+    @Resource
+    private AssessmentAppointmentMapper assessmentAppointmentMapper;
+
     // 用户管理
     @PostMapping("/users")
     public Result<Page<User>> getUsers(@RequestBody Map<String, Object> params) {
@@ -978,4 +981,100 @@ public class AdminController {
                         .orderByDesc(MemberUpgradeRecord::getCreatedAt));
         return Result.success(result);
     }
+
+    // ========== 测评待分配规划师 ==========
+
+    @PostMapping("/assessment/pending-list")
+    public Result<Map<String, Object>> getPendingAssessmentList(@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() : 10;
+        Page<AssessmentAppointment> pageParam = new Page<>(page, size);
+
+        Page<AssessmentAppointment> result = assessmentAppointmentMapper.selectPage(pageParam,
+                new LambdaQueryWrapper<AssessmentAppointment>()
+                        .isNull(AssessmentAppointment::getGuideId)
+                        .orderByDesc(AssessmentAppointment::getCreatedAt));
+
+        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("childId", app.getChildId());
+            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("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.getName() : "未知");
+            } else {
+                item.put("childName", "");
+            }
+
+            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/available-guides")
+    public Result<java.util.List<Map<String, Object>>> getAvailableGuides() {
+        java.util.List<User> teachers = userMapper.selectList(
+                new LambdaQueryWrapper<User>()
+                        .eq(User::getRole, "teacher")
+                        .eq(User::getTeacherStatus, "approved")
+                        .select(User::getId, User::getNickname, User::getRealName, User::getPhone, User::getTeacherNo));
+        java.util.List<Map<String, Object>> list = new java.util.ArrayList<>();
+        for (User t : teachers) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("id", t.getId());
+            item.put("nickname", t.getNickname());
+            item.put("realName", t.getRealName());
+            item.put("phone", t.getPhone());
+            item.put("teacherNo", t.getTeacherNo());
+            list.add(item);
+        }
+        return Result.success(list);
+    }
+
+    @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;
+        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);
+        appointment.setStatus(1); // 已确认
+        assessmentAppointmentMapper.updateById(appointment);
+        return Result.success(null);
+    }
 }

+ 25 - 0
cfc-web/src/api/admin.js

@@ -1595,3 +1595,28 @@ export function getFamilyEarningsRecords(familyId, type) {
     data: { familyId: familyId, type: type || '' }
   })
 }
+
+// ===== 测评待分配规划师 =====
+export function getPendingAssessmentList(params) {
+  return request({
+    url: '/api/admin/assessment/pending-list',
+    method: 'post',
+    data: params || {}
+  })
+}
+
+export function getAvailableGuides() {
+  return request({
+    url: '/api/admin/assessment/available-guides',
+    method: 'post',
+    data: {}
+  })
+}
+
+export function assignGuide(appointmentId, guideId) {
+  return request({
+    url: '/api/admin/assessment/assign-guide',
+    method: 'post',
+    data: { appointmentId, guideId }
+  })
+}

+ 6 - 0
cfc-web/src/router/index.js

@@ -186,6 +186,12 @@ const routes = [
         component: () => import('@/views/admin/Assessment.vue'),
         meta: { title: '测评管理', perm: 'assessment:dan' }
       },
+      {
+        path: 'assessment-assign',
+        name: 'AssessmentAssign',
+        component: () => import('@/views/admin/AssessmentAssign.vue'),
+        meta: { title: '待分配规划师', perm: 'assessment:dan' }
+      },
       {
         path: 'product-manage',
         name: 'ProductManage',

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

@@ -295,6 +295,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-assign', label: '待分配规划师', icon: 'el-icon-user', perm: 'assessment:dan' },
               ]},
             // --- 虚拟团队 ---
             { title: '虚拟团队', icon: 'el-icon-s-custom', perm: 'system:config',

+ 182 - 0
cfc-web/src/views/admin/AssessmentAssign.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="assessment-assign admin-page">
+    <el-card>
+      <div slot="header" class="admin-page-header">
+        <span class="admin-page-title">测评待分配规划师</span>
+        <el-button type="primary" size="small" icon="el-icon-refresh" @click="loadList">刷新</el-button>
+      </div>
+
+      <div style="overflow:auto;max-height:calc(100vh - 300px);">
+        <el-table style="max-height:calc(100vh - 300px);" :data="list" v-loading="loading" border stripe empty-text="暂无可分配记录">
+          <el-table-column prop="id" label="ID" width="70" />
+          <el-table-column label="用户" width="120">
+            <template slot-scope="{ row }">{{ row.userName }}</template>
+          </el-table-column>
+          <el-table-column label="孩子" width="120">
+            <template slot-scope="{ row }">{{ row.childName }}</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="90">
+            <template slot-scope="{ row }">{{ row.appointmentTime || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="类型" width="80">
+            <template slot-scope="{ row }">
+              <el-tag size="mini">{{ row.appointmentType === 'initial' ? '初测' : row.appointmentType === 'retest' ? '复测' : row.appointmentType || '-' }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" min-width="160" show-overflow-tooltip>
+            <template slot-scope="{ row }">{{ row.notes || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="创建时间" width="160">
+            <template slot-scope="{ row }">{{ row.createdAt }}</template>
+          </el-table-column>
+          <el-table-column label="操作" width="220" fixed="right">
+            <template slot-scope="{ row }">
+              <el-button size="mini" type="primary" @click="openAssignDialog(row)">分配规划师</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <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>
+
+    <el-dialog title="分配规划师" :visible.sync="assignDialogVisible" width="480px">
+      <div v-loading="guidesLoading">
+        <el-form label-width="100px">
+          <el-form-item label="预约ID">
+            <el-input :value="currentAppointmentId" disabled />
+          </el-form-item>
+          <el-form-item label="用户">
+            <el-input :value="currentUserName" disabled />
+          </el-form-item>
+          <el-form-item label="孩子">
+            <el-input :value="currentChildName" disabled />
+          </el-form-item>
+          <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="assignDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="confirmAssign" :loading="assigning">确认分配</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getPendingAssessmentList, getAvailableGuides, assignGuide } from '@/api/admin'
+
+export default {
+  name: 'AssessmentAssign',
+  data() {
+    return {
+      loading: false,
+      list: [],
+      page: 1,
+      size: 10,
+      total: 0,
+      assignDialogVisible: false,
+      guidesLoading: false,
+      assigning: false,
+      guides: [],
+      currentAppointmentId: null,
+      currentUserName: '',
+      currentChildName: '',
+      selectedGuideId: null
+    }
+  },
+  mounted() {
+    this.loadList()
+  },
+  methods: {
+    async loadList() {
+      this.loading = true
+      try {
+        const res = await getPendingAssessmentList({ page: this.page, size: this.size })
+        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 openAssignDialog(row) {
+      this.currentAppointmentId = row.id
+      this.currentUserName = row.userName
+      this.currentChildName = row.childName
+      this.selectedGuideId = null
+      this.assignDialogVisible = true
+
+      this.guidesLoading = true
+      try {
+        const res = await getAvailableGuides()
+        this.guides = res.data || []
+      } catch (e) {
+        this.$message.error(e.message || '加载规划师列表失败')
+      } finally {
+        this.guidesLoading = false
+      }
+    },
+    async confirmAssign() {
+      if (!this.selectedGuideId) {
+        this.$message.warning('请选择规划师')
+        return
+      }
+      this.assigning = true
+      try {
+        await assignGuide(this.currentAppointmentId, this.selectedGuideId)
+        this.$message.success('分配成功')
+        this.assignDialogVisible = false
+        this.loadList()
+      } catch (e) {
+        this.$message.error(e.message || '分配失败')
+      } finally {
+        this.assigning = false
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.assessment-assign .admin-page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.pagination-wrap {
+  margin-top: 20px;
+  text-align: right;
+}
+</style>