瀏覽代碼

update:书签分类列表改造v1

zhiqiang.lv 2 月之前
父節點
當前提交
54f2efda37

+ 92 - 0
src/main/java/top/lvzhiqiang/controller/BookmarkInfoController.java

@@ -0,0 +1,92 @@
+package top.lvzhiqiang.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import top.lvzhiqiang.entity.BookmarkCategory;
+import top.lvzhiqiang.entity.BookmarkInfo;
+import top.lvzhiqiang.mapper.BookmarkMapper;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 书签信息Controller
+ *
+ * @author lvzhiqiang
+ * 2025/9/24 15:45
+ */
+@RestController
+@RequestMapping("/bookmarkInfo")
+public class BookmarkInfoController {
+
+    @Resource
+    private BookmarkMapper bookmarkMapper;
+
+    /**
+     * 获取完整分类树
+     *
+     * @return
+     */
+    @GetMapping("/categories/tree")
+    public List<BookmarkCategory> getCategoryTree() {
+        List<BookmarkCategory> allCategories = bookmarkMapper.selectAllCategories();
+        return buildCategoryTree(null, allCategories);
+    }
+
+    /**
+     * 递归构建分类树
+     *
+     * @param parentId
+     * @param allCategories
+     * @return
+     */
+    private List<BookmarkCategory> buildCategoryTree(Long parentId, List<BookmarkCategory> allCategories) {
+        return allCategories.stream()
+                .filter(category -> {
+                    if (parentId == null) {
+                        return category.getParentId() == null;
+                    } else {
+                        return parentId.equals(category.getParentId());
+                    }
+                })
+                .map(category -> {
+                    // 设置子分类
+                    category.setChildren(buildCategoryTree(category.getId(), allCategories));
+                    // 设置完整路径
+                    if (category.getPath() != null) {
+                        List<String> pathNames = bookmarkMapper.selectCategoryPath(category.getPath());
+                        category.setFullPath(String.join(" > ", pathNames));
+                    }
+                    return category;
+                })
+                .sorted(Comparator.comparing(BookmarkCategory::getSort))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 根据父级ID获取子分类(用于级联下拉框)
+     *
+     * @param parentId
+     * @return
+     */
+    @GetMapping("/categories")
+    public List<JSONObject> getCategories(@RequestParam(required = false) Long parentId) {
+        return bookmarkMapper.selectCategoriesByParentId(parentId);
+    }
+
+    /**
+     * 获取分类下的书签
+     *
+     * @param categoryId
+     * @return
+     */
+    @GetMapping("/bookmarks")
+    public List<BookmarkInfo> getBookmarks(@RequestParam Long categoryId) {
+        return bookmarkMapper.selectBookmarksByCategoryId(categoryId);
+    }
+}

+ 30 - 0
src/main/java/top/lvzhiqiang/entity/BookmarkCategory.java

@@ -0,0 +1,30 @@
+package top.lvzhiqiang.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 书签分类表实体
+ *
+ * @author lvzhiqiang
+ * 2024/10/21 10:43
+ */
+@Data
+public class BookmarkCategory implements Serializable {
+
+    private Long id;
+    private String name;
+    private Long parentId;
+    private Integer sort;
+    private Integer level;
+    private String path;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+
+    // 前端需要的字段
+    private List<BookmarkCategory> children;
+    private String fullPath;
+}

+ 2 - 4
src/main/java/top/lvzhiqiang/entity/BookmarkInfo.java

@@ -39,8 +39,6 @@ public class BookmarkInfo implements Serializable {
      * 书签分类表ID
      */
     private Integer categoryId;
-    private String categoryName;
-    private String subCategoryName;
 
     /**
      * 如果需要实现多用户管理,可以加入这个外键,指向用户表的id(可选)
@@ -77,7 +75,7 @@ public class BookmarkInfo implements Serializable {
     private LocalDateTime updatedAt;
 
     /**
-     * 删除标志(1:正常,2:已删除)
+     * 关联字段
      */
-    private Integer deleteFlag;
+    private String categoryName;
 }

+ 122 - 0
src/main/java/top/lvzhiqiang/mapper/BookmarkMapper.java

@@ -0,0 +1,122 @@
+package top.lvzhiqiang.mapper;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import top.lvzhiqiang.entity.BookmarkCategory;
+import top.lvzhiqiang.entity.BookmarkInfo;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 书签mapper
+ *
+ * @author lvzhiqiang
+ * 2025/9/24 15:45
+ */
+public interface BookmarkMapper {
+
+    /**
+     * 获取所有分类(用于构建树形结构)
+     *
+     * @return
+     */
+    @Select("SELECT id, name, parent_id, sort, level, path FROM bookmark_category WHERE delete_flag = 1 ORDER BY sort")
+    List<BookmarkCategory> selectAllCategories();
+
+    /**
+     * 根据父级ID获取子分类
+     *
+     * @param parentId
+     * @return
+     */
+    @Select({"<script>" +
+            "select id, name, parent_id, sort, level, path from bookmark_category where delete_flag = 1" +
+            "<choose>" +
+            "                <when test=\"parentId != null and parentId != ''\"> " +
+            "                    and parent_id = #{parentId}" +
+            "                </when>" +
+            "                <when test=\"parentId == null\">" +
+            "                    and parent_id is null" +
+            "                </when>" +
+            "                <otherwise></otherwise>" +
+            "</choose>" +
+            " order by sort" +
+            "</script>"})
+    List<JSONObject> selectCategoriesByParentId(Long parentId);
+
+    /**
+     * 根据分类ID获取书签(单个分类)
+     *
+     * @param categoryId
+     * @return
+     */
+    @Select("SELECT bi.*, bc.name as category_name FROM bookmark_info bi LEFT JOIN bookmark_category bc ON bi.category_id = bc.id WHERE bi.category_id = #{categoryId} AND bi.delete_flag = 1 ORDER BY bi.created_at DESC")
+    List<BookmarkInfo> selectBookmarksByCategoryId(Long categoryId);
+
+    /**
+     * 获取分类及其所有子分类的ID列表
+     *
+     * @param categoryId
+     * @param path
+     * @return
+     */
+    @Select("SELECT id FROM bookmark_category WHERE delete_flag = 1  and (path LIKE CONCAT(#{path}, ',%') OR id = #{categoryId})")
+    List<Long> selectCategoryAndChildrenIds(@Param("categoryId") Long categoryId, @Param("path") String path);
+
+    /**
+     * 获取所有书签(无分类筛选)
+     *
+     * @return
+     */
+    @Select("SELECT bi.*, bc.name as category_name FROM bookmark_info bi LEFT JOIN bookmark_category bc ON bi.category_id = bc.id WHERE bi.delete_flag = 1 ORDER BY bi.created_at DESC")
+    List<BookmarkInfo> selectAllBookmarks();
+
+    /**
+     * 获取分类路径
+     *
+     * @param path
+     * @return
+     */
+    @Select("SELECT name FROM bookmark_category WHERE FIND_IN_SET(id, #{path}) ORDER BY FIND_IN_SET(id, #{path})")
+    List<String> selectCategoryPath(String path);
+
+    /**
+     * 根据分类ID获取路径
+     *
+     * @param categoryId
+     * @return
+     */
+    @Select("SELECT path FROM bookmark_category WHERE id = #{categoryId}")
+    String selectPathByCategoryId(Long categoryId);
+
+    @Select({"<script>" +
+            "select bi.*,CONCAT_WS('->', c1.name, c2.name, c3.name) categoryName from bookmark_info bi LEFT JOIN bookmark_category c3 ON bi.category_id = c3.id LEFT JOIN bookmark_category c2 ON c3.parent_id = c2.id LEFT JOIN bookmark_category c1 ON c2.parent_id = c1.id WHERE bi.delete_flag = 1" +
+            "<if test=\"keyword != null and keyword != ''\">" +
+            "   and (bi.title like concat('%',#{keyword},'%') or bi.remark like concat('%',#{keyword},'%') or bi.tags like concat('%',#{keyword},'%'))" +
+            "</if>" +
+
+            "<if test='categoryIds != null and categoryIds.size() > 0'>",
+            "AND bi.category_id IN",
+            "<foreach collection='categoryIds' item='categoryId' open='(' separator=',' close=')'>",
+            "#{categoryId}",
+            "</foreach>",
+            "</if>",
+
+            "<if test=\"accountId != null and accountId != ''\">" +
+            "   and bi.account_id = #{accountId}" +
+            "</if>" +
+            "<if test=\"isFavorite != null and isFavorite != ''\">" +
+            "   and bi.is_favorite = #{isFavorite}" +
+            "</if>" +
+            "<if test=\"status != null and status != ''\">" +
+            "   and bi.status = #{status}" +
+            "</if>" +
+            " order by " +
+            "<foreach collection='sortField' item='sf' index=\"index\" separator=\",\">" +
+            "   ${sf} ${sort}" +
+            " </foreach>" +
+            "</script>"})
+    List<BookmarkInfo> findBookmarkList(Map<String, Object> params);
+}

+ 0 - 15
src/main/java/top/lvzhiqiang/mapper/CoinApiConfigMapper.java

@@ -113,21 +113,6 @@ public interface CoinApiConfigMapper {
     @Select("select id,category_name categoryName from coin_exchange_category where delete_flag = 1")
     List<JSONObject> findFileCurrentHoldingCategoryList();
 
-    @Select({"<script>" +
-            "select id,name categoryName from bookmark_category where delete_flag = 1" +
-            "<choose>" +
-            "                <when test=\"parentId != null and parentId != ''\"> " +
-            "                    and parent_id = #{parentId}" +
-            "                </when>" +
-            "                <when test=\"parentId == null\">" +
-            "                    and parent_id is null" +
-            "                </when>" +
-            "                <otherwise></otherwise>" +
-            "</choose>" +
-            " order by sort desc" +
-            "</script>"})
-    List<JSONObject> findBookmarkCategoryList(String parentId);
-
     @Select("select distinct publish_time from coin_youtube_yt2140_live where delete_flag = 1 and status = 1 order by publish_time desc")
     List<String> findYtLivePublishTimeList();
 

+ 0 - 24
src/main/java/top/lvzhiqiang/mapper/CoinMapper.java

@@ -365,30 +365,6 @@ public interface CoinMapper {
             "</script>"})
     BigDecimal getCurrentHoldingTotalAmout(Map<String, Object> params);
 
-    @Select({"<script>" +
-            "select bi.*,bc.name categoryName from bookmark_info bi left join bookmark_category bc on bi.category_id=bc.id WHERE bi.delete_flag = 1 and bc.delete_flag = 1" +
-            "<if test=\"keyword != null and keyword != ''\">" +
-            "   and (bi.title like concat('%',#{keyword},'%') or bi.remark like concat('%',#{keyword},'%') or bi.tags like concat('%',#{keyword},'%'))" +
-            "</if>" +
-            "<if test=\"categoryField != null and categoryField != ''\">" +
-            "   and bi.category_id = #{categoryField}" +
-            "</if>" +
-            "<if test=\"accountId != null and accountId != ''\">" +
-            "   and bi.account_id = #{accountId}" +
-            "</if>" +
-            "<if test=\"isFavorite != null and isFavorite != ''\">" +
-            "   and bi.is_favorite = #{isFavorite}" +
-            "</if>" +
-            "<if test=\"status != null and status != ''\">" +
-            "   and bi.status = #{status}" +
-            "</if>" +
-            " order by " +
-            "<foreach collection='sortField' item='sf' index=\"index\" separator=\",\">" +
-            "   ${sf} ${sort}" +
-            " </foreach>" +
-            "</script>"})
-    List<BookmarkInfo> findBookmarkList(Map<String, Object> params);
-
     @Select("select 1 from coin_users where username = #{username} and password = #{password} and delete_flag = 1")
     Integer existUserByUsernameAndPassword(String username, String password);
 

+ 5 - 1
src/main/java/top/lvzhiqiang/service/impl/CoinApiConfigServiceImpl.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
 import org.springframework.stereotype.Service;
 import top.lvzhiqiang.entity.CoinApiConfig;
 import top.lvzhiqiang.exception.ParameterException;
+import top.lvzhiqiang.mapper.BookmarkMapper;
 import top.lvzhiqiang.mapper.CoinApiConfigMapper;
 import top.lvzhiqiang.mapper.CoinMapper;
 import top.lvzhiqiang.service.CoinApiConfigService;
@@ -28,6 +29,9 @@ public class CoinApiConfigServiceImpl implements CoinApiConfigService {
     @Resource
     private CoinMapper coinMapper;
 
+    @Resource
+    private BookmarkMapper bookmarkMapper;
+
     /**
      * 删除所有
      */
@@ -86,7 +90,7 @@ public class CoinApiConfigServiceImpl implements CoinApiConfigService {
             } else if (coinApiConfig.getNameEn().equals("currentHolding")) {
                 coinApiConfig.setExchangeCategoryList(coinApiConfigMapper.findFileCurrentHoldingCategoryList());
             } else if (coinApiConfig.getNameEn().equals("bookmark")) {
-                coinApiConfig.setCategoryList(coinApiConfigMapper.findBookmarkCategoryList(null));
+                coinApiConfig.setCategoryList(bookmarkMapper.selectCategoriesByParentId(null));
             } else if (coinApiConfig.getNameEn().equals("youtubeLive")) {
                 coinApiConfig.setYtLivePublishTimeList(coinApiConfigMapper.findYtLivePublishTimeList());
             } else if (coinApiConfig.getNameEn().equals("goldenQuotes")) {

+ 18 - 1
src/main/java/top/lvzhiqiang/service/impl/CoinServiceImpl.java

@@ -119,6 +119,8 @@ public class CoinServiceImpl implements CoinService {
     private CoinYoutubeMapper coinYoutubeMapper;
     @Resource
     private GoldenQuotesMapper goldenQuotesMapper;
+    @Resource
+    private BookmarkMapper bookmarkMapper;
 
     @Resource
     private RedissonClient redissonClient;
@@ -1889,7 +1891,22 @@ public class CoinServiceImpl implements CoinService {
                 params.put("sortField", Arrays.asList(params.getString("sortField").split(",")));
             }
 
-            List<BookmarkInfo> bookmarkInfoList = coinMapper.findBookmarkList(params.toJavaObject(Map.class));
+            String level1CategoryField = params.getString("level1CategoryField");
+            String level2CategoryField = params.getString("level2CategoryField");
+            String level3CategoryField = params.getString("level3CategoryField");
+            String categoryId = "999999999999999";
+            if (StringUtils.isNotEmpty(level3CategoryField)) {
+                categoryId = level3CategoryField;
+            } else if (StringUtils.isNotEmpty(level2CategoryField)) {
+                categoryId = level2CategoryField;
+            } else if (StringUtils.isNotEmpty(level1CategoryField)) {
+                categoryId = level1CategoryField;
+            }
+            String path = bookmarkMapper.selectPathByCategoryId(Long.valueOf(categoryId));
+            List<Long> categoryIds = bookmarkMapper.selectCategoryAndChildrenIds(Long.valueOf(categoryId), path);
+            params.put("categoryIds", categoryIds);
+
+            List<BookmarkInfo> bookmarkInfoList = bookmarkMapper.findBookmarkList(params.toJavaObject(Map.class));
 
             PageInfo<BookmarkInfo> bookmarkInfoPageInfo = new PageInfo<>(bookmarkInfoList);
 

+ 7 - 4
src/main/resources/static/coin.html

@@ -445,11 +445,14 @@
             <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-bookmark-pageSize" disabled="disabled" value="20">
             <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-bookmark-pages" disabled="disabled" value="999999">
             <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-bookmark-keyword" placeholder="关键词">
-            <select id="apis-quiet-div-bookmark-categoryField" style="height: 24px;">
-                <!--<option value="">全部</option>-->
+            <select id="apis-quiet-div-bookmark-level1CategoryField" style="height: 24px;">
+                <option value="">请选择一级分类</option>
             </select>
-            <select id="apis-quiet-div-bookmark-subCategoryField" style="height: 24px;">
-                <option value="">--</option>
+            <select id="apis-quiet-div-bookmark-level2CategoryField" style="height: 24px;">
+                <!--<option value="">二级分类</option>-->
+            </select>
+            <select id="apis-quiet-div-bookmark-level3CategoryField" style="height: 24px;">
+                <!--<option value="">三级分类</option>-->
             </select>
             <select id="apis-quiet-div-bookmark-sortField" style="height: 24px;">
                 <option value="bi.created_at">创建时间</option>

+ 76 - 11
src/main/resources/static/js/my-coin.js

@@ -202,11 +202,13 @@ function initOther4Select() {
                         uploadExchangeCategoryField = obj.exchangeCategoryList;
                         $("#apis-quiet-div-currentHolding-categoryField").append(exchangeCategoryStr);
                     } else if (obj.nameEn === 'bookmark') {
-                        var categoryStr = '';
+                        var level1CategoryStr = '';
                         $.each(obj.categoryList, function (index2, obj2) {
-                            categoryStr += '<option value="' + obj2.id + '">' + obj2.categoryName + '</option>';
+                            level1CategoryStr += '<option value="' + obj2.id + '">' + obj2.name + '</option>';
                         });
-                        $("#apis-quiet-div-bookmark-categoryField").append(categoryStr);
+                        $("#apis-quiet-div-bookmark-level1CategoryField").append(level1CategoryStr);
+
+                        resetLowerLevels();
                     } else if (obj.nameEn === 'youtubeLive') {
                         var ytLivePublishTimeStr = '';
                         $.each(obj.ytLivePublishTimeList, function (index2, obj2) {
@@ -448,14 +450,43 @@ function handleSelectChange(objj) {
                     $(".apis-quiet-div-button2").click();
                 }
             });
+
             $("div[id^=apis-quiet-div-]").find("select").unbind("change");
-            $("#apis-quiet-div-" + nameEn).find("select").change(function (e) {
-                if ($(this).attr("id") === 'apis-quiet-div-music-playRuleField') {
-                    return;
-                }
 
-                $(".apis-quiet-div-button2").click();
-            });
+            if (nameEn === 'bookmark') {
+                $('#apis-quiet-div-bookmark-level1CategoryField').change(function () {
+                    var categoryId = $(this).val();
+                    if (categoryId) {
+                        loadChildCategories(categoryId, '#apis-quiet-div-bookmark-level2CategoryField', '二级分类');
+                        $('#apis-quiet-div-bookmark-level2CategoryField').prop('disabled', false);
+                        $('#apis-quiet-div-bookmark-level3CategoryField').prop('disabled', true).html('<option value="">请选择三级分类</option>');
+                    } else {
+                        resetLowerLevels();
+                    }
+                    $(".apis-quiet-div-button2").click();
+                });
+                $('#apis-quiet-div-bookmark-level2CategoryField').change(function () {
+                    var categoryId = $(this).val();
+                    if (categoryId) {
+                        loadChildCategories(categoryId, '#apis-quiet-div-bookmark-level3CategoryField', '三级分类');
+                        $('#apis-quiet-div-bookmark-level3CategoryField').prop('disabled', false);
+                    } else {
+                        $('#apis-quiet-div-bookmark-level3CategoryField').prop('disabled', true).html('<option value="">请选择三级分类</option>');
+                    }
+                    $(".apis-quiet-div-button2").click();
+                });
+                $('#apis-quiet-div-bookmark-level3CategoryField, #apis-quiet-div-bookmark-sortField, #apis-quiet-div-bookmark-sort').change(function () {
+                    $(".apis-quiet-div-button2").click();
+                });
+            } else {
+                $("#apis-quiet-div-" + nameEn).find("select").change(function (e) {
+                    if ($(this).attr("id") === 'apis-quiet-div-music-playRuleField') {
+                        return;
+                    }
+
+                    $(".apis-quiet-div-button2").click();
+                });
+            }
         }
     });
 
@@ -464,6 +495,39 @@ function handleSelectChange(objj) {
     });
 }
 
+// 加载子分类
+function loadChildCategories(parentId, targetSelector, placeholder) {
+    $.ajax({
+        url: 'bookmarkInfo/categories?parentId=' + parentId,
+        type: 'GET',
+        success: function (data) {
+            //请求成功时处理
+            if (data != null && $.trim(data) != "" && data.success) {
+                var categories = data.data;
+
+                var options = '<option value="">请选择' + placeholder + '</option>';
+                if (categories.length > 0) {
+                    categories.forEach(function (category) {
+                        options += '<option value="' + category.id + '">' + category.name + '</option>';
+                    });
+                } else {
+                    options = '<option value="">暂无' + placeholder + '</option>';
+                }
+                $(targetSelector).html(options);
+
+            }
+        },
+        error: function () {
+            alert("加载子分类失败!");
+        }
+    });
+}
+// 重置下级分类
+function resetLowerLevels() {
+    $('#apis-quiet-div-bookmark-level2CategoryField, #apis-quiet-div-bookmark-level3CategoryField').prop('disabled', true)
+        .html('<option value="">请先选择上级分类</option>');
+}
+
 /**
  * 多条件搜索
  * @param pageNo
@@ -540,8 +604,9 @@ function mainSearch(url, nameEn, slideDiv, typetype, needCustomFlag) {
         jsonData.keyword = $("#apis-quiet-div-bookmark-keyword").val();
         jsonData.sortField = $("#apis-quiet-div-bookmark-sortField").val();
         jsonData.sort = $("#apis-quiet-div-bookmark-sort").val();
-        jsonData.categoryField = $("#apis-quiet-div-bookmark-categoryField").val();
-        jsonData.subCategoryField = $("#apis-quiet-div-bookmark-subCategoryField").val();
+        jsonData.level1CategoryField = $("#apis-quiet-div-bookmark-level1CategoryField").val();
+        jsonData.level2CategoryField = $("#apis-quiet-div-bookmark-level2CategoryField").val();
+        jsonData.level3CategoryField = $("#apis-quiet-div-bookmark-level3CategoryField").val();
     } else if (nameEn === 'youtubeLive') {
         jsonData.pageNo = $("#apis-quiet-div-youtubeLive-pageNo").val();
         jsonData.pageSize = $("#apis-quiet-div-youtubeLive-pageSize").val();