Prechádzať zdrojové kódy

Merge branch 'refs/heads/jiapu_aijiuyi'

jiapu 3 mesiacov pred
rodič
commit
0728a16932

+ 44 - 0
code/backend/src/main/java/com/aijiuyi/admin/service/impl/PlanServiceImpl.java

@@ -1,14 +1,17 @@
 package com.aijiuyi.admin.service.impl;
 
 import com.aijiuyi.admin.common.constant.ResultCode;
+import com.aijiuyi.admin.common.context.RequestContext;
 import com.aijiuyi.admin.common.exception.BusinessException;
 import com.aijiuyi.admin.common.util.LogUtil;
 import com.aijiuyi.admin.controller.dto.PlanQueryDTO;
 import com.aijiuyi.admin.controller.dto.PlanSaveDTO;
+import com.aijiuyi.admin.entity.AppUser;
 import com.aijiuyi.admin.entity.Plan;
 import com.aijiuyi.admin.entity.PlanStep;
 import com.aijiuyi.admin.mapper.PlanMapper;
 import com.aijiuyi.admin.mapper.PlanStepMapper;
+import com.aijiuyi.admin.service.AppUserService;
 import com.aijiuyi.admin.service.PlanService;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -35,6 +38,9 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
     @Autowired
     private PlanStepMapper planStepMapper;
 
+    @Autowired
+    private AppUserService appUserService;
+
     /**
      * 分页查询方案列表
      *
@@ -106,6 +112,7 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         String planCode = "S" + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
         Plan plan = new Plan();
         BeanUtils.copyProperties(dto, plan);
+        fillCurrentAuthor(plan);
         plan.setPlanCode(planCode);
         plan.setStatus(0); // 草稿
         plan.setUseCount(0);
@@ -136,6 +143,7 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
             throw new BusinessException(ResultCode.PLAN_NAME_EXISTS);
         }
         BeanUtils.copyProperties(dto, plan);
+        fillCurrentAuthor(plan);
         updateById(plan);
         // 删除旧步骤(逻辑删除)
         planStepMapper.delete(new LambdaQueryWrapper<PlanStep>().eq(PlanStep::getPlanId, dto.getId()));
@@ -146,6 +154,42 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
         LogUtil.info(PlanServiceImpl.class, "修改方案[{}]", dto.getId());
     }
 
+    /**
+     * 方案创作人固定为当前登录用户,避免前端传入其他用户覆盖。
+     */
+    private void fillCurrentAuthor(Plan plan) {
+        Long currentUserId = RequestContext.getUserId();
+        AppUser currentUser = currentUserId != null ? appUserService.getById(currentUserId) : null;
+        plan.setAuthorId(currentUserId);
+        plan.setAuthorName(resolveAuthorName(currentUser));
+        plan.setAuthorType(resolvePlanAuthorType(currentUser));
+    }
+
+    private String resolveAuthorName(AppUser currentUser) {
+        if (currentUser == null) {
+            String userName = RequestContext.getUserName();
+            return StringUtils.hasText(userName) ? userName : "ADMIN";
+        }
+        if (StringUtils.hasText(currentUser.getNickname())) {
+            return currentUser.getNickname();
+        }
+        if (StringUtils.hasText(currentUser.getUsername())) {
+            return currentUser.getUsername();
+        }
+        return "ADMIN";
+    }
+
+    private Integer resolvePlanAuthorType(AppUser currentUser) {
+        Integer userType = currentUser != null ? currentUser.getUserType() : RequestContext.getUserType();
+        if (Integer.valueOf(1).equals(userType)) {
+            return 2;
+        }
+        if (Integer.valueOf(2).equals(userType)) {
+            return 3;
+        }
+        return 1;
+    }
+
     /**
      * 删除方案(逻辑删除,仅草稿状态可删)
      *

+ 27 - 39
code/frontend/src/views/plan/index.vue

@@ -188,21 +188,8 @@
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="创作人" prop="authorId">
-              <el-select
-                v-model="formData.authorId"
-                placeholder="请选择创作人"
-                style="width:100%"
-                filterable
-                @change="onAuthorChange"
-              >
-                <el-option
-                  v-for="u in sysUserOptions"
-                  :key="u.id"
-                  :label="`${u.nickname || u.username} (${userTypeLabel(u.userType)})`"
-                  :value="u.id"
-                />
-              </el-select>
+            <el-form-item label="创作人" prop="authorName">
+              <el-input v-model="formData.authorName" disabled placeholder="当前登录用户" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -374,30 +361,28 @@ import {
 } from '@/api/plan'
 import { getAcupointList } from '@/api/acupoint'
 import { getTechniqueList } from '@/api/moxibustion'
-import { getAppUserList } from '@/api/auth'
+import { useUserStore } from '@/store/user'
 import { formatDateTime } from '@/utils/date'
 // ============ 枚举常量 ============
 const effectOptions = ['驱寒', '祛湿', '祛风', '化瘀', '活血', '化痰', '养颜', '扶阳']
 const symptomsOptions = ['怕冷', '感冒', '咳嗽', '痰多', '疼痛', '腰酸', '失眠', '疲劳', '皮肤暗沉', '手脚冰凉', '其他']
+const userStore = useUserStore()
 
 // ============ 选项列表(用于选择框) ============
 const acupointOptions = ref([])
 const techniqueOptions = ref([])
-const sysUserOptions = ref([])
 
 /**
- * 加载穴位、手法、系统用户选项列表
+ * 加载穴位、手法选项列表
  */
 async function loadOptions() {
   try {
-    const [acupointRes, techniqueRes, userRes] = await Promise.all([
+    const [acupointRes, techniqueRes] = await Promise.all([
       getAcupointList(),
-      getTechniqueList(),
-      getAppUserList()
+      getTechniqueList()
     ])
     acupointOptions.value = acupointRes.data || []
     techniqueOptions.value = techniqueRes.data || []
-    sysUserOptions.value = userRes.data || []
   } catch (e) {
     // 选项加载失败不影响主流程
   }
@@ -525,9 +510,9 @@ const defaultForm = () => ({
   applicableGender: 0,
   ageMin: 1,
   ageMax: 120,
-  authorId: null,
-  authorName: '',
-  authorType: 1,
+  authorId: getCurrentAuthorId(),
+  authorName: getCurrentAuthorName(),
+  authorType: getCurrentAuthorType(),
   description: '',
   steps: [defaultStep()]
 })
@@ -563,23 +548,23 @@ function onTechniqueChange(id, index) {
   formData.steps[index].techniqueName = found ? found.name : ''
 }
 
-/**
- * 应用用户类型标签文字
- */
-function userTypeLabel(val) {
-  return { 1: '普通用户', 2: '专家', 3: '管理员' }[val] || '未知'
+function getCurrentAuthorId() {
+  return userStore.userInfo?.userId || null
 }
 
-/**
- * 创作人选择变更:同步填充 authorName 和 authorType
- * AppUser.userType → Plan.authorType 映射:1(普通用户)→2,2(专家)→3,3(管理员)→1
- */
-function onAuthorChange(id) {
-  const found = sysUserOptions.value.find(u => u.id === id)
-  formData.authorName = found ? (found.nickname || found.username) : ''
-  // 角色映射
+function getCurrentAuthorName() {
+  return userStore.nickname || userStore.username || 'ADMIN'
+}
+
+function getCurrentAuthorType() {
   const typeMap = { 1: 2, 2: 3, 3: 1 }
-  formData.authorType = found ? (typeMap[found.userType] || 2) : 2
+  return typeMap[userStore.userInfo?.userType] || 1
+}
+
+function applyCurrentAuthor() {
+  formData.authorId = getCurrentAuthorId()
+  formData.authorName = getCurrentAuthorName()
+  formData.authorType = getCurrentAuthorType()
 }
 
 /**
@@ -603,6 +588,7 @@ function removeStep(index) {
 function handleAdd() {
   Object.assign(formData, defaultForm())
   formData.steps = [defaultStep()]
+  applyCurrentAuthor()
   dialogTitle.value = '新增方案'
   dialogMode.value = 'add'
   dialogVisible.value = true
@@ -619,6 +605,7 @@ async function handleEdit(row) {
     symptomsList: detail.symptoms ? detail.symptoms.split(',') : [],
     steps: detail.steps?.length > 0 ? detail.steps : [defaultStep()]
   })
+  applyCurrentAuthor()
   dialogTitle.value = '编辑方案'
   dialogMode.value = 'edit'
   dialogVisible.value = true
@@ -629,6 +616,7 @@ async function handleEdit(row) {
  */
 async function handleSubmit(publishFlag) {
   await formRef.value.validate()
+  applyCurrentAuthor()
   submitLoading.value = true
   try {
     const payload = {

+ 8 - 8
code/frontend/src/views/user/profile/index.vue

@@ -15,7 +15,7 @@
             <el-option label="女" :value="2" />
           </el-select>
         </el-form-item>
-        <el-form-item label="用户类">
+        <el-form-item label="用户类">
           <el-select v-model="queryForm.userType" placeholder="全部" clearable style="width:120px">
             <el-option
               v-for="item in userCategoryOptions"
@@ -129,7 +129,7 @@
           <template #default="{ row }">{{ row.age != null ? row.age + '岁' : '-' }}</template>
         </el-table-column>
         <el-table-column prop="phone" label="手机号" width="130" />
-        <el-table-column prop="userType" label="用户类" width="95" align="center">
+        <el-table-column prop="userType" label="用户类" width="95" align="center">
           <template #default="{ row }">
             <el-tag :type="userTypeTagType(row.userType)" size="small" v-if="row.userType">
               {{ userTypeText(row.userType) }}
@@ -231,9 +231,9 @@
               <el-input v-model="formData.phone" placeholder="请输入手机号" maxlength="11" :disabled="!!formData.id" />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="用户类" prop="userType">
-              <el-select v-model="formData.userType" placeholder="请选择用户类" style="width:100%">
+          <el-col v-if="formData.id" :span="12">
+            <el-form-item label="用户类" prop="userType">
+              <el-select v-model="formData.userType" placeholder="请选择用户类" style="width:100%">
                 <el-option
                   v-for="item in userCategoryOptions"
                   :key="item.code"
@@ -394,7 +394,7 @@ const exportHeaders = [
   { label: '性别', prop: 'genderText' },
   { label: '年龄', prop: 'age' },
   { label: '手机号', prop: 'phone' },
-  { label: '用户类', prop: 'userTypeLabel' },
+  { label: '用户类', prop: 'userTypeLabel' },
   { label: '所在地区', prop: 'regionText' },
   { label: '详细地址', prop: 'address' },
   { label: '设备编号', prop: 'deviceCodes' },
@@ -497,14 +497,14 @@ function onQueryCityChange(val) {
 /** 用户类别选项(从后台 user_category 动态加载) */
 const userCategoryOptions = ref([])
 
-/** 用户类文本(优先从动态加载的类别表查) */
+/** 用户类文本(优先从动态加载的类别表查) */
 function userTypeText(type) {
   if (type == null) return '-'
   const item = userCategoryOptions.value.find(c => c.code === type)
   return item ? item.name : '-'
 }
 
-/** 用户类标签颜色(优先使用后台配置的 tagType) */
+/** 用户类标签颜色(优先使用后台配置的 tagType) */
 function userTypeTagType(type) {
   if (type == null) return 'info'
   const item = userCategoryOptions.value.find(c => c.code === type)