diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..ae7a074 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,28 @@ +name: "Close inactive issues" +on: + schedule: + - cron: "10 15 * * *" + +permissions: + issues: write + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v5 + with: + debug-only: false + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-issue-stale: 30 + days-before-issue-close: 7 + stale-issue-label: "stale" + stale-issue-message: "This issue has been open 30 days with no activity. This will be closed in 7 days." + close-issue-message: "This issue has been automatically marked as stale because it hasn't had any recent activity." + days-before-pr-stale: -1 + days-before-pr-close: -1 + exempt-issue-labels: bug,enhancement diff --git a/.gitignore b/.gitignore index 13e4eff..1301597 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ stats.html /nbdist/ /.nb-gradle/ /build/ +/lucene/ ### VS Code ### .vscode/ diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/param/ParamService.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/param/ParamService.java index a7c60e2..e55b88c 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/param/ParamService.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/param/ParamService.java @@ -41,7 +41,7 @@ public class ParamService extends ServiceImpl { */ @EventListener(ApplicationStartedEvent.class) public void refresh() { - log.info("[ BASE] 初始化系统参数缓存"); + log.info("[ PARAM] 初始化系统参数缓存"); CACHE.clear(); List params = baseMapper.selectList(new QueryWrapper<>()); if (CollUtil.isEmpty(params)) { diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/paramu/UserParamService.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/paramu/UserParamService.java index b032d97..c1c2eb5 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/paramu/UserParamService.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/paramu/UserParamService.java @@ -15,6 +15,7 @@ import com.blossom.backend.base.user.pojo.UserEntity; import com.blossom.common.base.exception.XzException500; import com.blossom.common.base.util.BeanUtil; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; @@ -28,6 +29,7 @@ import java.util.stream.Collectors; /** * 用户参数 */ +@Slf4j @Service @AllArgsConstructor public class UserParamService extends ServiceImpl { @@ -42,6 +44,7 @@ public class UserParamService extends ServiceImpl users = userMapper.selectList(new QueryWrapper<>()); // 初始化所有用户的配置参数 diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/EnableIndex.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/EnableIndex.java index 4294cc4..da6e342 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/EnableIndex.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/EnableIndex.java @@ -16,13 +16,11 @@ public @interface EnableIndex { /** * 索引操作类型, 默认值为追加 - * @return */ IndexMsgTypeEnum type(); /** - * id字段表达式 - * @return + * ID 字段表达式 */ String id(); diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexAspect.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexAspect.java index dcb49e8..395b758 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexAspect.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexAspect.java @@ -33,36 +33,32 @@ public class IndexAspect { private static final LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); @Pointcut("@annotation(com.blossom.backend.base.search.EnableIndex)") - private void cutMethod(){ + private void cutMethod() { } /** - * 成功返回后调用该方法,维护索引。 使用after防止事务未提交导致的数据滞后 + * 成功返回后调用该方法, 维护索引. 使用after防止事务未提交导致的数据滞后 */ @AfterReturning("cutMethod()") public void afterReturning(JoinPoint joinPoint) { // 此处进行索引处理, 降低索引维护代码侵入 // 获取注解对象 EnableIndex annotation = getAnnotation(joinPoint); - if (annotation == null){ + if (annotation == null) { // 记录问题, 并结束逻辑 log.error("索引切面获取注解失败!"); return; } IndexMsgTypeEnum indexMsgTypeEnum = annotation.type(); - if (indexMsgTypeEnum == null){ - log.error("获取索引消息操作类型失败"); - return; - } String idSpEL = annotation.id(); - if (!StringUtils.hasText(idSpEL)){ + if (!StringUtils.hasText(idSpEL)) { log.error("获取id表达式失败"); return; } Long customerId = parse(idSpEL, joinPoint, Long.class); - if (customerId == null){ + if (customerId == null) { return; } ArticleIndexMsg indexMsg = new ArticleIndexMsg(indexMsgTypeEnum, customerId, AuthContext.getUserId()); @@ -76,15 +72,16 @@ public class IndexAspect { /** * 获取method + * * @return method */ private Method getTargetMethod(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); - MethodSignature methodSignature = (MethodSignature)signature; + MethodSignature methodSignature = (MethodSignature) signature; Method agentMethod = methodSignature.getMethod(); try { - return joinPoint.getTarget().getClass().getMethod(agentMethod.getName(),agentMethod.getParameterTypes()); - }catch (Exception e){ + return joinPoint.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes()); + } catch (Exception e) { // 只记录异常 log.error("获取目标方法失败"); } @@ -93,33 +90,26 @@ public class IndexAspect { /** * 获取注解声明对象 - * @param joinPoint - * @return */ - private EnableIndex getAnnotation(JoinPoint joinPoint){ + private EnableIndex getAnnotation(JoinPoint joinPoint) { // 获取方法上的注解 MethodSignature sign = (MethodSignature) joinPoint.getSignature(); Method method = sign.getMethod(); - return method.getAnnotation(EnableIndex.class); + return method.getAnnotation(EnableIndex.class); } /** * 解析SpEL表达式, 提供后续拓展的灵活性 - * @param spel - * @param joinPoint - * @param clazz - * @return - * @param */ - private T parse(String spel, JoinPoint joinPoint, Class clazz){ + private T parse(String spel, JoinPoint joinPoint, Class clazz) { ExpressionParser parser = new SpelExpressionParser(); Method method = getTargetMethod(joinPoint); - if (method == null){ + if (method == null) { return null; } String[] params = discoverer.getParameterNames(method); - if (params == null || params.length == 0){ + if (params == null || params.length == 0) { log.error("获取参数列表失败"); return null; } @@ -135,5 +125,4 @@ public class IndexAspect { } - } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexObserver.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexObserver.java index c4d1831..0bbae30 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexObserver.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/IndexObserver.java @@ -7,9 +7,10 @@ import com.blossom.backend.base.search.message.consumer.BatchIndexMsgConsumer; import com.blossom.backend.server.article.draft.ArticleService; import com.blossom.backend.server.article.draft.pojo.ArticleEntity; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import java.io.IOException; import java.util.ArrayList; @@ -18,37 +19,47 @@ import java.util.List; /** * 对既有索引进行监控与维护 */ - -@Component @Slf4j +@Component public class IndexObserver { - private SearchProperties searchProperties; - private ArticleService articleService; - private BatchIndexMsgConsumer batchIndexMsgConsumer; + private final SearchProperties searchProperties; + private final ArticleService articleService; + private final BatchIndexMsgConsumer batchIndexMsgConsumer; - - IndexObserver(SearchProperties searchProperties, ArticleService articleService, BatchIndexMsgConsumer batchIndexMsgConsumer){ + IndexObserver(SearchProperties searchProperties, ArticleService articleService, BatchIndexMsgConsumer batchIndexMsgConsumer) { this.searchProperties = searchProperties; this.articleService = articleService; this.batchIndexMsgConsumer = batchIndexMsgConsumer; } + /** + * 启动时维护索引 + */ + @EventListener(ApplicationStartedEvent.class) + public void refresh() { + try { + log.info("[ SEARCH] 重建全部用户索引开始"); + long start = System.currentTimeMillis(); + this.reloadIndex(); + log.info("[ SEARCH] 重建全部用户索引完成, 用时: {} ms", (System.currentTimeMillis() - start)); + } catch (IOException e) { + e.printStackTrace(); + } + } + /** * 进行索引的维护 */ @Scheduled(cron = "0 0 04 * * ?") public void reloadIndex() throws IOException { - if (StringUtils.hasText(searchProperties.getPath())){ - List allArticleWithContent = articleService.listAllArticleWithContent(); - List batchReloadMsgs = new ArrayList<>(); - allArticleWithContent.forEach(article ->{ - ArticleIndexMsg articleIndexMsg = new ArticleIndexMsg(IndexMsgTypeEnum.ADD,article.getId(),article.getName(),article.getTags(),article.getMarkdown(),article.getUserId()); - batchReloadMsgs.add(articleIndexMsg); - }); - - batchIndexMsgConsumer.batchReload(batchReloadMsgs); - } + List allArticleWithContent = articleService.listAllIndexField(); + List batchReloadMsgs = new ArrayList<>(); + allArticleWithContent.forEach(article -> { + ArticleIndexMsg articleIndexMsg = new ArticleIndexMsg(IndexMsgTypeEnum.ADD, article.getId(), article.getName(), article.getTags(), article.getMarkdown(), article.getUserId()); + batchReloadMsgs.add(articleIndexMsg); + }); + batchIndexMsgConsumer.batchReload(batchReloadMsgs); } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchController.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchController.java new file mode 100644 index 0000000..20a6991 --- /dev/null +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchController.java @@ -0,0 +1,37 @@ +package com.blossom.backend.base.search; + +import com.blossom.backend.base.auth.AuthContext; +import com.blossom.common.base.pojo.R; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 搜索接口 + */ +@Slf4j +@RestController +public class SearchController { + + @Autowired + private Searcher searcher; + + /** + * 搜索 + * + * @param keyword 搜索关键字 + * @param hlColor 高亮颜色 + * @param operator 是否全部匹配 + * @param debug 是否DEBUG, 为 true 时高亮前后缀为【】 + * @since 1.12.0 + */ + @GetMapping("/search") + public R search(@RequestParam("keyword") String keyword, + @RequestParam("hlColor") String hlColor, + @RequestParam("operator") boolean operator, + @RequestParam("debug") boolean debug) { + return R.ok(searcher.search(keyword, AuthContext.getUserId(), hlColor, operator, debug)); + } +} diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchProperties.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchProperties.java index 50d5ca4..fa31dbc 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchProperties.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchProperties.java @@ -1,26 +1,32 @@ package com.blossom.backend.base.search; -import cn.hutool.core.convert.Convert; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; +import java.io.File; import java.nio.file.Path; -import java.nio.file.Paths; -@Data -@Configuration -@ConfigurationProperties(prefix = "project.search") +/** + * 全文搜索配置项 + */ +@Component public class SearchProperties { - private String path = ""; + private static final String USER_HOME = "user.dir"; /** - * 根据用户id, 获取对应索引库path - * @param userId 用户id - * @return 索引库path + * 根据用户ID, 获取对应索引库 Path + * + * @param userId 用户ID */ - public Path getUserIndexDirectory(Long userId){ - return Paths.get(this.path, Convert.toStr(userId)); + public Path getUserIndexDirectory(Long userId) { + File file = new File(addSeparator("/lucene/" + userId)); + return file.toPath(); + } + + public static String addSeparator(String dir) { + if (!dir.endsWith(File.separator)) { + dir += File.separator; + } + return System.getProperty(USER_HOME) + dir; } } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchRes.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchRes.java new file mode 100644 index 0000000..d435f04 --- /dev/null +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchRes.java @@ -0,0 +1,53 @@ +package com.blossom.backend.base.search; + +import lombok.Data; + +import java.util.List; + +/** + * 全文搜索结果 + */ +@Data +public class SearchRes { + + /** + * 命中总数 + */ + private Long totalHit; + + /** + * 命中数据 + */ + private List hits; + + /** + * 命中数据 + */ + @Data + public static class Hit { + /** + * 主键 + */ + private Long id; + /** + * 文章名称 + */ + private String name; + /** + * 源文章名称 + */ + private String originName; + /** + * 标签 + */ + private List tags; + /** + * 正文 + */ + private String markdown; + /** + * 命中分数 + */ + private Float score; + } +} diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchResult.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchResult.java deleted file mode 100644 index deee830..0000000 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/SearchResult.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.blossom.backend.base.search; - -import lombok.Data; - -/** - * 全文搜索返回对象 - */ -@Data -public class SearchResult { - /** - * 主键 - */ - private Long id; - /** - * 标题 - */ - private String title; - /** - * 标签 - */ - private String tags; - /** - * 正文 - */ - private String content; - -} diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/Searcher.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/Searcher.java index d2f94ee..014e079 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/Searcher.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/Searcher.java @@ -2,12 +2,15 @@ package com.blossom.backend.base.search; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ArrayUtil; -import com.blossom.common.base.exception.XzException500; +import cn.hutool.core.util.StrUtil; +import com.blossom.backend.server.utils.DocUtil; +import lombok.extern.slf4j.Slf4j; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; +import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; @@ -20,117 +23,148 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +/** + * 搜索类 + */ +@Slf4j @Component public class Searcher { - private SimpleHTMLFormatter simpleHTMLFormatter; + private final SimpleHTMLFormatter debugFmt = new SimpleHTMLFormatter("【", "】"); - private String[] queryField; + private final String[] queryField; - private Map boostsMap; + private final Map boostsMap; @Autowired private SearchProperties searchProperties; - Searcher() { - // 构造f高亮显示formatter - this.simpleHTMLFormatter = new SimpleHTMLFormatter("", ""); // 构造默认查询域 this.queryField = new String[3]; - this.queryField[0] = "content"; - this.queryField[1] = "title"; + this.queryField[0] = "markdown"; + this.queryField[1] = "name"; this.queryField[2] = "tags"; // 构造权重配置 this.boostsMap = new HashMap<>(); - boostsMap.put("title", 2F); - boostsMap.put("tags", 2F); - boostsMap.put("content", 1F); + this.boostsMap.put("name", 3F); + this.boostsMap.put("tags", 3F); + this.boostsMap.put("markdown", 1F); } /** - * 进行索引查询, 传入关键词以及用户id - * @param keyword 关键词 - * @param userId 用户id + * 搜索 + * + * @param keyword 关键词 + * @param userId 用户id + * @param hlColor 高亮颜色 + * @param operator 是否全部匹配 + * @param debug 是否DEBUG, 为 true 时高亮前后缀为【】 * @return 查询结果 */ - public List search(String keyword, Long userId) { - List result = new ArrayList<>(); - if (!StringUtils.hasText(searchProperties.getPath())) { - throw new XzException500("未配置索引库地址,无法进行全文检索"); + public SearchRes search(String keyword, Long userId, String hlColor, boolean operator, boolean debug) { + SearchRes result = new SearchRes(); + result.setHits(new ArrayList<>()); + + if (StrUtil.isBlank(keyword)) { + result.setTotalHit(0L); + return result; } - if (userId == null){ + if (userId == null) { throw new IllegalArgumentException("未获取到用户信息"); } try (Directory directory = FSDirectory.open(searchProperties.getUserIndexDirectory(userId)); - IndexReader indexReader = DirectoryReader.open(directory); - ) { + IndexReader indexReader = DirectoryReader.open(directory)) { IndexSearcher indexSearcher = new IndexSearcher(indexReader); - MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(queryField, new StandardAnalyzer(),boostsMap); + MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(queryField, new StandardAnalyzer(), boostsMap); + if (operator) { + multiFieldQueryParser.setDefaultOperator(QueryParser.Operator.AND); + } + Query query = multiFieldQueryParser.parse(keyword); - TopDocs topDocs = indexSearcher.search(query, 10); + + TopDocs topDocs = indexSearcher.search(query, 20); ScoreDoc[] scoreDocs = topDocs.scoreDocs; + result.setTotalHit(topDocs.totalHits.value); if (!ArrayUtil.isEmpty(scoreDocs)) { - Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query)); - highlighter.setTextFragmenter(new SimpleFragmenter(20)); + Highlighter highlighter; + if (debug) { + highlighter = new Highlighter(debugFmt, new QueryScorer(query)); + } else { + highlighter = new Highlighter(new SimpleHTMLFormatter("", ""), new QueryScorer(query)); + } + highlighter.setTextFragmenter(new SimpleFragmenter(100)); + for (ScoreDoc doc : scoreDocs) { Document document = indexSearcher.doc(doc.doc); String id = document.get("id"); - String title = document.get("title"); - String content = document.get("content"); + String name = document.get("name"); + String markdown = document.get("markdown"); String tags = document.get("tags"); - SearchResult searchResult = new SearchResult(); - searchResult.setId(Convert.toLong(id)); - if (StringUtils.hasText(title)){ - String matchTitle = highlighter.getBestFragment(new StandardAnalyzer(), "title", title); - if (StringUtils.hasText(matchTitle)){ - searchResult.setTitle(matchTitle); - }else { - searchResult.setTitle(title); + + SearchRes.Hit hit = new SearchRes.Hit(); + hit.setScore(doc.score); + hit.setId(Convert.toLong(id)); + if (StrUtil.isNotBlank(name)) { + hit.setOriginName(name); + String hlName = highlighter.getBestFragment(new StandardAnalyzer(), "name", name); + if (StrUtil.isNotBlank(hlName)) { + hit.setName(hlName); + } else { + hit.setName(name); } - }else { - searchResult.setContent(title); - } - if (StringUtils.hasText(content)){ - String matchContent = highlighter.getBestFragment(new StandardAnalyzer(), "content", content); - if (StringUtils.hasText(matchContent)){ - searchResult.setContent(matchContent); - }else { - searchResult.setContent(content); - } - }else { - searchResult.setContent(content); + } else { + hit.setName(""); + hit.setOriginName(""); } - if (StringUtils.hasText(tags)){ - String matchTags= highlighter.getBestFragment(new StandardAnalyzer(), "tags", tags); - if (StringUtils.hasText(matchTags)){ - searchResult.setTags(matchTags); - }else { - searchResult.setTags(tags); + // 无内容或无高亮匹配时, 返回 "" + if (StrUtil.isNotBlank(markdown)) { + String hlMarkdown = highlighter.getBestFragment(new StandardAnalyzer(), "markdown", markdown); + if (StrUtil.isNotBlank(hlMarkdown)) { + hit.setMarkdown(fmtMarkdown(hlMarkdown)); + } else { + hit.setMarkdown(""); } - }else { - searchResult.setTags(tags); + } else { + hit.setMarkdown(""); } - result.add(searchResult); + + if (StrUtil.isNotBlank(tags)) { + String hlTags = highlighter.getBestFragment(new StandardAnalyzer(), "tags", tags); + if (StrUtil.isNotBlank(hlTags)) { + hit.setTags(DocUtil.toTagList(hlTags)); + } else { + hit.setTags(DocUtil.toTagList(tags)); + } + } else { + hit.setTags(DocUtil.toTagList(tags)); + } + + result.getHits().add(hit); } } - - } catch (Exception e) { - throw new XzException500("索引查询异常"); + log.error("搜索异常: {}", e.getMessage()); + return result; } return result; + } + /** + * 将正文中的换行转换成 html 内容 + * + * @param markdown markdown 正文内容 + */ + public String fmtMarkdown(String markdown) { + return markdown.replaceAll("(\r\n|\n\r|\r|\n)", "
"); } } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/ArticleIndexMsg.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/ArticleIndexMsg.java index ac69ba9..1877cf2 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/ArticleIndexMsg.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/ArticleIndexMsg.java @@ -1,70 +1,59 @@ package com.blossom.backend.base.search.message; import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import lombok.Getter; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; -import org.springframework.util.StringUtils; /** * 文章索引消息的实现 */ - +@Getter public class ArticleIndexMsg implements IndexMsg { - private IndexMsgTypeEnum type; + private final IndexMsgTypeEnum type; - private Long data; - - private Document document; - - private Long userId; + private final Long id; + private Document doc; + private final Long userId; public ArticleIndexMsg(IndexMsgTypeEnum indexMsgType, Long id, Long userId) { this.type = indexMsgType; - this.data = id; + this.id = id; this.userId = userId; } - public ArticleIndexMsg(IndexMsgTypeEnum indexMsgType, Long id, String title, String tags, String content, Long userId){ + /** + * 创建文章索引消息 + * + * @param indexMsgType 操作类型 + * @param id 唯一ID + * @param name 标题 + * @param tags 标签 + * @param markdown 正文内容 + * @param userId 用户ID + */ + public ArticleIndexMsg(IndexMsgTypeEnum indexMsgType, Long id, String name, String tags, String markdown, Long userId) { this.type = indexMsgType; - this.data = id; + this.id = id; this.userId = userId; Document document = new Document(); - // 存储文章的id, content + // 存储文章的id, markdown document.add(new StringField("id", Convert.toStr(id), Field.Store.YES)); - if (StringUtils.hasText(title)){ - document.add(new TextField("title", title, Field.Store.YES)); + if (StrUtil.isNotBlank(name)) { + document.add(new TextField("name", name, Field.Store.YES)); } - if (StringUtils.hasText(content)){ - document.add(new TextField("content", content, Field.Store.YES)); + if (StrUtil.isNotBlank(markdown)) { + document.add(new TextField("markdown", markdown, Field.Store.YES)); } - if (StringUtils.hasText(tags)){ + if (StrUtil.isNotBlank(tags)) { document.add(new TextField("tags", tags, Field.Store.YES)); } - this.document = document; - } - - @Override - public IndexMsgTypeEnum getType() { - return this.type; - } - - @Override - public Long getId() { - return this.data; - } - - @Override - public Document getDoc() { - return this.document; - } - - @Override - public Long getCurrentUserId() { - return this.userId; + this.doc = document; } } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsg.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsg.java index 210690e..c9608b6 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsg.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsg.java @@ -8,27 +8,23 @@ import org.apache.lucene.document.Document; public interface IndexMsg { /** - * 消息类型 - * @return + * 消息操作类型 */ IndexMsgTypeEnum getType(); /** - * 主键id - * @return + * 主键 ID */ Long getId(); /** - * 为批量reload提供的提前构造数据的接口,避免多次查询数据库 - * @return + * 为批量 reload 提供的提前构造数据的接口, 避免多次查询数据库 */ Document getDoc(); /** - * 消息对应用户id - * @return + * 消息对应用户 ID */ - Long getCurrentUserId(); + Long getUserId(); } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsgTypeEnum.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsgTypeEnum.java index d5217ed..8066d09 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsgTypeEnum.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/IndexMsgTypeEnum.java @@ -5,6 +5,13 @@ package com.blossom.backend.base.search.message; */ public enum IndexMsgTypeEnum { - ADD,DELETE + /** + * 新增文档 + */ + ADD, + /** + * 删除文档 + */ + DELETE } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/BatchIndexMsgConsumer.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/BatchIndexMsgConsumer.java index 841f175..adfb7ca 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/BatchIndexMsgConsumer.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/BatchIndexMsgConsumer.java @@ -13,59 +13,63 @@ import org.apache.lucene.index.Term; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -@Component +/** + * 批量构建索引 + */ @Slf4j +@Component public class BatchIndexMsgConsumer { - private SearchProperties searchProperties; + private final SearchProperties searchProperties; BatchIndexMsgConsumer(SearchProperties searchProperties) { this.searchProperties = searchProperties; - if (!StringUtils.hasText(searchProperties.getPath())) { - log.info("未配置索引库地址, 关闭全文搜索功能支持"); - } } - public void batchReload(List list) throws IOException { - // 需要对所有用户的索引进行维护,减少文件打开次数, 优先进行分组 - Map> userGroupMsgMap = list.stream().collect(Collectors.groupingBy(IndexMsg::getCurrentUserId)); - // 遍历map, 逐个用户进行索引重建 - for (Map.Entry> entity : userGroupMsgMap.entrySet()){ + /** + * 批量构建所有用户的索引 + * + * @param articles 全部文章 + */ + public void batchReload(List articles) throws IOException { + // 需要对所有用户的索引进行维护, 减少文件打开次数, 优先进行分组 + Map> userGroupMsgMap = articles.stream().collect(Collectors.groupingBy(IndexMsg::getUserId)); + // 遍历 Map, 逐个用户进行索引重建 + for (Map.Entry> entity : userGroupMsgMap.entrySet()) { Long userId = entity.getKey(); List msgList = entity.getValue(); - if (userId == null){ + if (userId == null) { continue; } try (Directory directory = FSDirectory.open(searchProperties.getUserIndexDirectory(userId)); - IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer())); - ){ - for (IndexMsg indexMsg : msgList){ - if (IndexMsgTypeEnum.ADD.equals(indexMsg.getType())) { - // 插入 or 更新索引 - // 打开索引库 --->通过getDoc方法获取索引文档 + IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) { + for (IndexMsg indexMsg : msgList) { + /* + * 插入或更新索引, 通过 getDoc 方法获取索引文档 + */ + if (IndexMsgTypeEnum.ADD == indexMsg.getType()) { Document document = indexMsg.getDoc(); String id = document.get("id"); indexWriter.updateDocument(new Term("id", id), document); - } else if (IndexMsgTypeEnum.DELETE.equals(indexMsg.getType())) { - // 删除索引 + } + /* + * 删除索引内容, 批量处理时不会进行删除, 所有的删除都由用户主动操作 + */ + else if (IndexMsgTypeEnum.DELETE == indexMsg.getType()) { Long id = indexMsg.getId(); indexWriter.deleteDocuments(new Term("id", Convert.toStr(id))); } } - // 完成 indexWriter.flush(); indexWriter.commit(); } - } - } } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/IndexMsgConsumer.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/IndexMsgConsumer.java index fd13b9f..414f8aa 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/IndexMsgConsumer.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/message/consumer/IndexMsgConsumer.java @@ -19,7 +19,6 @@ import org.apache.lucene.index.Term; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import java.util.concurrent.Executors; @@ -28,79 +27,61 @@ import java.util.concurrent.Executors; * * @author Andecheal */ -@Component @Slf4j +@Component public class IndexMsgConsumer { - private SearchProperties searchProperties; + private final SearchProperties searchProperties; - private ArticleService articleService; + private final ArticleService articleService; + /** + * 单线程处理索引文档消息 + * + * @param searchProperties 索引配置 + * @param articleService 文章服务 + */ IndexMsgConsumer(SearchProperties searchProperties, ArticleService articleService) { this.searchProperties = searchProperties; this.articleService = articleService; - if (!StringUtils.hasText(searchProperties.getPath())) { - log.info("未配置索引库地址, 关闭全文搜索功能支持"); - return; - } - Executors.newSingleThreadExecutor().submit(new Runnable() { - @Override - public void run() { - while (true) { - try { - IndexMsg indexMsg = IndexMsgQueue.take(); - // 首先获取消息中的userId, 根据userId 打开对应的索引库 - Long userId = indexMsg.getCurrentUserId(); - if (userId == null){ - // 记录异常并继续消费 - log.error("消费异常. 获取用户id为空"); - continue; - } - if (IndexMsgTypeEnum.ADD.equals(indexMsg.getType())) { - // 插入 or 更新索引 - // 打开索引库 - try (Directory directory = FSDirectory.open(searchProperties.getUserIndexDirectory(userId)); - IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer())); - ) { - // 查询doc数据 ---> 避免service部分功能未查询全部字段数据导致的索引field丢失 - Long id = indexMsg.getId(); - ArticleEntity article = articleService.getById(id); - Document document = new Document(); - String title = article.getName(); - String markdownContent = article.getMarkdown(); - String tags = article.getTags(); - // 存储文章的id, content - document.add(new StringField("id", Convert.toStr(id), Field.Store.YES)); - if (StringUtils.hasText(title)){ - document.add(new TextField("title", article.getName(), Field.Store.YES)); - } - if (StringUtils.hasText(markdownContent)){ - document.add(new TextField("content", markdownContent, Field.Store.YES)); - } - if (StringUtils.hasText(tags)){ - document.add(new TextField("tags", tags, Field.Store.YES)); - } - - indexWriter.updateDocument(new Term("id", Convert.toStr(id)), document); - indexWriter.flush(); - indexWriter.commit(); - } - } else if (IndexMsgTypeEnum.DELETE.equals(indexMsg.getType())) { - // 删除索引 - try (Directory directory = FSDirectory.open(searchProperties.getUserIndexDirectory(userId)); - IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer())); - - ) { - Long id = indexMsg.getId(); - indexWriter.deleteDocuments(new Term("id", Convert.toStr(id))); - indexWriter.flush(); - indexWriter.commit(); - } - } - - } catch (Exception e) { - log.error("消费失败" + e.getMessage()); + Executors.newSingleThreadExecutor().submit(() -> { + while (true) { + try { + IndexMsg indexMsg = IndexMsgQueue.take(); + final Long userId = indexMsg.getUserId(); + final Long id = indexMsg.getId(); + if (userId == null || id == null) { + log.error("消费异常. 获取用户id为空"); + continue; } + if (IndexMsgTypeEnum.ADD == indexMsg.getType()) { + // 插入 or 更新索引 + // 打开索引库 + try (Directory directory = FSDirectory.open(this.searchProperties.getUserIndexDirectory(userId)); + IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) { + // 查询最新的消息 + ArticleEntity article = this.articleService.selectById(id, false, true, false); + Document document = new Document(); + document.add(new StringField("id", Convert.toStr(id), Field.Store.YES)); + document.add(new TextField("name", article.getName(), Field.Store.YES)); + document.add(new TextField("tags", article.getTags(), Field.Store.YES)); + document.add(new TextField("markdown", article.getMarkdown(), Field.Store.YES)); + indexWriter.updateDocument(new Term("id", Convert.toStr(id)), document); + indexWriter.flush(); + indexWriter.commit(); + } + } else if (IndexMsgTypeEnum.DELETE == indexMsg.getType()) { + // 删除索引 + try (Directory directory = FSDirectory.open(this.searchProperties.getUserIndexDirectory(userId)); + IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) { + indexWriter.deleteDocuments(new Term("id", Convert.toStr(id))); + indexWriter.flush(); + indexWriter.commit(); + } + } + + } catch (Exception e) { + log.error("消费失败" + e.getMessage()); } } }); diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/queue/IndexMsgQueue.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/queue/IndexMsgQueue.java index e7119df..44b0b52 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/queue/IndexMsgQueue.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/search/queue/IndexMsgQueue.java @@ -7,19 +7,18 @@ import java.util.concurrent.BlockingQueue; /** * 消息处理使用的阻塞队列 + * * @author Andecheal */ public class IndexMsgQueue { /** - * 阻塞队列 , 存放消息 + * 阻塞队列, 存放消息 */ private static final BlockingQueue indexMsgQueue = new ArrayBlockingQueue<>(2048); /** - * 应用提交消息 - * @param msg - * @throws InterruptedException + * 提交消息 */ public static void add(IndexMsg msg) throws InterruptedException { indexMsgQueue.add(msg); @@ -27,8 +26,6 @@ public class IndexMsgQueue { /** * 提供一个阻塞式消息入口 - * @param msg - * @throws InterruptedException */ public static void put(IndexMsg msg) throws InterruptedException { indexMsgQueue.put(msg); @@ -36,8 +33,6 @@ public class IndexMsgQueue { /** * 获取消息 - * @return - * @throws InterruptedException */ public static IndexMsg take() throws InterruptedException { return indexMsgQueue.take(); diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/base/user/UserController.java b/blossom-backend/backend/src/main/java/com/blossom/backend/base/user/UserController.java index 363e9a9..fe69bd4 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/base/user/UserController.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/base/user/UserController.java @@ -15,6 +15,7 @@ import com.blossom.backend.server.article.stat.ArticleStatService; import com.blossom.common.base.exception.XzException400; import com.blossom.common.base.exception.XzException404; import com.blossom.common.base.pojo.R; +import com.blossom.common.base.util.spring.SpringUtil; import lombok.AllArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -45,6 +46,7 @@ public class UserController { user.setOsRes(sysService.getOsConfig()); Map paramMap = paramService.selectMap(true, ParamEnum.values()); user.setParams(paramMap); + paramMap.put("SERVER_VERSION", SpringUtil.get("project.base.version")); Map userParamMap = userParamService.selectMap(AuthContext.getUserId(), true, UserParamEnum.values()); user.setUserParams(userParamMap); return R.ok(user); diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleMapper.java b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleMapper.java index a07856d..10890ee 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleMapper.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleMapper.java @@ -18,7 +18,8 @@ public interface ArticleMapper extends BaseMapper { /** * 批量查询文章正文 - * @param ids 文章ID + * + * @param ids 文章ID * @param contentType 正文类型 MARKDOWN/HTML */ List listAllContent(@Param("ids") List ids, @Param("contentType") String contentType); @@ -28,7 +29,10 @@ public interface ArticleMapper extends BaseMapper { */ List listAll(ArticleEntity entity); - List listAllArticleWithContent(); + /** + * 查询全部需要索引的字段 + */ + List listAllIndexField(); /** * 根据ID修改 diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleService.java b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleService.java index b98f965..f29450e 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleService.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleService.java @@ -83,10 +83,11 @@ public class ArticleService extends ServiceImpl { /** * 获取所有文章,包含markdown字段,用于索引的批量维护 + * * @return */ - public List listAllArticleWithContent() { - List articles = baseMapper.listAllArticleWithContent(); + public List listAllIndexField() { + List articles = baseMapper.listAllIndexField(); if (CollUtil.isEmpty(articles)) { return new ArrayList<>(); } diff --git a/blossom-backend/backend/src/main/resources/config/application-dev.yml b/blossom-backend/backend/src/main/resources/config/application-dev.yml index df6b3c3..e9787b9 100644 --- a/blossom-backend/backend/src/main/resources/config/application-dev.yml +++ b/blossom-backend/backend/src/main/resources/config/application-dev.yml @@ -40,7 +40,7 @@ project: duration: 600000 # 动态日志级别 6 分钟后失效 restore-duration: 30000 # 30 秒判断一次是否失效 db: - slow-interval: 10 # 慢SQL + slow-interval: 100 # 慢SQL # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ Auth ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ auth: # 授权 enabled: true # 开启授权 @@ -86,7 +86,4 @@ project: # 请以 /pic 结尾, 如果你在 nginx 中配置有代理, 注意别忘了添加你的代理路径 domain: "http://www.xxx.com/" # 请以 / 开头, / 结尾, 简短的路径在文章中有更好的显示效果, 过长一定程度会使文章内容混乱 - default-path: "/home/bl/img/" - # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ 全文搜索 ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - search: - path: "/home/index/" \ No newline at end of file + default-path: "/home/bl/img/" \ No newline at end of file diff --git a/blossom-backend/backend/src/main/resources/config/application-prod.yml b/blossom-backend/backend/src/main/resources/config/application-prod.yml index b1eae5b..9511248 100644 --- a/blossom-backend/backend/src/main/resources/config/application-prod.yml +++ b/blossom-backend/backend/src/main/resources/config/application-prod.yml @@ -78,7 +78,4 @@ project: # 注意:在下方示例中, /bl 即为 nginx 反向代理路径, 如果你的访问路径中不包含反向代理或路径不同, 请酌情删除或修改 domain: "http://www.xxx.com/" # 请以 / 开头, / 结尾, 简短的路径在文章中有更好的显示效果, 过长一定程度会使文章内容混乱 - default-path: "/home/bl/img/" - # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ 全文搜索 ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - search: - path: "/home/index/" + default-path: "/home/bl/img/" \ No newline at end of file diff --git a/blossom-backend/backend/src/main/resources/config/application-test.yml b/blossom-backend/backend/src/main/resources/config/application-test.yml index 179690a..125356c 100644 --- a/blossom-backend/backend/src/main/resources/config/application-test.yml +++ b/blossom-backend/backend/src/main/resources/config/application-test.yml @@ -80,7 +80,4 @@ project: # 注意:在下方示例中, /bl 即为 nginx 反向代理路径, 如果你的访问路径中不包含反向代理或路径不同, 请酌情删除或修改 domain: "https://www.wangyunf.com/blall/pic/" # 请以 / 开头, / 结尾, 简短的路径在文章中有更好的显示效果, 过长一定程度会使文章内容混乱 - default-path: "/home/blall/img/" - # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ 全文搜索 ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - search: - path: "/home/index/" \ No newline at end of file + default-path: "/home/blall/img/" \ No newline at end of file diff --git a/blossom-backend/backend/src/main/resources/mapper/ArticleMapper.xml b/blossom-backend/backend/src/main/resources/mapper/ArticleMapper.xml index 23d5aaa..04253cf 100644 --- a/blossom-backend/backend/src/main/resources/mapper/ArticleMapper.xml +++ b/blossom-backend/backend/src/main/resources/mapper/ArticleMapper.xml @@ -49,7 +49,7 @@ - select id, `name`, diff --git a/blossom-backend/backend/src/main/resources/schema-mysql.sql b/blossom-backend/backend/src/main/resources/schema-mysql.sql index da12994..f6785e1 100644 --- a/blossom-backend/backend/src/main/resources/schema-mysql.sql +++ b/blossom-backend/backend/src/main/resources/schema-mysql.sql @@ -391,8 +391,10 @@ CREATE TABLE IF NOT EXISTS `blossom_article_reference` ROW_FORMAT = DYNAMIC; -- ---------------------------- --- Records of blossom_article_reference +-- since: 1.12.0 -- ---------------------------- +alter table blossom_article_reference + modify target_url varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL comment '链接地址'; -- ---------------------------- -- Table structure for blossom_article_view diff --git a/blossom-editor/src/renderer/src/api/blossom.ts b/blossom-editor/src/renderer/src/api/blossom.ts index 6c7af4c..0fede7b 100644 --- a/blossom-editor/src/renderer/src/api/blossom.ts +++ b/blossom-editor/src/renderer/src/api/blossom.ts @@ -472,6 +472,13 @@ export const articleRecycleListApi = (): Promise => { export const articleRecycleRestoreApi = (data?: object): Promise => { return rq.post('/article/recycle/restore', data) } + +/** + * 文章全文搜索 + */ +export const articleSearchApi = (params?: object): Promise => { + return rq.get('/search', { params }) +} //#endregion //#region ====================================================< picture >=================================================== diff --git a/blossom-editor/src/renderer/src/assets/constants/system.ts b/blossom-editor/src/renderer/src/assets/constants/system.ts index d557881..1475c10 100644 --- a/blossom-editor/src/renderer/src/assets/constants/system.ts +++ b/blossom-editor/src/renderer/src/assets/constants/system.ts @@ -9,7 +9,8 @@ const blossom = { // DOC: 'https://www.wangyunf.com/blossom-doc/index', - CONTACT: 'https://www.wangyunf.com/blossom-doc/guide/about/all.html', + CONTACT: 'https://www.wangyunf.com/blossom-doc/guide/about/contact.html', + SPONSOR: 'https://www.wangyunf.com/blossom-doc/guide/about/sponsor.html', GITHUB_REPO: 'https://github.com/blossom-editor/blossom', GITHUB_RELEASE: 'https://github.com/blossom-editor/blossom/releases', GITEE_REPO: 'https://gitee.com/blossom-editor/blossom' diff --git a/blossom-editor/src/renderer/src/assets/css/main.css b/blossom-editor/src/renderer/src/assets/css/main.css index 0cd5f08..4d57f8c 100644 --- a/blossom-editor/src/renderer/src/assets/css/main.css +++ b/blossom-editor/src/renderer/src/assets/css/main.css @@ -45,7 +45,6 @@ img { ::-webkit-scrollbar-thumb { border-radius: 2px; background-color: var(--bl-scrollbar-color); - transition: 0.3s; } ::-webkit-scrollbar-thumb:hover { diff --git a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.css b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.css index beec7c3..5561320 100644 --- a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.css +++ b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconbl"; /* Project id 4118609 */ - src: url('iconfont.woff2?t=1703588816023') format('woff2'), - url('iconfont.woff?t=1703588816023') format('woff'), - url('iconfont.ttf?t=1703588816023') format('truetype'); + src: url('iconfont.woff2?t=1704477350520') format('woff2'), + url('iconfont.woff?t=1704477350520') format('woff'), + url('iconfont.ttf?t=1704477350520') format('truetype'); } .iconbl { @@ -13,6 +13,18 @@ -moz-osx-font-smoothing: grayscale; } +.bl-and:before { + content: "\e71b"; +} + +.bl-wuneirong:before { + content: "\e6a7"; +} + +.bl-enter:before { + content: "\e65a"; +} + .bl-blog:before { content: "\e7c6"; } diff --git a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.js b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.js index 9d7f15d..0cfc33b 100644 --- a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.js +++ b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4118609='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1],l=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var v,m,z,i,o,t=function(a,l){l.parentNode.insertBefore(a,l)};if(l&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}v=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_4118609,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?t(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(v,0):(m=function(){document.removeEventListener("DOMContentLoaded",m,!1),v()},document.addEventListener("DOMContentLoaded",m,!1)):document.attachEvent&&(z=v,i=h.document,o=!1,b(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){o||(o=!0,z())}function b(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(b,50)}e()}}(window); \ No newline at end of file +window._iconfont_svg_string_4118609='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1],l=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var v,m,z,i,o,t=function(a,l){l.parentNode.insertBefore(a,l)};if(l&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}v=function(){var a,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_4118609,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?t(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(v,0):(m=function(){document.removeEventListener("DOMContentLoaded",m,!1),v()},document.addEventListener("DOMContentLoaded",m,!1)):document.attachEvent&&(z=v,i=h.document,o=!1,b(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,e())})}function e(){o||(o=!0,z())}function b(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(b,50)}e()}}(window); \ No newline at end of file diff --git a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.json b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.json index 68bfb54..4f598f6 100644 --- a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.json +++ b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.json @@ -5,6 +5,27 @@ "css_prefix_text": "bl-", "description": "", "glyphs": [ + { + "icon_id": "8990451", + "name": "相交", + "font_class": "and", + "unicode": "e71b", + "unicode_decimal": 59163 + }, + { + "icon_id": "17525623", + "name": "无内容", + "font_class": "wuneirong", + "unicode": "e6a7", + "unicode_decimal": 59047 + }, + { + "icon_id": "12687054", + "name": "回车", + "font_class": "enter", + "unicode": "e65a", + "unicode_decimal": 58970 + }, { "icon_id": "12579639", "name": "blog", diff --git a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.ttf b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.ttf index 935abe0..83d722a 100644 Binary files a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.ttf and b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.ttf differ diff --git a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff index 51d5014..88ba767 100644 Binary files a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff and b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff differ diff --git a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff2 b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff2 index ec77cef..6bb9e6d 100644 Binary files a/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff2 and b/blossom-editor/src/renderer/src/assets/iconfont/blossom/iconfont.woff2 differ diff --git a/blossom-editor/src/renderer/src/assets/styles/bl-common.scss b/blossom-editor/src/renderer/src/assets/styles/bl-common.scss index f28fa43..de0d01f 100644 --- a/blossom-editor/src/renderer/src/assets/styles/bl-common.scss +++ b/blossom-editor/src/renderer/src/assets/styles/bl-common.scss @@ -10,16 +10,35 @@ text-align: center; box-shadow: inset 0 0 20px white, - 0 5px 0 #b1b1b1, - 0 6px 0 1px #7e7e7e, - 0 8px 5px #a5a5a5; + 0 3px 0 #b1b1b1, + 0 4px 0 1px #7e7e7e, + 0 7px 5px #a5a5a5; [class='dark'] & { box-shadow: inset 0 0 20px #000000, - 0 5px 0 #272727, - 0 6px 0 2px #121212, - 0 8px 5px #252525; + 0 3px 0 #272727, + 0 4px 0 1px #121212, + 0 7px 5px #252525; + } +} + +.keyboard.small { + padding: 0 3px; + font-size: 10px; + font-weight: 300; + box-shadow: + inset 0 0 20px white, + 0 2px 0 #b1b1b1, + 0 2px 0 1px #7e7e7e, + 0 5px 4px #a5a5a5; + + [class='dark'] & { + box-shadow: + inset 0 0 20px #000000, + 0 2px 0 #272727, + 0 3px 0 1px #121212, + 0 5px 4px #252525; } } diff --git a/blossom-editor/src/renderer/src/assets/styles/bl-dialog-info.scss b/blossom-editor/src/renderer/src/assets/styles/bl-dialog-info.scss index 757aabc..99cfb27 100644 --- a/blossom-editor/src/renderer/src/assets/styles/bl-dialog-info.scss +++ b/blossom-editor/src/renderer/src/assets/styles/bl-dialog-info.scss @@ -10,6 +10,7 @@ $height-form: calc(100% - #{$height-title} - #{$height-footer}); padding-left: 10px; color: var(--bl-text-title-color); text-shadow: var(--bl-text-shadow); + padding-bottom: 8px; .iconbl { font-size: 25px; diff --git a/blossom-editor/src/renderer/src/assets/styles/bl-dialog.scss b/blossom-editor/src/renderer/src/assets/styles/bl-dialog.scss index 9833977..9e3bc56 100644 --- a/blossom-editor/src/renderer/src/assets/styles/bl-dialog.scss +++ b/blossom-editor/src/renderer/src/assets/styles/bl-dialog.scss @@ -3,5 +3,43 @@ --el-dialog-padding-primary: 0 !important; --el-dialog-bg-color: var(--bl-dialog-bg-color) !important; --el-dialog-box-shadow: var(--bl-dialog-box-shadow) !important; - +} + +// 更大的 header close 按钮 +.bl-dialog-bigger-headerbtn { + .el-dialog__headerbtn { + height: 30px; + width: 30px; + font-size: 20px; + } +} + +// 无 header +.bl-dialog-hidden-header { + .el-dialog__header { + display: none !important; + } + .el-dialog__headerbtn { + display: none; + } +} + +// 固定 body 长度 +.bl-dialog-fixed-body { + .el-dialog__body { + height: calc(100% - 10px); + } +} + +// 无 header 且固定 body 长度 +.bl-dialog-hidden-header-fixed-body { + .el-dialog__header { + display: none !important; + } + .el-dialog__headerbtn { + display: none; + } + .el-dialog__body { + height: 100%; + } } diff --git a/blossom-editor/src/renderer/src/assets/styles/bl-notification.scss b/blossom-editor/src/renderer/src/assets/styles/bl-notification.scss index 740d52f..dc764e1 100644 --- a/blossom-editor/src/renderer/src/assets/styles/bl-notification.scss +++ b/blossom-editor/src/renderer/src/assets/styles/bl-notification.scss @@ -1,6 +1,5 @@ .el-notification { word-wrap: break-word; word-break: break-all; - border-radius: 2px !important; border: 2px solid var(--el-color-primary-light-5) !important; } \ No newline at end of file diff --git a/blossom-editor/src/renderer/src/assets/styles/bl-tooltip.scss b/blossom-editor/src/renderer/src/assets/styles/bl-tooltip.scss index 96c2363..f3a53a2 100644 --- a/blossom-editor/src/renderer/src/assets/styles/bl-tooltip.scss +++ b/blossom-editor/src/renderer/src/assets/styles/bl-tooltip.scss @@ -37,10 +37,6 @@ .el-popper.is-light { @include themeShadow(1px 3px 10px #dedede, 1px 3px 10px #000000); color: var(--bl-text-doctree-color); - - .keyboard { - margin-top: 5px; - } } .el-popper.el-picker__popper { diff --git a/blossom-editor/src/renderer/src/assets/utils/color.ts b/blossom-editor/src/renderer/src/assets/utils/color.ts new file mode 100644 index 0000000..31800e3 --- /dev/null +++ b/blossom-editor/src/renderer/src/assets/utils/color.ts @@ -0,0 +1,144 @@ +export interface IColorObj { + r: number + g: number + b: number + a?: number +} + +/** + * 255颜色值转16进制颜色值 + * @param n 255颜色值 + * @returns hex 16进制颜色值 + */ +export const toHex = (n: number) => `${n > 15 ? '' : 0}${n.toString(16)}` + +/** + * 颜色对象转化为16进制颜色字符串 + * @param colorObj 颜色对象 + */ +export const toHexString = (colorObj: IColorObj) => { + const { r, g, b, a = 1 } = colorObj + return `#${toHex(r)}${toHex(g)}${toHex(b)}${a === 1 ? '' : toHex(Math.floor(a * 255))}` +} + +/** + * 颜色对象转化为rgb颜色字符串 + * @param colorObj 颜色对象 + */ +export const toRgbString = (colorObj: IColorObj) => { + const { r, g, b } = colorObj + return `rgb(${r},${g},${b})` +} + +/** + * 颜色对象转化为rgba颜色字符串 + * @param colorObj 颜色对象 + */ +export const toRgbaString = (colorObj: IColorObj, n = 10000) => { + const { r, g, b, a = 1 } = colorObj + return `rgba(${r},${g},${b},${Math.floor(a * n) / n})` +} + +/** + * 16进制颜色字符串解析为颜色对象 + * @param color 颜色字符串 + * @returns IColorObj + */ +export const parseHexColor = (color: string) => { + let hex = color.slice(1) + let a = 1 + if (hex.length === 3) { + hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}` + } + if (hex.length === 8) { + a = parseInt(hex.slice(6), 16) / 255 + hex = hex.slice(0, 6) + } + const bigint = parseInt(hex, 16) + return { + r: (bigint >> 16) & 255, + g: (bigint >> 8) & 255, + b: bigint & 255, + a + } as IColorObj +} + +/** + * rgba颜色字符串解析为颜色对象 + * @param color 颜色字符串 + * @returns IColorObj + */ +export const parseRgbaColor = (color: string) => { + const arr = color.match(/(\d(\.\d+)?)+/g) || [] + const res = arr.map((s: string) => parseInt(s, 10)) + return { + r: res[0], + g: res[1], + b: res[2], + a: parseFloat(arr[3]) || 1 + } as IColorObj +} + +/** + * 颜色字符串解析为颜色对象 + * @param color 颜色字符串 + * @returns IColorObj + */ +export const parseColorString = (color: string) => { + if (color.startsWith('#')) { + return parseHexColor(color) + } else if (color.startsWith('rgb')) { + return parseRgbaColor(color) + } else if (color === 'transparent') { + return parseHexColor('#00000000') + } + throw new Error(`color string error: ${color}`) +} + +/** + * 颜色字符串解析为各种颜色表达方式 + * @param color 颜色字符串 + * @returns IColorObj + */ +export const getColorInfo = (color: string) => { + const colorObj = parseColorString(color) + const hex = toHexString(colorObj) + const rgba = toRgbaString(colorObj) + const rgb = toRgbString(colorObj) + return { + hex, + rgba, + rgb, + rgbaObj: colorObj + } +} + +/** + * 16进制颜色字符串转化为rgba颜色字符串 + * @param hex 16进制颜色字符串 + * @returns rgba颜色字符串 + */ +export const hexToRgba = (hex: string) => { + const colorObj = parseColorString(hex) + return toRgbaString(colorObj) +} + +/** + * rgb颜色字符串转化为16进制颜色字符串 + * @param rgb rgb颜色字符串 + * @returns 16进制颜色字符串 + */ +export const rgbToHex = (rgb: string) => { + const colorObj = parseColorString(rgb) + return toHexString(colorObj) +} + +/** + * rgba颜色字符串转化为16进制颜色字符串 + * @param rgba rgba颜色字符串 + * @returns 16进制颜色字符串 + */ +export const rgbaToHex = (rgba: string) => { + const colorObj = parseColorString(rgba) + return toHexString(colorObj) +} diff --git a/blossom-editor/src/renderer/src/assets/utils/util.ts b/blossom-editor/src/renderer/src/assets/utils/util.ts index c6b094a..86ad07c 100644 --- a/blossom-editor/src/renderer/src/assets/utils/util.ts +++ b/blossom-editor/src/renderer/src/assets/utils/util.ts @@ -152,7 +152,7 @@ export const timestampToDatetime = (timestamp: number | string | Date): string = } /** - * 两个日期相差的条数 + * 两个日期相差的天数 * * @param date1 yyyy-MM-dd * @param date2 yyyy-MM-dd diff --git a/blossom-editor/src/renderer/src/components/AppHeader.vue b/blossom-editor/src/renderer/src/components/AppHeader.vue index ee25200..a3364aa 100644 --- a/blossom-editor/src/renderer/src/components/AppHeader.vue +++ b/blossom-editor/src/renderer/src/components/AppHeader.vue @@ -64,7 +64,7 @@ { diff --git a/blossom-editor/src/renderer/src/views/article/ArticleTreeDocs.vue b/blossom-editor/src/renderer/src/views/article/ArticleTreeDocs.vue index c1002e5..ba9e6e4 100644 --- a/blossom-editor/src/renderer/src/views/article/ArticleTreeDocs.vue +++ b/blossom-editor/src/renderer/src/views/article/ArticleTreeDocs.vue @@ -1,7 +1,12 @@ @@ -270,11 +314,3 @@ defineExpose({ handleShowBackupDialog }) transition: all 0.2s ease; } - - diff --git a/blossom-editor/src/renderer/src/views/article/EditorTools.vue b/blossom-editor/src/renderer/src/views/article/EditorTools.vue index 744fd12..e6eccb9 100644 --- a/blossom-editor/src/renderer/src/views/article/EditorTools.vue +++ b/blossom-editor/src/renderer/src/views/article/EditorTools.vue @@ -47,235 +47,7 @@
- - -
-
+
@@ -303,13 +75,25 @@
{{ remainStr }}
+ + + + + diff --git a/blossom-editor/src/renderer/src/views/article/scripts/editor-tools.ts b/blossom-editor/src/renderer/src/views/article/scripts/editor-tools.ts index 377ec8c..0d0551b 100644 --- a/blossom-editor/src/renderer/src/views/article/scripts/editor-tools.ts +++ b/blossom-editor/src/renderer/src/views/article/scripts/editor-tools.ts @@ -9,6 +9,8 @@ export const keymaps = { fullViewer: isMac ? 'Cmd + 3' : 'Alt + 3', fullEditor: isMac ? 'Cmd + 4' : 'Alt + 4', formatAll: isMac ? 'Slift + Cmd + F' : 'Slift + Alt + F', + fullSearch: isMac ? 'Ctrl + Shift + F' : 'Ctrl + Shift + F', + fullSearchOperatorAnd: isMac ? 'Cmd + G' : 'Alt + G', blod: isMac ? 'Cmd + B' : 'Alt + B', italic: isMac ? 'Cmd + I' : 'Alt + I', diff --git a/blossom-editor/src/renderer/src/views/article/styles/article-index.scss b/blossom-editor/src/renderer/src/views/article/styles/article-index.scss index 2d6b6be..acbef3d 100644 --- a/blossom-editor/src/renderer/src/views/article/styles/article-index.scss +++ b/blossom-editor/src/renderer/src/views/article/styles/article-index.scss @@ -77,7 +77,7 @@ .ep-placeholder { position: absolute; - z-index: 2001; + z-index: 1999; height: 100%; width: 100%; background-color: var(--bl-html-color); diff --git a/blossom-editor/src/renderer/src/views/doc/doc.ts b/blossom-editor/src/renderer/src/views/doc/doc.ts index 2c68184..8ced3cb 100644 --- a/blossom-editor/src/renderer/src/views/doc/doc.ts +++ b/blossom-editor/src/renderer/src/views/doc/doc.ts @@ -29,7 +29,7 @@ export const treeToInfo = (tree: DocTree): DocInfo => { } } -export enum TitleColor { +export enum SortLevelColor { ONE = '#C9515193', TWO = '#E6981293', THREE = '#127EA993', @@ -43,15 +43,15 @@ export enum TitleColor { */ export const computedDocTitleColor = (level: number) => { if (level === 1) { - return TitleColor.ONE + return SortLevelColor.ONE } if (level === 2) { - return TitleColor.TWO + return SortLevelColor.TWO } if (level === 3) { - return TitleColor.THREE + return SortLevelColor.THREE } - return TitleColor.FOUR + return SortLevelColor.FOUR } /** diff --git a/blossom-editor/src/renderer/src/views/doc/tree-workbench.scss b/blossom-editor/src/renderer/src/views/doc/tree-workbench.scss index 2218834..55f1f1c 100644 --- a/blossom-editor/src/renderer/src/views/doc/tree-workbench.scss +++ b/blossom-editor/src/renderer/src/views/doc/tree-workbench.scss @@ -45,7 +45,9 @@ text-shadow: var(--bl-text-shadow); font-size: 25px; padding: 3px 2px; - transition: 0.3s; + transition: + color 0.3s, + text-shadow 0.3s; cursor: pointer; &:hover { @@ -58,50 +60,55 @@ padding-right: 0px; } + .bl-search-line { + font-size: 22px; + padding-bottom: 5px; + } + // 公开图标 - .bl-cloud-fill { - color: #7ac20c; - @include themeText(0px 5px 5px #79c20ca4, 2px 3px 5px rgba(183, 183, 183, 0.8)); - } + // .bl-cloud-fill { + // color: #7ac20c; + // @include themeText(0px 5px 5px #79c20ca4, 2px 3px 5px rgba(183, 183, 183, 0.8)); + // } - .bl-cloud-fill, - .bl-cloud-line { - &:hover { - color: #7ac20c; - @include themeText(5px 5px 5px #79c20ca4, 2px 3px 5px rgba(183, 183, 183, 0.8)); - } - } + // .bl-cloud-fill, + // .bl-cloud-line { + // &:hover { + // color: #7ac20c; + // @include themeText(5px 5px 5px #79c20ca4, 2px 3px 5px rgba(183, 183, 183, 0.8)); + // } + // } // 专题图标 - .bl-a-lowerrightpage-fill { - color: salmon; - @include themeText(0px 5px 5px rgba(250, 128, 114, 0.546), 2px 3px 5px rgba(183, 183, 183, 0.8)); - } + // .bl-a-lowerrightpage-fill { + // color: salmon; + // @include themeText(0px 5px 5px rgba(250, 128, 114, 0.546), 2px 3px 5px rgba(183, 183, 183, 0.8)); + // } // 专题图标 - .bl-a-lowerrightpage-fill, - .bl-a-lowerrightpage-line { - &:hover { - color: salmon; - @include themeText(5px 5px 5px rgba(250, 128, 114, 0.546), 2px 3px 5px rgba(183, 183, 183, 0.8)); - } - } + // .bl-a-lowerrightpage-fill, + // .bl-a-lowerrightpage-line { + // &:hover { + // color: salmon; + // @include themeText(5px 5px 5px rgba(250, 128, 114, 0.546), 2px 3px 5px rgba(183, 183, 183, 0.8)); + // } + // } // star 图标 - .bl-star-fill { - color: rgb(237, 204, 11); - @include themeText(0px 5px 5px rgba(237, 204, 11, 0.546), 2px 3px 5px rgba(183, 183, 183, 0.8)); - } + // .bl-star-fill { + // color: rgb(237, 204, 11); + // @include themeText(0px 5px 5px rgba(237, 204, 11, 0.546), 2px 3px 5px rgba(183, 183, 183, 0.8)); + // } .bl-star-line, .bl-star-fill { font-size: 23px; padding-bottom: 5px; - &:hover { - color: rgb(237, 204, 11); - @include themeText(5px 5px 5px rgba(237, 203, 11, 0.756), 2px 3px 5px rgba(183, 183, 183, 0.8)); - } + // &:hover { + // color: rgb(237, 204, 11); + // @include themeText(5px 5px 5px rgba(237, 203, 11, 0.756), 2px 3px 5px rgba(183, 183, 183, 0.8)); + // } } // 刷新图标 diff --git a/blossom-editor/src/renderer/src/views/home/Home.vue b/blossom-editor/src/renderer/src/views/home/Home.vue index d3031d3..96844aa 100644 --- a/blossom-editor/src/renderer/src/views/home/Home.vue +++ b/blossom-editor/src/renderer/src/views/home/Home.vue @@ -14,10 +14,10 @@ - +
- 字数统计 + 字数统计 diff --git a/blossom-editor/src/renderer/src/views/index/SettingAbout.vue b/blossom-editor/src/renderer/src/views/index/SettingAbout.vue index b639016..46d363d 100644 --- a/blossom-editor/src/renderer/src/views/index/SettingAbout.vue +++ b/blossom-editor/src/renderer/src/views/index/SettingAbout.vue @@ -1,104 +1,109 @@