Просмотр исходного кода

feat: 章鱼图录入成效时支持关联关键人(多选+角色标记)

Sisyphus Agent 1 неделя назад
Родитель
Сommit
62a4d54478

+ 15 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/KeyPersonController.java

@@ -17,6 +17,12 @@ public class KeyPersonController {
     private KeyPersonService keyPersonService;
 
     // ===== 1. 盘点项目 =====
+    /** 列出当前用户的全部关键人(供成效录入时选择关联) */
+    @PostMapping("/key-person/list")
+    public Result<List<Map<String, Object>>> listKeyPersons(@RequestAttribute("userId") Long userId) {
+        return keyPersonService.listKeyPersons(userId);
+    }
+
     @PostMapping("/effect/list")
     public Result<Map<String, Object>> listEffects(@RequestBody(required = false) Map<String, Object> params,
                                                    @RequestAttribute("userId") Long userId) {
@@ -37,7 +43,15 @@ public class KeyPersonController {
         BigDecimal effectAmount = params.get("effectAmount") != null
                 ? new BigDecimal(params.get("effectAmount").toString()) : BigDecimal.ZERO;
         String effectDesc = (String) params.get("effectDesc");
-        return keyPersonService.addEffect(userId, memberOrderId, effectType, effectAmount, effectDesc);
+        // 关键人列表(可选):[{keyPersonId, role}]
+        Object kpObj = params.get("keyPersons");
+        List<Map<String, Object>> keyPersons = null;
+        if (kpObj instanceof List) {
+            @SuppressWarnings("unchecked")
+            List<Map<String, Object>> list = (List<Map<String, Object>>) kpObj;
+            keyPersons = list;
+        }
+        return keyPersonService.addEffect(userId, memberOrderId, effectType, effectAmount, effectDesc, keyPersons);
     }
 
     @PostMapping("/effect/delete")

+ 53 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/KeyPersonService.java

@@ -134,8 +134,8 @@ public class KeyPersonService {
     }
 
     public Result<OctopusEffectRecord> addEffect(Long userId, Long memberOrderId, Integer effectType,
-                                                 BigDecimal effectAmount, String effectDesc) {
-        // memberOrderId 为可选关联(MemberSubscriptionOrder 为家庭维度表,无 userId 字段,仅记录关联不校验归属)
+                                                 BigDecimal effectAmount, String effectDesc,
+                                                 List<Map<String, Object>> keyPersons) {
         OctopusEffectRecord r = new OctopusEffectRecord();
         r.setUserId(userId);
         r.setMemberOrderId(memberOrderId);
@@ -145,6 +145,33 @@ public class KeyPersonService {
         r.setStatus(1);
         r.setCreatedAt(new Date());
         effectRecordMapper.insert(r);
+
+        // 保存关键人关联
+        if (keyPersons != null) {
+            for (Map<String, Object> kp : keyPersons) {
+                Long keyPersonId = kp.get("keyPersonId") != null
+                        ? Long.valueOf(kp.get("keyPersonId").toString()) : null;
+                Integer role = kp.get("role") != null
+                        ? Integer.valueOf(kp.get("role").toString()) : 3;
+                if (keyPersonId == null) continue;
+                // 校验关键人归属
+                OctopusKeyPerson kpEntity = keyPersonMapper.selectById(keyPersonId);
+                if (kpEntity == null || !kpEntity.getUserId().equals(userId)) continue;
+                // 避免重复关联
+                Long exists = ekpMapper.selectCount(
+                        new LambdaQueryWrapper<OctopusEffectKeyPerson>()
+                                .eq(OctopusEffectKeyPerson::getEffectId, r.getId())
+                                .eq(OctopusEffectKeyPerson::getKeyPersonId, keyPersonId)
+                );
+                if (exists != null && exists > 0) continue;
+                OctopusEffectKeyPerson rel = new OctopusEffectKeyPerson();
+                rel.setEffectId(r.getId());
+                rel.setKeyPersonId(keyPersonId);
+                rel.setRole(role);
+                rel.setCreatedAt(new Date());
+                ekpMapper.insert(rel);
+            }
+        }
         return Result.success(r);
     }
 
@@ -161,6 +188,30 @@ public class KeyPersonService {
 
     // ========== 2. 找关键人 ==========
 
+    /** 列出当前用户的全部关键人(供成效录入时选择关联) */
+    public Result<List<Map<String, Object>>> listKeyPersons(Long userId) {
+        List<OctopusKeyPerson> kps = keyPersonMapper.selectList(
+                new LambdaQueryWrapper<OctopusKeyPerson>()
+                        .eq(OctopusKeyPerson::getUserId, userId)
+                        .orderByDesc(OctopusKeyPerson::getCreatedAt)
+        );
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (OctopusKeyPerson kp : kps) {
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("id", kp.getId());
+            m.put("name", kp.getName());
+            m.put("organization", kp.getOrganization());
+            m.put("department", kp.getDepartment());
+            m.put("title", kp.getTitle());
+            m.put("firstMeetScene", kp.getFirstMeetScene());
+            m.put("knowWay", kp.getKnowWay());
+            m.put("contactInfo", kp.getContactInfo());
+            m.put("createdAt", kp.getCreatedAt() != null ? kp.getCreatedAt().toString() : "");
+            list.add(m);
+        }
+        return Result.success(list);
+    }
+
     public Result<OctopusKeyPerson> addKeyPerson(Long userId, String name, String organization,
                                                  String department, String title,
                                                  String firstMeetScene, String knowWay,

+ 126 - 4
cfc-frontend/pages/action-detail/octopus-add-effect.vue

@@ -4,18 +4,43 @@
       <text class="page-title">记录成效</text>
     </view>
 
-    <!-- 成金额 -->
+    <!-- 成金额 -->
     <view class="form-section">
       <text class="section-label">成交金额(元) <text class="required-mark">*</text></text>
       <input class="amount-input" v-model="effectAmount" type="digit" placeholder="如 2500.00" />
     </view>
 
-    <!-- 成描述 -->
+    <!-- 成交来源描述 -->
     <view class="form-section">
       <text class="section-label">成交来源描述(选填)</text>
       <textarea class="desc-input" v-model="effectDesc" placeholder="简述成交来源,如:家长转介绍、社区活动等" maxlength="200"></textarea>
     </view>
 
+    <!-- 关联关键人(可多选 + 角色标记) -->
+    <view class="form-section">
+      <view class="kp-header">
+        <text class="section-label">关联关键人(可多选)</text>
+        <text class="kp-add-link" @tap="goAddKeyPerson">+ 新建</text>
+      </view>
+      <view class="kp-list" v-if="keyPersons.length > 0">
+        <view class="kp-item" v-for="kp in keyPersons" :key="kp.id"
+              :class="{selected: selectedMap[kp.id]}"
+              @click="toggleKeyPerson(kp.id)">
+          <view class="kp-info">
+            <text class="kp-name">{{ kp.name }}</text>
+            <text class="kp-org">{{ kp.organization || kp.title || '' }}</text>
+          </view>
+          <!-- 角色选择(选中时显示) -->
+          <view class="kp-role" v-if="selectedMap[kp.id]" @click.stop>
+            <text class="role-tag" :class="{active: roleMap[kp.id] === 1}" @click="setRole(kp.id, 1)">引荐</text>
+            <text class="role-tag" :class="{active: roleMap[kp.id] === 2}" @click="setRole(kp.id, 2)">决策</text>
+            <text class="role-tag" :class="{active: roleMap[kp.id] === 3}" @click="setRole(kp.id, 3)">其他</text>
+          </view>
+        </view>
+      </view>
+      <text class="empty-hint" v-else>暂无关键人,点击「+ 新建」添加</text>
+    </view>
+
     <!-- 提交按钮 -->
     <view class="submit-bar">
       <button class="btn-submit" @click="onSubmit" :disabled="!canSubmit">保存成效</button>
@@ -24,13 +49,16 @@
 </template>
 
 <script>
-import { addOctopusEffect } from '../../utils/api.js'
+import { addOctopusEffect, getOctopusKeyPersons } from '../../utils/api.js'
 
 export default {
   data() {
     return {
       effectAmount: '',
-      effectDesc: ''
+      effectDesc: '',
+      keyPersons: [],
+      selectedMap: {},   // kpId -> true
+      roleMap: {}        // kpId -> role (1引荐/2决策/3其他)
     }
   },
   computed: {
@@ -38,13 +66,46 @@ export default {
       return this.effectAmount.trim()
     }
   },
+  onLoad() {
+    this.loadKeyPersons()
+  },
   methods: {
+    async loadKeyPersons() {
+      try {
+        var res = await getOctopusKeyPersons()
+        if (res.code === 200 && Array.isArray(res.data)) {
+          this.keyPersons = res.data
+        }
+      } catch (e) { /* 静默 */ }
+    },
+    goAddKeyPerson() {
+      uni.navigateTo({ url: '/pages/action-detail/octopus-add-key-person' })
+    },
+    toggleKeyPerson(id) {
+      if (this.selectedMap[id]) {
+        this.$set(this.selectedMap, id, false)
+        this.$delete(this.roleMap, id)
+      } else {
+        this.$set(this.selectedMap, id, true)
+        this.$set(this.roleMap, id, 1) // 默认引荐
+      }
+    },
+    setRole(id, role) {
+      this.$set(this.roleMap, id, role)
+    },
     async onSubmit() {
       if (!this.effectAmount.trim()) {
         uni.showToast({ title: '请填写成交金额', icon: 'none' })
         return
       }
       var data = { effectAmount: this.effectAmount, effectDesc: this.effectDesc.trim() }
+      // 组装关键人列表
+      var kpList = []
+      var ids = Object.keys(this.selectedMap).filter(function(k) { return this.selectedMap[k] }, this)
+      ids.forEach(function(k) {
+        kpList.push({ keyPersonId: Number(k), role: this.roleMap[k] || 3 })
+      }, this)
+      if (kpList.length > 0) data.keyPersons = kpList
       try {
         var res = await addOctopusEffect(data)
         if (res.code === 200) {
@@ -107,6 +168,67 @@ export default {
   font-size: 28rpx;
   box-sizing: border-box;
 }
+/* 关键人选择 */
+.kp-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.kp-header .section-label {
+  margin-bottom: 0;
+}
+.kp-add-link {
+  font-size: 26rpx;
+  color: #F97316;
+}
+.kp-list {
+  margin-top: 16rpx;
+}
+.kp-item {
+  padding: 16rpx 20rpx;
+  border: 2rpx solid #e0e0e0;
+  border-radius: 12rpx;
+  background: #fafafa;
+  margin-bottom: 12rpx;
+}
+.kp-item.selected {
+  border-color: #F97316;
+  background: #FFF7ED;
+}
+.kp-info {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.kp-name {
+  font-size: 28rpx;
+  color: #333;
+}
+.kp-org {
+  font-size: 22rpx;
+  color: #999;
+}
+.kp-role {
+  display: flex;
+  margin-top: 12rpx;
+  gap: 12rpx;
+}
+.role-tag {
+  font-size: 22rpx;
+  padding: 6rpx 20rpx;
+  border-radius: 8rpx;
+  border: 2rpx solid #e0e0e0;
+  color: #666;
+}
+.role-tag.active {
+  border-color: #F97316;
+  background: #F97316;
+  color: #fff;
+}
+.empty-hint {
+  font-size: 26rpx;
+  color: #999;
+}
 .submit-bar {
   margin-top: 40rpx;
 }

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

@@ -2292,6 +2292,7 @@ export const addOctopusEffect = (data) => request('/api/octopus/effect/add', 'PO
 export const deleteOctopusEffect = (data) => request('/api/octopus/effect/delete', 'POST', data)
 
 export const addOctopusKeyPerson = (data) => request('/api/octopus/key-person/add', 'POST', data)
+export const getOctopusKeyPersons = (data) => request('/api/octopus/key-person/list', 'POST', data)
 export const updateOctopusKeyPerson = (data) => request('/api/octopus/key-person/update', 'POST', data)
 export const deleteOctopusKeyPerson = (data) => request('/api/octopus/key-person/delete', 'POST', data)
 export const getOctopusEffectKeyPersons = (data) => request('/api/octopus/effect/key-persons', 'POST', data)