Procházet zdrojové kódy

feat: 章鱼图成效类型改为系统自动计算(金额大/频次高/新成交)

Sisyphus Agent před 1 týdnem
rodič
revize
c495c6f0de

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

@@ -20,13 +20,11 @@ public class KeyPersonController {
     @PostMapping("/effect/list")
     public Result<Map<String, Object>> listEffects(@RequestBody(required = false) Map<String, Object> params,
                                                    @RequestAttribute("userId") Long userId) {
-        Integer effectType = params != null && params.get("effectType") != null
-                ? Integer.valueOf(params.get("effectType").toString()) : null;
         Integer page = params != null && params.get("page") != null
                 ? Integer.valueOf(params.get("page").toString()) : 1;
         Integer pageSize = params != null && params.get("pageSize") != null
                 ? Integer.valueOf(params.get("pageSize").toString()) : 20;
-        return keyPersonService.listEffects(userId, effectType, page, pageSize);
+        return keyPersonService.listEffects(userId, null, page, pageSize);
     }
 
     @PostMapping("/effect/add")

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/entity/OctopusEffectRecord.java

@@ -20,7 +20,7 @@ public class OctopusEffectRecord implements Serializable {
 
     private Long memberOrderId;
 
-    private Integer effectType; // bitmask
+    private Integer effectType; // bitmask(自动计算,非用户填写)
 
     private BigDecimal effectAmount;
 
@@ -30,6 +30,6 @@ public class OctopusEffectRecord implements Serializable {
 
     private Date createdAt;
 
-    // 非持久化:解析后的类型标签列表
+    // 非持久化:系统自动计算的标签列表(查询时动态计算)
     private transient java.util.List<String> effectTypeList;
 }

+ 77 - 13
cfc-backend/src/main/java/com/etotem/cfc/service/KeyPersonService.java

@@ -32,13 +32,14 @@ public class KeyPersonService {
         Page<OctopusEffectRecord> p = new Page<>(page, pageSize);
         LambdaQueryWrapper<OctopusEffectRecord> q = new LambdaQueryWrapper<OctopusEffectRecord>()
                 .eq(OctopusEffectRecord::getUserId, userId);
-        if (effectType != null && effectType > 0) {
-            q.apply("effect_type & {0}", effectType);
-        }
         q.orderByDesc(OctopusEffectRecord::getCreatedAt);
         effectRecordMapper.selectPage(p, q);
 
-        List<Map<String, Object>> list = p.getRecords().stream().map(this::toEffectVO).collect(Collectors.toList());
+        // 自动计算标签
+        Map<Long, List<String>> typeMap = computeEffectTypes(p.getRecords());
+        List<Map<String, Object>> list = p.getRecords().stream()
+                .map(r -> toEffectVO(r, typeMap.getOrDefault(r.getId(), new ArrayList<>())))
+                .collect(Collectors.toList());
         Map<String, Object> data = new LinkedHashMap<>();
         data.put("list", list);
         data.put("total", p.getTotal());
@@ -47,12 +48,12 @@ public class KeyPersonService {
         return Result.success(data);
     }
 
-    private Map<String, Object> toEffectVO(OctopusEffectRecord r) {
+    private Map<String, Object> toEffectVO(OctopusEffectRecord r, List<String> computedTypes) {
         Map<String, Object> m = new LinkedHashMap<>();
         m.put("id", r.getId());
         m.put("memberOrderId", r.getMemberOrderId());
         m.put("effectType", r.getEffectType());
-        m.put("effectTypeList", parseEffectType(r.getEffectType()));
+        m.put("effectTypeList", computedTypes);
         m.put("effectAmount", r.getEffectAmount());
         m.put("effectDesc", r.getEffectDesc());
         m.put("status", r.getStatus());
@@ -60,13 +61,76 @@ public class KeyPersonService {
         return m;
     }
 
-    private List<String> parseEffectType(Integer type) {
-        List<String> list = new ArrayList<>();
-        if (type == null) return list;
-        if ((type & 1) != 0) list.add("金额大");
-        if ((type & 2) != 0) list.add("频次高");
-        if ((type & 4) != 0) list.add("新成交");
-        return list;
+    /**
+     * 自动计算成效类型标签:
+     * - 金额大:金额 Top 3
+     * - 频次高:与某关键人的成交次数最多
+     * - 新成交:时间最近的1条
+     */
+    private Map<Long, List<String>> computeEffectTypes(List<OctopusEffectRecord> records) {
+        Map<Long, List<String>> typeMap = new HashMap<>();
+        if (records.isEmpty()) return typeMap;
+
+        // 初始化所有记录为空列表
+        for (OctopusEffectRecord r : records) {
+            typeMap.put(r.getId(), new ArrayList<>());
+        }
+
+        // 1. 金额大:按金额降序取 Top 3
+        List<OctopusEffectRecord> sortedByAmount = new ArrayList<>(records);
+        sortedByAmount.sort((a, b) -> {
+            BigDecimal amtA = a.getEffectAmount() != null ? a.getEffectAmount() : BigDecimal.ZERO;
+            BigDecimal amtB = b.getEffectAmount() != null ? b.getEffectAmount() : BigDecimal.ZERO;
+            return amtB.compareTo(amtA);
+        });
+        for (int i = 0; i < Math.min(3, sortedByAmount.size()); i++) {
+            typeMap.get(sortedByAmount.get(i).getId()).add("金额大");
+        }
+
+        // 2. 频次高:统计每个关键人的成交次数,取最多的
+        Map<Long, Long> personCount = new HashMap<>();
+        for (OctopusEffectRecord r : records) {
+            List<OctopusEffectKeyPerson> rels = ekpMapper.selectList(
+                    new LambdaQueryWrapper<OctopusEffectKeyPerson>().eq(OctopusEffectKeyPerson::getEffectId, r.getId())
+            );
+            for (OctopusEffectKeyPerson rel : rels) {
+                personCount.put(rel.getKeyPersonId(), personCount.getOrDefault(rel.getKeyPersonId(), 0L) + 1);
+            }
+        }
+        if (!personCount.isEmpty()) {
+            Long maxCount = personCount.values().stream().max(Long::compareTo).orElse(0L);
+            if (maxCount >= 2) { // 只有频次>=2才标记
+                for (Map.Entry<Long, Long> entry : personCount.entrySet()) {
+                    if (entry.getValue() == maxCount) {
+                        // 找到该关键人的所有成效记录
+                        for (OctopusEffectRecord r : records) {
+                            List<OctopusEffectKeyPerson> rels = ekpMapper.selectList(
+                                    new LambdaQueryWrapper<OctopusEffectKeyPerson>()
+                                            .eq(OctopusEffectKeyPerson::getEffectId, r.getId())
+                                            .eq(OctopusEffectKeyPerson::getKeyPersonId, entry.getKey())
+                            );
+                            if (!rels.isEmpty()) {
+                                typeMap.get(r.getId()).add("频次高");
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // 3. 新成交:时间最近的1条
+        OctopusEffectRecord newest = records.stream()
+                .max((a, b) -> {
+                    if (a.getCreatedAt() == null) return -1;
+                    if (b.getCreatedAt() == null) return 1;
+                    return a.getCreatedAt().compareTo(b.getCreatedAt());
+                })
+                .orElse(null);
+        if (newest != null) {
+            typeMap.get(newest.getId()).add("新成交");
+        }
+
+        return typeMap;
     }
 
     public Result<OctopusEffectRecord> addEffect(Long userId, Long memberOrderId, Integer effectType,

+ 7 - 30
cfc-frontend/pages/action-detail/octopus-add-effect.vue

@@ -4,29 +4,16 @@
       <text class="page-title">记录成效</text>
     </view>
 
-    <!-- 成效类型(多选) -->
-    <view class="form-section">
-      <text class="section-label">成效类型 <text class="required-mark">*</text></text>
-      <view class="type-grid">
-        <view class="type-item" v-for="(name, key) in effectTypes" :key="key"
-              :class="{selected: effectTypeList.includes(Number(key))}"
-              @click="toggleType(Number(key))">
-          <text class="type-icon">{{ typeIcons[key] }}</text>
-          <text class="type-name">{{ name }}</text>
-        </view>
-      </view>
-    </view>
-
     <!-- 成效金额 -->
     <view class="form-section">
-      <text class="section-label">成效金额(元)</text>
+      <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>
+      <text class="section-label">成交来源描述(选填)</text>
+      <textarea class="desc-input" v-model="effectDesc" placeholder="简述成交来源,如:家长转介绍、社区活动等" maxlength="200"></textarea>
     </view>
 
     <!-- 提交按钮 -->
@@ -42,32 +29,22 @@ import { addOctopusEffect } from '../../utils/api.js'
 export default {
   data() {
     return {
-      effectTypeList: [],
-      effectTypes: {1: '金额大', 2: '频次高', 4: '新成交'},
-      typeIcons: {1: '💰', 2: '🔁', 4: '✨'},
       effectAmount: '',
       effectDesc: ''
     }
   },
   computed: {
     canSubmit() {
-      return this.effectTypeList.length > 0 && this.effectAmount.trim()
+      return this.effectAmount.trim()
     }
   },
   methods: {
-    toggleType(val) {
-      var idx = this.effectTypeList.indexOf(val)
-      if (idx > -1) this.effectTypeList.splice(idx, 1)
-      else this.effectTypeList.push(val)
-    },
     async onSubmit() {
-      if (!this.canSubmit) {
-        uni.showToast({ title: '请选择类型并填写金额', icon: 'none' })
+      if (!this.effectAmount.trim()) {
+        uni.showToast({ title: '请填写成交金额', icon: 'none' })
         return
       }
-      var type = 0
-      this.effectTypeList.forEach(v => { type |= v })
-      var data = { effectType: type, effectAmount: this.effectAmount, effectDesc: this.effectDesc.trim() }
+      var data = { effectAmount: this.effectAmount, effectDesc: this.effectDesc.trim() }
       try {
         var res = await addOctopusEffect(data)
         if (res.code === 200) {