Преглед на файлове

chore: auto bump version and changelog [skip ci]

iwt преди 1 месец
родител
ревизия
d54d990c98

+ 74 - 0
cfc-backend/src/main/java/com/etotem/cfc/util/SortUtil.java

@@ -0,0 +1,74 @@
+package com.etotem.cfc.util;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通用排序工具:将前端 sort 参数注入 MyBatis-Plus 查询包装器。
+ * 支持 LambdaQueryWrapper 和 QueryWrapper。
+ */
+public class SortUtil {
+
+    /**
+     * 对 QueryWrapper 应用排序。如果 sortSpecs 为空则不修改排序(保留原有排序)。
+     *
+     * @param wrapper    QueryWrapper 实例
+     * @param sortSpecs  前端传入的排序数组,每项 {column, order}
+     * @param snakeMap   驼峰属性名 → 数据库列名 映射(可为 null)
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> void applySort(QueryWrapper<T> wrapper,
+                                     List<Map<String, String>> sortSpecs,
+                                     Map<String, String> snakeMap) {
+        if (sortSpecs == null || sortSpecs.isEmpty() || wrapper == null) return;
+        StringBuilder sb = new StringBuilder(" order by ");
+        for (int i = 0; i < sortSpecs.size(); i++) {
+            if (i > 0) sb.append(", ");
+            Map<String, String> spec = sortSpecs.get(i);
+            String col = spec.get("column");
+            if (!StringUtils.hasText(col)) continue;
+            boolean asc = !"desc".equalsIgnoreCase(spec.get("order"));
+            String dbCol = resolveDbColumn(col, snakeMap);
+            sb.append(dbCol).append(asc ? " asc" : " desc");
+        }
+        if (sb.length() > 11) {
+            wrapper.last(sb.toString());
+        }
+    }
+
+    /**
+     * 对 LambdaQueryWrapper 应用排序(通过反射设置 last SQL)。
+     * 因为 LambdaQueryWrapper 没有通用的 orderBy 重载接受 db 列名字符串,
+     * 所以直接通过 last() 追加。
+     */
+    public static <T> void applySort(LambdaQueryWrapper<T> wrapper,
+                                     List<Map<String, String>> sortSpecs,
+                                     Map<String, String> snakeMap) {
+        if (sortSpecs == null || sortSpecs.isEmpty() || wrapper == null) return;
+        StringBuilder sb = new StringBuilder(" order by ");
+        for (int i = 0; i < sortSpecs.size(); i++) {
+            if (i > 0) sb.append(", ");
+            Map<String, String> spec = sortSpecs.get(i);
+            String col = spec.get("column");
+            if (!StringUtils.hasText(col)) continue;
+            boolean asc = !"desc".equalsIgnoreCase(spec.get("order"));
+            String dbCol = resolveDbColumn(col, snakeMap);
+            sb.append(dbCol).append(asc ? " asc" : " desc");
+        }
+        if (sb.length() > 11) {
+            wrapper.last(sb.toString());
+        }
+    }
+
+    private static String resolveDbColumn(String camelCol, Map<String, String> snakeMap) {
+        if (snakeMap != null && snakeMap.containsKey(camelCol)) {
+            return snakeMap.get(camelCol);
+        }
+        // 默认:驼峰转下划线
+        return camelCol.replaceAll("([A-Z])", "_$1").toLowerCase();
+    }
+}

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-e81af27c634e873089e05044f89437df82d1f86c
+17baef104dd5dc53760266c122b3bfd17cc0ccec

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.967",
+  "version": "1.0.968",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.967",
+      "version": "1.0.968",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.968",
+  "version": "1.0.969",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 6 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,12 @@
 
 ---
 
+## v1.0.969 (2026-08-11)
+
+### Bug 修复
+- 我的页面isLoggedIn改为data+onShow赋值,修复登录后仍显示登录前视图
+
+
 ## v1.0.968 (2026-08-11)
 
 ### Bug 修复

+ 7 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.968
+> 当前版本: v1.0.969
 
 ## 历史版本
 
@@ -8,6 +8,12 @@
 
 ---
 
+## v1.0.969 (2026-08-11)
+
+### Bug 修复
+- 我的页面isLoggedIn改为data+onShow赋值,修复登录后仍显示登录前视图
+
+
 ## v1.0.968 (2026-08-11)
 
 ### Bug 修复

+ 19 - 0
cfc-web/src/assets/css/sort.css

@@ -0,0 +1,19 @@
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+  white-space: nowrap;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-asc, .sort-desc {
+  color: #409eff !important;
+  font-weight: 600;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}

+ 1 - 0
cfc-web/src/main.js

@@ -15,6 +15,7 @@ import {
 } from 'element-ui'
 import 'element-ui/lib/theme-chalk/index.css'
 import './styles/admin-tokens.css'
+import './assets/css/sort.css'
 import { formatPrice, formatPriceWithSymbol, formatRate, formatScore } from './utils/format'
 
 Vue.use(Button)

+ 92 - 0
cfc-web/src/mixins/SortMixin.js

@@ -0,0 +1,92 @@
+/**
+ * 通用多列排序 Mixin
+ * 用法:在页面 data() 中声明 sortableColumns,在 loadList() 中加入 sortParams,
+ *       在 el-table-column 的 slot="header" 中调用 this.sortHeader(col)。
+ */
+export default {
+  data() {
+    return {
+      sortState: [],          // [{column, order:'asc'|'desc'}]
+    }
+  },
+  computed: {
+    sortLabel() {
+      if (!this.sortState || this.sortState.length === 0) return ''
+      const labels = this.sortableColumns || {}
+      return this.sortState.map(s => {
+        const label = labels[s.column] || s.column
+        return (s.order === 'asc' ? label + '↑' : label + '↓')
+      }).join(' > ')
+    }
+  },
+  methods: {
+    /**
+     * 渲染排序列头 HTML 字符串(供 slot 内联使用)
+     * 用法: <span v-html="sortHeader('name')"></span>
+     */
+    sortHeader(col, label) {
+      const icon = this.sortIcon(col)
+      const cls = this.sortClass(col)
+      const safeCol = col.replace(/[."']/g, '')
+      return `<span class="sort-header ${cls}" `
+        + `onclick="window.__sortClick && window.__sortClick('${safeCol}', event)" `
+        + `ondblclick="window.__sortToggle && window.__sortToggle('${safeCol}')" `
+        + `>${label || col}${icon}</span>`
+    },
+    sortClass(col) {
+      const s = this.sortState.find(x => x.column === col)
+      if (!s) return ''
+      return s.order === 'asc' ? 'sort-asc' : 'sort-desc'
+    },
+    sortIcon(col) {
+      const s = this.sortState.find(x => x.column === col)
+      if (!s) return ''
+      return s.order === 'asc' ? ' ↑' : ' ↓'
+    },
+    onSortClick(col, event) {
+      if (event.shiftKey) {
+        this.shiftAppendSort(col)
+      } else {
+        this.setPrimarySort(col)
+      }
+      if (typeof this.loadList === 'function') this.loadList()
+    },
+    onSortToggle(col) {
+      const idx = this.sortState.findIndex(s => s.column === col)
+      if (idx >= 0) {
+        this.sortState[idx].order = this.sortState[idx].order === 'asc' ? 'desc' : 'asc'
+      } else {
+        this.sortState = [{ column: col, order: 'asc' }]
+      }
+      if (typeof this.loadList === 'function') this.loadList()
+    },
+    setPrimarySort(col) {
+      const idx = this.sortState.findIndex(s => s.column === col)
+      if (idx >= 0) {
+        const item = this.sortState.splice(idx, 1)[0]
+        this.sortState.unshift(item)
+      } else {
+        this.sortState = [{ column: col, order: 'asc' }]
+      }
+    },
+    shiftAppendSort(col) {
+      const idx = this.sortState.findIndex(s => s.column === col)
+      if (idx >= 0) {
+        this.sortState[idx].order = this.sortState[idx].order === 'asc' ? 'desc' : 'asc'
+      } else if (this.sortState.length < 3) {
+        this.sortState.push({ column: col, order: 'asc' })
+      }
+    },
+    clearSort() {
+      this.sortState = []
+      if (typeof this.loadList === 'function') this.loadList()
+    },
+    /** 将当前 sortState 注入到请求参数中 */
+    applySortToParams(params) {
+      if (this.sortState.length > 0) {
+        params.sort = this.sortState
+      }
+      return params
+    }
+  }
+}