mirror of
https://gitee.com/blossom-editor/blossom.git
synced 2025-12-06 08:48:29 +08:00
Merge branch 'blossom-editor:dev' into dev
This commit is contained in:
commit
c9c68cd0b2
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>blossom-backend</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -39,10 +39,57 @@ public enum UserParamEnum {
|
||||
*/
|
||||
WEB_BLOG_LINKS(false, 0, ""),
|
||||
/**
|
||||
* 博客端专题特殊形式, 0:false;1:是
|
||||
* 博客端专题特殊形式
|
||||
* 0:否;1:是
|
||||
*
|
||||
* @since 1.13.0
|
||||
*/
|
||||
WEB_BLOG_SUBJECT_TITLE(false, 0, "0"),
|
||||
/**
|
||||
* 是否在文章内容的顶部显示文章的标题
|
||||
* 0:否;1:是
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_SHOW_ARTICLE_NAME(false, 0, "1"),
|
||||
/**
|
||||
* 博客主题色
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_COLOR(false, 0, "rgb(104, 104, 104)"),
|
||||
// ----------< 博客水印 >----------
|
||||
/**
|
||||
* 启用博客水印
|
||||
* 0:否;1:是
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_WATERMARK_ENABLED(false, 0, "0"),
|
||||
/**
|
||||
* 水印内容
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_WATERMARK_CONTENT(false, 0, ""),
|
||||
/**
|
||||
* 水印字体大小
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_WATERMARK_FONTSIZE(false, 0, "15"),
|
||||
/**
|
||||
* 水印颜色
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_WATERMARK_COLOR(false, 0, "rgba(157, 157, 157, 0.2)"),
|
||||
/**
|
||||
* 水印密集度
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
WEB_BLOG_WATERMARK_GAP(false, 0, "100"),
|
||||
;
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.blossom.backend.base.paramu.pojo;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 修改参数
|
||||
@ -21,6 +22,7 @@ public class UserParamUpdReq {
|
||||
/**
|
||||
* 参数值
|
||||
*/
|
||||
@NotNull(message = "参数值为必填项")
|
||||
private String paramValue;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.blossom.backend.base.search.message.consumer;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.blossom.backend.base.search.SearchProperties;
|
||||
import com.blossom.backend.base.search.message.IndexMsg;
|
||||
import com.blossom.backend.base.search.message.IndexMsgTypeEnum;
|
||||
@ -51,7 +50,6 @@ public class IndexMsgConsumer {
|
||||
final Long userId = indexMsg.getUserId();
|
||||
final Long id = indexMsg.getId();
|
||||
if (userId == null || id == null) {
|
||||
log.error("消费异常. 获取用户id为空");
|
||||
continue;
|
||||
}
|
||||
if (IndexMsgTypeEnum.ADD == indexMsg.getType()) {
|
||||
@ -61,12 +59,15 @@ public class IndexMsgConsumer {
|
||||
IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) {
|
||||
// 查询最新的消息
|
||||
ArticleEntity article = this.articleService.selectById(id, false, true, false, userId);
|
||||
if (null == article) {
|
||||
continue;
|
||||
}
|
||||
Document document = new Document();
|
||||
document.add(new StringField("id", Convert.toStr(id), Field.Store.YES));
|
||||
document.add(new StringField("id", String.valueOf(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.updateDocument(new Term("id", String.valueOf(id)), document);
|
||||
indexWriter.flush();
|
||||
indexWriter.commit();
|
||||
}
|
||||
@ -74,7 +75,7 @@ public class IndexMsgConsumer {
|
||||
// 删除索引
|
||||
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.deleteDocuments(new Term("id", String.valueOf(id)));
|
||||
indexWriter.flush();
|
||||
indexWriter.commit();
|
||||
}
|
||||
|
||||
@ -1,11 +1,33 @@
|
||||
package com.blossom.backend.config;
|
||||
|
||||
import com.blossom.backend.server.folder.pojo.FolderEntity;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
|
||||
Set<FolderEntity> set = new HashSet<>();
|
||||
|
||||
FolderEntity f1 = new FolderEntity();
|
||||
f1.setId(1L);
|
||||
f1.setName("1");
|
||||
|
||||
|
||||
FolderEntity f2 = new FolderEntity();
|
||||
f2.setId(1L);
|
||||
f2.setName("2");
|
||||
|
||||
set.add(f1);
|
||||
set.add(f2);
|
||||
|
||||
System.out.println(set.size());
|
||||
System.out.println(Arrays.toString(set.toArray()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,21 +1,19 @@
|
||||
package com.blossom.backend.server.article.backup;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.blossom.backend.base.auth.AuthContext;
|
||||
import com.blossom.backend.base.auth.annotation.AuthIgnore;
|
||||
import com.blossom.backend.base.param.ParamEnum;
|
||||
import com.blossom.backend.base.param.ParamService;
|
||||
import com.blossom.backend.base.param.pojo.ParamEntity;
|
||||
import com.blossom.backend.server.article.backup.pojo.BackupFile;
|
||||
import com.blossom.backend.server.article.backup.pojo.DownloadReq;
|
||||
import com.blossom.backend.server.utils.DownloadUtil;
|
||||
import com.blossom.common.base.enums.YesNo;
|
||||
import com.blossom.common.base.exception.XzException404;
|
||||
import com.blossom.common.base.exception.XzException500;
|
||||
import com.blossom.common.base.pojo.R;
|
||||
import com.blossom.common.base.util.SortUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.PathResource;
|
||||
import org.springframework.core.io.support.ResourceRegion;
|
||||
import org.springframework.http.*;
|
||||
@ -23,10 +21,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -36,6 +31,7 @@ import java.util.stream.Collectors;
|
||||
* @author xzzz
|
||||
* @order 8
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/article/backup")
|
||||
@ -52,7 +48,7 @@ public class ArticleBackupController {
|
||||
* @param articleId 备份指定的文章
|
||||
*/
|
||||
@GetMapping
|
||||
public R<ArticleBackupService.BackupFile> backup(
|
||||
public R<BackupFile> backup(
|
||||
@RequestParam("type") String type,
|
||||
@RequestParam("toLocal") String toLocal,
|
||||
@RequestParam(value = "articleId", required = false) Long articleId) {
|
||||
@ -77,7 +73,7 @@ public class ArticleBackupController {
|
||||
* 备份记录
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public R<List<ArticleBackupService.BackupFile>> list() {
|
||||
public R<List<BackupFile>> list() {
|
||||
return R.ok(backupService.listAll(AuthContext.getUserId())
|
||||
.stream()
|
||||
.sorted((b1, b2) -> SortUtil.dateSort.compare(b1.getDatetime(), b2.getDatetime()))
|
||||
@ -89,9 +85,12 @@ public class ArticleBackupController {
|
||||
* 下载压缩包
|
||||
*
|
||||
* @param filename 文件名称
|
||||
* @deprecated 1.14.0
|
||||
*/
|
||||
@GetMapping("/download")
|
||||
@Deprecated
|
||||
public void download(@RequestParam("filename") String filename, HttpServletResponse response) {
|
||||
/*
|
||||
final ParamEntity param = paramService.getValue(ParamEnum.BACKUP_PATH);
|
||||
XzException500.throwBy(ObjUtil.isNull(param), ArticleBackupService.ERROR_MSG);
|
||||
final String rootPath = param.getParamValue();
|
||||
@ -103,6 +102,7 @@ public class ArticleBackupController {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +112,6 @@ public class ArticleBackupController {
|
||||
* @param request request
|
||||
* @apiNote 返回类 ResponseEntity<ResourceRegion>
|
||||
*/
|
||||
@AuthIgnore
|
||||
@GetMapping("/download/fragment")
|
||||
public ResponseEntity<ResourceRegion> downloadFragment(@RequestParam("filename") String filename,
|
||||
HttpServletRequest request) {
|
||||
@ -129,15 +128,17 @@ public class ArticleBackupController {
|
||||
* @apiNote 返回类 ResponseEntity<ResourceRegion>
|
||||
* @apiNote 通过 Range 请求头获取分片请求, 返回头中会比说明本次分片大小 Content-Range
|
||||
*/
|
||||
@AuthIgnore
|
||||
@PostMapping("/download/fragment")
|
||||
public ResponseEntity<ResourceRegion> downloadFragment(@RequestBody DownloadReq req,
|
||||
HttpServletRequest request) {
|
||||
final ParamEntity param = paramService.getValue(ParamEnum.BACKUP_PATH);
|
||||
XzException500.throwBy(ObjUtil.isNull(param), ArticleBackupService.ERROR_MSG);
|
||||
final String rootPath = param.getParamValue();
|
||||
XzException500.throwBy(StrUtil.isBlank(rootPath), ArticleBackupService.ERROR_MSG);
|
||||
String filename = rootPath + "/" + req.getFilename();
|
||||
// 检查文件名
|
||||
if (!checkFilename(req.getFilename())) {
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.body(new ResourceRegion(new PathResource(""), 0, 0));
|
||||
}
|
||||
// 拼接文件名
|
||||
String filename = getRootPath() + "/" + req.getFilename();
|
||||
File file = new File(filename);
|
||||
long contentLength = file.length();
|
||||
|
||||
@ -167,12 +168,43 @@ public class ArticleBackupController {
|
||||
return ResponseEntity
|
||||
.status(HttpStatus.OK)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
|
||||
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(resourceRegion.getCount()))
|
||||
.header(HttpHeaders.ACCEPT_RANGES, "bytes")
|
||||
.header(HttpHeaders.CONTENT_RANGE, contentRange)
|
||||
.body(resourceRegion);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备份根目录
|
||||
*/
|
||||
private String getRootPath() {
|
||||
final ParamEntity param = paramService.getValue(ParamEnum.BACKUP_PATH);
|
||||
XzException500.throwBy(ObjUtil.isNull(param), ArticleBackupService.ERROR_MSG);
|
||||
final String rootPath = param.getParamValue();
|
||||
XzException500.throwBy(StrUtil.isBlank(rootPath), ArticleBackupService.ERROR_MSG);
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化文件名, 不能包含 / 进行隐性的路径切换
|
||||
*/
|
||||
private boolean checkFilename(String filename) {
|
||||
// 不能包含 /
|
||||
if (filename.contains("/")) {
|
||||
return false;
|
||||
}
|
||||
if (filename.contains("%2f")) {
|
||||
return false;
|
||||
}
|
||||
if (filename.contains("%2F")) {
|
||||
return false;
|
||||
}
|
||||
BackupFile backupFile = new BackupFile();
|
||||
backupFile.build(filename);
|
||||
if (!backupFile.checkPrefix()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package com.blossom.backend.server.article.backup;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -12,6 +11,7 @@ import com.blossom.backend.base.param.ParamService;
|
||||
import com.blossom.backend.base.param.pojo.ParamEntity;
|
||||
import com.blossom.backend.base.user.UserService;
|
||||
import com.blossom.backend.base.user.pojo.UserEntity;
|
||||
import com.blossom.backend.server.article.backup.pojo.BackupFile;
|
||||
import com.blossom.backend.server.article.draft.ArticleService;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleEntity;
|
||||
import com.blossom.backend.server.article.reference.ArticleReferenceService;
|
||||
@ -29,8 +29,6 @@ import com.blossom.common.base.util.DateUtils;
|
||||
import com.blossom.common.base.util.PrimaryKeyUtil;
|
||||
import com.blossom.common.base.util.SortUtil;
|
||||
import com.blossom.common.iaas.IaasProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@ -72,7 +70,6 @@ public class ArticleBackupService {
|
||||
private Executor executor;
|
||||
|
||||
public static final String ERROR_MSG = String.format("[文章备份] 备份失败, 未配置备份路径 [%s]", ParamEnum.BACKUP_PATH.name());
|
||||
private static final String SEPARATOR = "_";
|
||||
|
||||
/**
|
||||
* 查看记录
|
||||
@ -212,6 +209,7 @@ public class ArticleBackupService {
|
||||
if (toLocal == YesNo.YES) {
|
||||
backLogs.add("");
|
||||
if (articleId != null) {
|
||||
// 查询文章引用的图片
|
||||
List<ArticleReferenceEntity> refs = referenceService.listPics(articleId);
|
||||
PictureEntity where = new PictureEntity();
|
||||
where.setUrls(refs.stream().map(ArticleReferenceEntity::getTargetUrl).collect(Collectors.toList()));
|
||||
@ -219,11 +217,16 @@ public class ArticleBackupService {
|
||||
backLogs.add("[图片备份] 图片个数: " + pics.size());
|
||||
backLogs.add("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ↓↓ 图片列表 ↓↓ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
for (PictureEntity pic : pics) {
|
||||
backLogs.add("┃ " + pic.getPathName());
|
||||
try {
|
||||
FileUtil.copy(
|
||||
pic.getPathName(),
|
||||
backupFile.getRootPath() + "/" + pic.getPathName(),
|
||||
true);
|
||||
backLogs.add("┃ " + pic.getPathName());
|
||||
} catch (Exception e) {
|
||||
backLogs.add("┃ [警告] " + pic.getPathName() + " 未在存储路径中找到");
|
||||
log.warn("{} 未在存储路径中找到", pic.getPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 备份全部图片
|
||||
@ -381,7 +384,7 @@ public class ArticleBackupService {
|
||||
}
|
||||
|
||||
List<ArticleReferenceEntity> refs = referenceService.listPics(articleId);
|
||||
final String domain = iaasProperties.getBlos().getDomain();
|
||||
final String domain = paramService.getDomain();
|
||||
|
||||
// 计算字符出现的次数
|
||||
int separatorCount = countChar(articleName, '/');
|
||||
@ -400,12 +403,18 @@ public class ArticleBackupService {
|
||||
}
|
||||
String localPath = parent.substring(0, parent.length() > 0 ? parent.length() - 1 : 0) + ref.getTargetUrl().replace(domain, "");
|
||||
content = content.replaceAll(ref.getTargetUrl(), localPath);
|
||||
System.out.println(localPath);
|
||||
}
|
||||
return content;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定字符在字符串中出现的此处
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param target 查询字符
|
||||
* @return 出现次数
|
||||
*/
|
||||
private static int countChar(String str, char target) {
|
||||
int times = 0;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
@ -443,111 +452,4 @@ public class ArticleBackupService {
|
||||
.replaceAll("\\|", "")
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件
|
||||
*/
|
||||
@Data
|
||||
public static class BackupFile {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
/**
|
||||
* 备份日期 YYYYMMDD
|
||||
*
|
||||
* @mock 20230101
|
||||
*/
|
||||
private String date;
|
||||
/**
|
||||
* 备份时间 HHMMSS
|
||||
*
|
||||
* @mock 123001
|
||||
*/
|
||||
private String time;
|
||||
/**
|
||||
* 备份的日期和时间, yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
private Date datetime;
|
||||
/**
|
||||
* 备份包的名称
|
||||
*/
|
||||
private String filename;
|
||||
/**
|
||||
* 备份包路径
|
||||
*/
|
||||
private String path;
|
||||
/**
|
||||
* 本地文件
|
||||
*/
|
||||
@JsonIgnore
|
||||
private File file;
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
private Long fileLength;
|
||||
|
||||
/**
|
||||
* 通过本地备份文件初始化
|
||||
*
|
||||
* @param file 本地备份文件
|
||||
*/
|
||||
public BackupFile(File file) {
|
||||
build(FileUtil.getPrefix(file.getName()));
|
||||
this.file = file;
|
||||
this.fileLength = file.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定用户的开始备份
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public BackupFile(Long userId, BackupTypeEnum type, YesNo toLocal) {
|
||||
String filename = String.format("%s_%s_%s", buildFilePrefix(type, toLocal), userId, DateUtils.toYMDHMS_SSS(System.currentTimeMillis()));
|
||||
filename = filename.replaceAll(" ", SEPARATOR)
|
||||
.replaceAll("-", "")
|
||||
.replaceAll(":", "")
|
||||
.replaceAll("\\.", SEPARATOR);
|
||||
build(filename);
|
||||
}
|
||||
|
||||
private static String buildFilePrefix(BackupTypeEnum type, YesNo toLocal) {
|
||||
String prefix = "B";
|
||||
if (type == BackupTypeEnum.MARKDOWN) {
|
||||
prefix += "M";
|
||||
} else if (type == BackupTypeEnum.HTML) {
|
||||
prefix += "H";
|
||||
}
|
||||
|
||||
if (toLocal == YesNo.YES) {
|
||||
prefix += "L";
|
||||
} else if (toLocal == YesNo.NO) {
|
||||
prefix += "N";
|
||||
}
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
private void build(String filename) {
|
||||
this.filename = filename;
|
||||
String[] tags = filename.split(SEPARATOR);
|
||||
if (tags.length < 5) {
|
||||
return;
|
||||
}
|
||||
this.userId = tags[1];
|
||||
this.date = tags[2];
|
||||
this.time = tags[3];
|
||||
this.datetime = DateUtil.parse(this.date + this.time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备份文件的路径, 由备份路径 + 本次备份名称构成
|
||||
*/
|
||||
public String getRootPath() {
|
||||
return this.path + "/" + this.filename;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
package com.blossom.backend.server.article.backup.pojo;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.blossom.backend.server.article.backup.BackupTypeEnum;
|
||||
import com.blossom.common.base.enums.YesNo;
|
||||
import com.blossom.common.base.util.DateUtils;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
public class BackupFile {
|
||||
|
||||
private static final String SEPARATOR = "_";
|
||||
private static Set<String> prefixs = new HashSet<String>() {{
|
||||
this.add("BML");
|
||||
this.add("BMN");
|
||||
this.add("BHL");
|
||||
this.add("BHN");
|
||||
}};
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
/**
|
||||
* 备份日期 YYYYMMDD
|
||||
*
|
||||
* @mock 20230101
|
||||
*/
|
||||
private String date;
|
||||
/**
|
||||
* 备份时间 HHMMSS
|
||||
*
|
||||
* @mock 123001
|
||||
*/
|
||||
private String time;
|
||||
/**
|
||||
* 备份的日期和时间, yyyy-MM-dd HH:mm:ss
|
||||
*/
|
||||
private Date datetime;
|
||||
/**
|
||||
* 备份包的名称
|
||||
*/
|
||||
private String filename;
|
||||
/**
|
||||
* 备份包路径
|
||||
*/
|
||||
private String path;
|
||||
/**
|
||||
* 本地文件
|
||||
*/
|
||||
@JsonIgnore
|
||||
private File file;
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
private Long fileLength;
|
||||
|
||||
/**
|
||||
* 通过本地备份文件初始化
|
||||
*
|
||||
* @param file 本地备份文件
|
||||
*/
|
||||
public BackupFile(File file) {
|
||||
build(FileUtil.getPrefix(file.getName()));
|
||||
this.file = file;
|
||||
this.fileLength = file.length();
|
||||
}
|
||||
|
||||
public BackupFile() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定用户的开始备份
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public BackupFile(Long userId, BackupTypeEnum type, YesNo toLocal) {
|
||||
String filename = String.format("%s_%s_%s", buildFilePrefix(type, toLocal), userId, DateUtils.toYMDHMS_SSS(System.currentTimeMillis()));
|
||||
filename = filename.replaceAll(" ", SEPARATOR)
|
||||
.replaceAll("-", "")
|
||||
.replaceAll(":", "")
|
||||
.replaceAll("\\.", SEPARATOR);
|
||||
build(filename);
|
||||
}
|
||||
|
||||
private static String buildFilePrefix(BackupTypeEnum type, YesNo toLocal) {
|
||||
String prefix = "B";
|
||||
if (type == BackupTypeEnum.MARKDOWN) {
|
||||
prefix += "M";
|
||||
} else if (type == BackupTypeEnum.HTML) {
|
||||
prefix += "H";
|
||||
}
|
||||
|
||||
if (toLocal == YesNo.YES) {
|
||||
prefix += "L";
|
||||
} else if (toLocal == YesNo.NO) {
|
||||
prefix += "N";
|
||||
}
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void build(String filename) {
|
||||
this.filename = filename;
|
||||
String[] tags = filename.split(SEPARATOR);
|
||||
if (tags.length < 5) {
|
||||
return;
|
||||
}
|
||||
this.userId = tags[1];
|
||||
this.date = tags[2];
|
||||
this.time = tags[3];
|
||||
this.datetime = DateUtil.parse(this.date + this.time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件格式
|
||||
*/
|
||||
public boolean checkPrefix() {
|
||||
String[] tags = filename.split(SEPARATOR);
|
||||
if (tags.length != 5) {
|
||||
return false;
|
||||
}
|
||||
String prefix = tags[0];
|
||||
// 不是固定文件前缀则失败
|
||||
if (!prefixs.contains(prefix)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备份文件的路径, 由备份路径 + 本次备份名称构成
|
||||
*/
|
||||
public String getRootPath() {
|
||||
return this.path + "/" + this.filename;
|
||||
}
|
||||
|
||||
}
|
||||
@ -12,8 +12,10 @@ import com.blossom.backend.server.article.draft.pojo.*;
|
||||
import com.blossom.backend.server.article.open.ArticleOpenService;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenEntity;
|
||||
import com.blossom.backend.server.doc.DocService;
|
||||
import com.blossom.backend.server.doc.DocSortChecker;
|
||||
import com.blossom.backend.server.doc.DocTypeEnum;
|
||||
import com.blossom.backend.server.folder.FolderService;
|
||||
import com.blossom.backend.server.folder.FolderTypeEnum;
|
||||
import com.blossom.backend.server.folder.pojo.FolderEntity;
|
||||
import com.blossom.backend.server.utils.ArticleUtil;
|
||||
import com.blossom.backend.server.utils.DocUtil;
|
||||
@ -50,13 +52,14 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/article")
|
||||
public class ArticleController {
|
||||
|
||||
private final ArticleService baseService;
|
||||
private final ArticleOpenService openService;
|
||||
private final FolderService folderService;
|
||||
private final UserService userService;
|
||||
private final ArticleTempVisitService tempVisitService;
|
||||
private final DocService docService;
|
||||
private final DocSortChecker docSortChecker;
|
||||
private final ImportManager importManager;
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
@ -122,9 +125,9 @@ public class ArticleController {
|
||||
ArticleEntity article = req.to(ArticleEntity.class);
|
||||
article.setTags(DocUtil.toTagStr(req.getTags()));
|
||||
article.setUserId(AuthContext.getUserId());
|
||||
// 如果新增到顶部, 获取最小的
|
||||
// 如果新增到顶部, 获取最小的排序
|
||||
if (BooleanUtil.isTrue(req.getAddToLast())) {
|
||||
article.setSort(docService.selectMinSortByPid(req.getPid()) + 1);
|
||||
article.setSort(docService.selectMaxSortByPid(req.getPid(), AuthContext.getUserId(), FolderTypeEnum.ARTICLE) + 1);
|
||||
}
|
||||
return R.ok(baseService.insert(article));
|
||||
}
|
||||
@ -136,10 +139,19 @@ public class ArticleController {
|
||||
* @apiNote 该接口只能修改文章的基本信息, 正文及版本修改请使用 "/upd/content" 接口,或者 {@link ArticleService#updateContentById(ArticleEntity)}
|
||||
*/
|
||||
@PostMapping("/upd")
|
||||
public R<Long> insert(@Validated @RequestBody ArticleUpdReq req) {
|
||||
public R<Long> update(@Validated @RequestBody ArticleUpdReq req) {
|
||||
ArticleEntity article = req.to(ArticleEntity.class);
|
||||
article.setTags(DocUtil.toTagStr(req.getTags()));
|
||||
article.setUserId(AuthContext.getUserId());
|
||||
// 检查排序是否重复
|
||||
// if (req.getSort() != null && req.getPid() != null) {
|
||||
// final long newPid = req.getPid();
|
||||
// docSortChecker.checkUnique(CollUtil.newArrayList(newPid),
|
||||
// null,
|
||||
// CollUtil.newArrayList(article),
|
||||
// FolderTypeEnum.ARTICLE,
|
||||
// AuthContext.getUserId());
|
||||
// }
|
||||
return R.ok(baseService.update(article));
|
||||
}
|
||||
|
||||
@ -173,7 +185,7 @@ public class ArticleController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 为文章快速增加/删除标签
|
||||
* 快速增加/删除标签
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
@ -263,11 +275,11 @@ public class ArticleController {
|
||||
* @param pid 上级菜单
|
||||
*/
|
||||
@PostMapping("import")
|
||||
public R<?> upload(@RequestParam("file") MultipartFile file, @RequestParam(value = "pid") Long pid) {
|
||||
public R<?> upload(@RequestParam("file") MultipartFile file, @RequestParam(value = "pid") Long pid, @RequestParam(value = "batchId") String batchId) {
|
||||
try {
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
if (!"txt".equals(suffix) && !"md".equals(suffix)) {
|
||||
throw new XzException404("不支持的文件类型: [" + suffix + "]");
|
||||
throw new XzException400("不支持的文件类型: [" + suffix + "]");
|
||||
}
|
||||
FolderEntity folder = folderService.selectById(pid);
|
||||
XzException404.throwBy(ObjUtil.isNull(folder), "上级文件夹不存在");
|
||||
@ -278,10 +290,12 @@ public class ArticleController {
|
||||
article.setPid(pid);
|
||||
article.setUserId(AuthContext.getUserId());
|
||||
article.setName(FileUtil.getPrefix(file.getOriginalFilename()));
|
||||
article.setWords(ArticleUtil.statWords(content));
|
||||
// article.setWords(ArticleUtil.statWords(content));
|
||||
article.setSort(importManager.getSort(batchId, pid, AuthContext.getUserId()));
|
||||
baseService.insert(article);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new XzException400("上传失败");
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@ -50,6 +50,16 @@ public interface ArticleMapper extends BaseMapper<ArticleEntity> {
|
||||
*/
|
||||
void updContentById(ArticleEntity entity);
|
||||
|
||||
/**
|
||||
* 查询某段时间内编辑过内容的文章数
|
||||
*
|
||||
* @param beginUpdTime 开始修改日期
|
||||
* @param endUpdTime 结束修改日期
|
||||
*/
|
||||
ArticleStatRes statUpdArticleCount(@Param("beginUpdTime") String beginUpdTime,
|
||||
@Param("endUpdTime") String endUpdTime,
|
||||
@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 修改某段日期内修改的文章数据
|
||||
*
|
||||
|
||||
@ -2,6 +2,7 @@ package com.blossom.backend.server.article.draft;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.blossom.backend.base.search.EnableIndex;
|
||||
@ -18,6 +19,7 @@ import com.blossom.backend.server.doc.pojo.DocTreeRes;
|
||||
import com.blossom.backend.server.utils.ArticleUtil;
|
||||
import com.blossom.backend.server.utils.DocUtil;
|
||||
import com.blossom.common.base.exception.XzException404;
|
||||
import com.blossom.common.base.util.DateUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@ -166,7 +168,9 @@ public class ArticleService extends ServiceImpl<ArticleMapper, ArticleEntity> {
|
||||
public Long update(ArticleEntity req) {
|
||||
XzException404.throwBy(req.getId() == null, "ID不得为空");
|
||||
baseMapper.updById(req);
|
||||
if(StrUtil.isNotBlank(req.getName())) {
|
||||
referenceService.updateInnerName(req.getUserId(), req.getId(), req.getName());
|
||||
}
|
||||
return req.getId();
|
||||
}
|
||||
|
||||
@ -185,6 +189,7 @@ public class ArticleService extends ServiceImpl<ArticleMapper, ArticleEntity> {
|
||||
if (req.getHtml() != null) {
|
||||
req.setHtml(req.getHtml().replaceAll("<p><br></p>", ""));
|
||||
}
|
||||
req.setUpdMarkdownTime(DateUtils.date());
|
||||
baseMapper.updContentById(req);
|
||||
referenceService.bind(req.getUserId(), req.getId(), req.getName(), req.getReferences());
|
||||
logService.insert(req.getId(), 0, req.getMarkdown());
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
package com.blossom.backend.server.article.draft;
|
||||
|
||||
import com.blossom.backend.server.doc.DocService;
|
||||
import com.blossom.backend.server.folder.FolderTypeEnum;
|
||||
import com.blossom.common.base.exception.XzException500;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class ImportManager {
|
||||
|
||||
private final DocService docService;
|
||||
private final ReentrantLock LOCK = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* 批量导入时的批次缓存, 一个批次的导入只会从数据库获取一次排序, 后续排序从缓存中递增
|
||||
*/
|
||||
private final Cache<String, AtomicInteger> batchCache = Caffeine.newBuilder()
|
||||
.initialCapacity(50)
|
||||
.expireAfterWrite(120, TimeUnit.MINUTES)
|
||||
.removalListener((String location, AtomicInteger i, RemovalCause cause) ->
|
||||
log.info("batch import [" + location + "] has been deleted")
|
||||
)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 并发导入时的排序获取
|
||||
*/
|
||||
public Integer getSort(String batchId, Long pid, Long userId) {
|
||||
AtomicInteger sort = batchCache.getIfPresent(batchId);
|
||||
if (null == sort) {
|
||||
try {
|
||||
LOCK.tryLock(1000, TimeUnit.MILLISECONDS);
|
||||
sort = batchCache.getIfPresent(batchId);
|
||||
if (null == sort) {
|
||||
sort = initBatchCount(batchId, pid, userId);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOCK.unlock();
|
||||
}
|
||||
}
|
||||
if (sort == null) {
|
||||
throw new XzException500("导入失败");
|
||||
}
|
||||
return sort.getAndIncrement();
|
||||
}
|
||||
|
||||
private AtomicInteger initBatchCount(String batchId, Long pid, Long userId) {
|
||||
System.out.println("初始化导入排序");
|
||||
AtomicInteger i = new AtomicInteger(docService.selectMaxSortByPid(pid, userId, FolderTypeEnum.ARTICLE) + 1);
|
||||
batchCache.put(batchId, i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -118,6 +118,10 @@ public class ArticleEntity extends AbstractPOJO implements Serializable {
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 文章内容的修改时间
|
||||
*/
|
||||
private Date updMarkdownTime;
|
||||
|
||||
//region ============================== 非数据库字段 ==============================
|
||||
/**
|
||||
|
||||
@ -13,10 +13,7 @@ import com.blossom.backend.config.BlConstants;
|
||||
import com.blossom.backend.server.article.draft.ArticleService;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleEntity;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleInfoRes;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenEntity;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenReq;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenRes;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenSyncReq;
|
||||
import com.blossom.backend.server.article.open.pojo.*;
|
||||
import com.blossom.backend.server.doc.DocTypeEnum;
|
||||
import com.blossom.backend.server.utils.DocUtil;
|
||||
import com.blossom.common.base.exception.XzException404;
|
||||
@ -90,7 +87,19 @@ public class ArticleOpenController {
|
||||
@PostMapping
|
||||
public R<Long> open(@Validated @RequestBody ArticleOpenReq req) {
|
||||
req.setUserId(AuthContext.getUserId());
|
||||
return R.ok(openService.open(req));
|
||||
return R.ok(openService.openSingle(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量公开文章
|
||||
*
|
||||
* @param req 文章对象
|
||||
*/
|
||||
@PostMapping("/batch")
|
||||
public R<?> open(@Validated @RequestBody ArticleBatchOpenReq req) {
|
||||
req.setUserId(AuthContext.getUserId());
|
||||
openService.openBatch(req);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.blossom.backend.server.article.TagEnum;
|
||||
import com.blossom.backend.server.article.draft.ArticleService;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleEntity;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleQueryReq;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleBatchOpenReq;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenEntity;
|
||||
import com.blossom.backend.server.article.open.pojo.ArticleOpenReq;
|
||||
import com.blossom.common.base.enums.YesNo;
|
||||
@ -58,28 +60,92 @@ public class ArticleOpenService extends ServiceImpl<ArticleOpenMapper, ArticleOp
|
||||
* 公开或关闭公开访问
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long open(ArticleOpenReq req) {
|
||||
public Long openSingle(ArticleOpenReq req) {
|
||||
ArticleEntity article = articleService.getById(req.getId());
|
||||
ArticleEntity entity = req.to(ArticleEntity.class);
|
||||
open(req, article);
|
||||
// ArticleEntity upd = new ArticleEntity();
|
||||
// upd.setId(req.getId());
|
||||
// upd.setUserId(req.getUserId());
|
||||
// upd.setOpenStatus(req.getOpenStatus());
|
||||
// /*
|
||||
// * 公开文章 将 article 表插入到 article_open 表
|
||||
// */
|
||||
// if (YesNo.YES.getValue().equals(req.getOpenStatus())) {
|
||||
// XzException400.throwBy(article.getOpenStatus().equals(YesNo.YES.getValue()), "文章已[" + req.getId() + "]已允许公开访问, 若要同步最新文章内容, 请使用同步");
|
||||
// upd.setOpenVersion(article.getVersion());
|
||||
// baseMapper.open(req.getId());
|
||||
// }
|
||||
// /*
|
||||
// * 取消公开 删除 article_open 表数据
|
||||
// */
|
||||
// else if (YesNo.NO.getValue().equals(req.getOpenStatus())) {
|
||||
// upd.setOpenVersion(0);
|
||||
// XzException400.throwBy(article.getOpenStatus().equals(YesNo.NO.getValue()), "文章[" + req.getId() + "]未公开, 无法取消公开访问");
|
||||
// baseMapper.delById(req.getId());
|
||||
// }
|
||||
//
|
||||
// // 修改文章的公开状态
|
||||
// articleService.update(upd);
|
||||
return req.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量公开
|
||||
*
|
||||
* @param req
|
||||
* @since 1.14.0
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void openBatch(ArticleBatchOpenReq req) {
|
||||
ArticleQueryReq where = new ArticleQueryReq();
|
||||
where.setPids(CollUtil.newArrayList(req.getPid()));
|
||||
List<ArticleEntity> articles = articleService.listAll(where);
|
||||
for (ArticleEntity article : articles) {
|
||||
ArticleOpenReq open = new ArticleOpenReq();
|
||||
open.setId(article.getId());
|
||||
open.setOpenStatus(req.getOpenStatus());
|
||||
open.setUserId(req.getUserId());
|
||||
open(open, article);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开状态
|
||||
*
|
||||
* @param req 本次公开请求
|
||||
* @param article 文章
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void open(ArticleOpenReq req, ArticleEntity article) {
|
||||
ArticleEntity upd = new ArticleEntity();
|
||||
upd.setId(req.getId());
|
||||
upd.setUserId(req.getUserId());
|
||||
upd.setOpenStatus(req.getOpenStatus());
|
||||
/*
|
||||
* 公开文章 将 article 表插入到 article_open 表
|
||||
*/
|
||||
if (YesNo.YES.getValue().equals(req.getOpenStatus())) {
|
||||
XzException400.throwBy(article.getOpenStatus().equals(YesNo.YES.getValue()), "文章已[" + req.getId() + "]已允许公开访问, 若要同步最新文章内容, 请使用同步");
|
||||
entity.setOpenVersion(article.getVersion());
|
||||
if (YesNo.YES.getValue().equals(article.getOpenStatus())) {
|
||||
return;
|
||||
}
|
||||
// XzException400.throwBy(article.getOpenStatus().equals(YesNo.YES.getValue()), "文章已[" + req.getId() + "]已允许公开访问, 若要同步最新文章内容, 请使用同步");
|
||||
upd.setOpenVersion(article.getVersion());
|
||||
baseMapper.open(req.getId());
|
||||
}
|
||||
/*
|
||||
* 取消公开 删除 article_open 表数据
|
||||
*/
|
||||
else if (YesNo.NO.getValue().equals(req.getOpenStatus())) {
|
||||
entity.setOpenVersion(0);
|
||||
XzException400.throwBy(article.getOpenStatus().equals(YesNo.NO.getValue()), "文章[" + req.getId() + "]未公开, 无法取消公开访问");
|
||||
if (YesNo.NO.getValue().equals(article.getOpenStatus())) {
|
||||
return;
|
||||
}
|
||||
// XzException400.throwBy(article.getOpenStatus().equals(YesNo.NO.getValue()), "文章[" + req.getId() + "]未公开, 无法取消公开访问");
|
||||
upd.setOpenVersion(0);
|
||||
baseMapper.delById(req.getId());
|
||||
}
|
||||
|
||||
articleService.update(entity);
|
||||
return req.getId();
|
||||
// 修改文章的公开状态
|
||||
articleService.update(upd);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package com.blossom.backend.server.article.open.pojo;
|
||||
|
||||
|
||||
import com.blossom.common.base.enums.YesNo;
|
||||
import com.blossom.common.base.pojo.AbstractPOJO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 文件夹下的文章全部公开
|
||||
*
|
||||
* @author xzzz
|
||||
* @since 1.14.0
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ArticleBatchOpenReq extends AbstractPOJO {
|
||||
|
||||
/**
|
||||
* 文章ID
|
||||
*/
|
||||
@Min(value = 0, message = "[文件夹ID] 不能小于0")
|
||||
@NotNull(message = "[文件夹ID] 为必填项")
|
||||
private Long pid;
|
||||
|
||||
/**
|
||||
* 公开状态 {@link YesNo}
|
||||
*
|
||||
* @see YesNo
|
||||
*/
|
||||
@Min(value = 0, message = "[open 状态] 不能小于0")
|
||||
@Max(value = 1, message = "[open 状态] 不能大于1")
|
||||
@NotNull(message = "[open 状态] 为必填项")
|
||||
private Integer openStatus;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
}
|
||||
@ -42,12 +42,12 @@ public class ArticleStatJob {
|
||||
}
|
||||
|
||||
for (UserEntity user : users) {
|
||||
ArticleStatRes statCount = statService.statCount(toDayBegin, toDayEnd, user.getId());
|
||||
statService.updByDate(1, toDay, statCount.getArticleCount(), user.getId());
|
||||
ArticleStatRes statCount = statService.statUpdArticleCount(toDayBegin, toDayEnd, user.getId());
|
||||
statService.updByDate(ArticleStatTypeEnum.ARTICLE_HEATMAP, toDay, statCount.getArticleCount(), user.getId());
|
||||
|
||||
String toMouth = DateUtils.format(DateUtils.beginOfMonth(DateUtils.date()), DateUtils.PATTERN_YYYYMMDD);
|
||||
ArticleStatRes statWord = statService.statCount(null, null, user.getId());
|
||||
statService.updByDate(2, toMouth, statWord.getArticleWords(), user.getId());
|
||||
statService.updByDate(ArticleStatTypeEnum.ARTICLE_WORDS, toMouth, statWord.getArticleWords(), user.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +140,16 @@ public class ArticleStatService extends ServiceImpl<ArticleStatMapper, ArticleSt
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章统计, 文章数, 总字数
|
||||
*
|
||||
* @param beginUpdTime 修改日期的开始范围
|
||||
* @param endUpdTime 修改日期的结束范围
|
||||
*/
|
||||
public ArticleStatRes statUpdArticleCount(String beginUpdTime, String endUpdTime, Long userId) {
|
||||
return articleMapper.statUpdArticleCount(beginUpdTime, endUpdTime, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章统计, 文章数, 总字数
|
||||
*
|
||||
@ -161,7 +171,7 @@ public class ArticleStatService extends ServiceImpl<ArticleStatMapper, ArticleSt
|
||||
return;
|
||||
}
|
||||
for (ArticleWordsRes words : req.getWordsList()) {
|
||||
this.updByDate(ArticleStatTypeEnum.ARTICLE_WORDS.getType(), words.getDate() + "-01", words.getValue(), userId);
|
||||
this.updByDate(ArticleStatTypeEnum.ARTICLE_WORDS, words.getDate() + "-01", words.getValue(), userId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,26 +184,26 @@ public class ArticleStatService extends ServiceImpl<ArticleStatMapper, ArticleSt
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updByDate(Integer type, String date, Integer value, Long userId) {
|
||||
public void updByDate(ArticleStatTypeEnum type, String date, Integer value, Long userId) {
|
||||
if (value == null) {
|
||||
value = 0;
|
||||
}
|
||||
LambdaQueryWrapper<ArticleStatEntity> existWhere = new LambdaQueryWrapper<>();
|
||||
existWhere
|
||||
.eq(ArticleStatEntity::getUserId, userId)
|
||||
.eq(ArticleStatEntity::getType, type)
|
||||
.eq(ArticleStatEntity::getType, type.getType())
|
||||
.eq(ArticleStatEntity::getStatDate, date);
|
||||
if (baseMapper.exists(existWhere)) {
|
||||
LambdaQueryWrapper<ArticleStatEntity> updWhere = new LambdaQueryWrapper<>();
|
||||
updWhere.eq(ArticleStatEntity::getUserId, userId)
|
||||
.eq(ArticleStatEntity::getType, type)
|
||||
.eq(ArticleStatEntity::getType, type.getType())
|
||||
.eq(ArticleStatEntity::getStatDate, date);
|
||||
ArticleStatEntity upd = new ArticleStatEntity();
|
||||
upd.setStatValue(value);
|
||||
baseMapper.update(upd, updWhere);
|
||||
} else {
|
||||
ArticleStatEntity ist = new ArticleStatEntity();
|
||||
ist.setType(type);
|
||||
ist.setType(type.getType());
|
||||
ist.setStatDate(DateUtils.parse(date, DateUtils.PATTERN_YYYYMMDD));
|
||||
ist.setStatValue(value);
|
||||
ist.setUserId(userId);
|
||||
|
||||
@ -5,6 +5,8 @@ import com.blossom.backend.base.auth.annotation.AuthIgnore;
|
||||
import com.blossom.backend.config.BlConstants;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeReq;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeRes;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeUpdSortReq;
|
||||
import com.blossom.backend.server.folder.FolderTypeEnum;
|
||||
import com.blossom.common.base.pojo.R;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -29,7 +31,7 @@ public class DocController {
|
||||
/**
|
||||
* 文档列表
|
||||
*
|
||||
* @return 文件夹列表
|
||||
* @return 文档列表
|
||||
* @apiNote 文档包含文章和文件夹, 文件夹分为图片文件夹和文章文件夹 {@link DocTypeEnum}
|
||||
*/
|
||||
@GetMapping("/trees")
|
||||
@ -56,4 +58,20 @@ public class DocController {
|
||||
open.setUserId(userId);
|
||||
return R.ok(docService.listTree(open));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改排序
|
||||
*
|
||||
* @param tree 需要修改排序的文档列表
|
||||
* @return 文档列表
|
||||
* @since 1.14.0
|
||||
*/
|
||||
@PostMapping("/upd/sort")
|
||||
public R<List<DocTreeRes>> updSort(@RequestBody DocTreeUpdSortReq tree) {
|
||||
docService.updSort(tree.getDocs(), AuthContext.getUserId(), FolderTypeEnum.getType(tree.getFolderType()));
|
||||
DocTreeReq req = new DocTreeReq();
|
||||
req.setUserId(AuthContext.getUserId());
|
||||
req.setOnlyPicture(tree.getOnlyPicture());
|
||||
return R.ok(docService.listTree(req));
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,10 @@ public interface DocMapper {
|
||||
|
||||
/**
|
||||
* 查询 PID 下的最小排序
|
||||
*
|
||||
* @param pid PID
|
||||
* @return 最小排序
|
||||
*/
|
||||
Integer selectMaxSortByPid(@Param("pid") Long pid);
|
||||
Integer selectMaxSortByPid(@Param("pid") Long pid, @Param("userId") Long userId, @Param("type") Integer type);
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
package com.blossom.backend.server.doc;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.blossom.backend.base.auth.AuthContext;
|
||||
import com.blossom.backend.server.article.TagEnum;
|
||||
import com.blossom.backend.server.article.draft.ArticleMapper;
|
||||
import com.blossom.backend.server.article.draft.ArticleService;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleQueryReq;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleEntity;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeReq;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeRes;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeUpdSortReq;
|
||||
import com.blossom.backend.server.folder.FolderMapper;
|
||||
import com.blossom.backend.server.folder.FolderService;
|
||||
import com.blossom.backend.server.folder.FolderTypeEnum;
|
||||
import com.blossom.backend.server.folder.pojo.FolderQueryReq;
|
||||
import com.blossom.backend.server.folder.pojo.FolderEntity;
|
||||
import com.blossom.backend.server.picture.PictureService;
|
||||
import com.blossom.backend.server.utils.DocUtil;
|
||||
import com.blossom.backend.server.utils.PictureUtil;
|
||||
@ -16,6 +20,7 @@ import com.blossom.common.base.enums.YesNo;
|
||||
import com.blossom.common.base.util.SortUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -29,23 +34,28 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Service
|
||||
public class DocService {
|
||||
private FolderService folderService;
|
||||
private ArticleService articleService;
|
||||
private PictureService pictureService;
|
||||
|
||||
private FolderService folderService;
|
||||
@Autowired
|
||||
private DocMapper baseMapper;
|
||||
|
||||
@Autowired
|
||||
public void setFolderService(FolderService folderService) {
|
||||
this.folderService = folderService;
|
||||
}
|
||||
private FolderMapper folderMapper;
|
||||
@Autowired
|
||||
private ArticleMapper articleMapper;
|
||||
@Autowired
|
||||
private DocSortChecker docSortChecker;
|
||||
|
||||
@Autowired
|
||||
public void setArticleService(ArticleService articleService) {
|
||||
this.articleService = articleService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setFolderService(FolderService folderService) {
|
||||
this.folderService = folderService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setPictureService(PictureService pictureService) {
|
||||
this.pictureService = pictureService;
|
||||
@ -64,10 +74,10 @@ public class DocService {
|
||||
* 只查询文件夹
|
||||
* =============================================================================================== */
|
||||
if (req.getOnlyFolder()) {
|
||||
FolderQueryReq where = req.to(FolderQueryReq.class);
|
||||
List<DocTreeRes> folder = folderService.listTree(where);
|
||||
FolderEntity where = req.to(FolderEntity.class);
|
||||
List<FolderEntity> folder = folderMapper.listAll(where);
|
||||
all.addAll(CollUtil.newArrayList(PictureUtil.getDefaultFolder(req.getUserId())));
|
||||
all.addAll(folder);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(folder));
|
||||
priorityType = true;
|
||||
}
|
||||
/* ===============================================================================================
|
||||
@ -75,16 +85,21 @@ public class DocService {
|
||||
* =============================================================================================== */
|
||||
else if (req.getOnlyPicture()) {
|
||||
// 1. 所有图片文件夹
|
||||
FolderQueryReq folder = req.to(FolderQueryReq.class);
|
||||
folder.setType(FolderTypeEnum.PICTURE.getType());
|
||||
List<DocTreeRes> picFolder = folderService.listTree(folder);
|
||||
all.addAll(picFolder);
|
||||
FolderEntity where = req.to(FolderEntity.class);
|
||||
where.setType(FolderTypeEnum.PICTURE.getType());
|
||||
List<FolderEntity> picFolder = folderMapper.listAll(where);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(picFolder));
|
||||
|
||||
// 2. 有图片的图片或文章文件夹
|
||||
List<Long> pids = pictureService.listDistinctPid(req.getUserId());
|
||||
if (CollUtil.isNotEmpty(pids)) {
|
||||
List<DocTreeRes> articleTopFolder = folderService.recursiveToParentTree(pids);
|
||||
all.addAll(articleTopFolder);
|
||||
List<Long> picFolderIds = picFolder.stream().map(FolderEntity::getId).collect(Collectors.toList());
|
||||
// 剔除掉图片文件夹
|
||||
List<Long> articleFolderIds = pids.stream().filter(i -> !picFolderIds.contains(i)).collect(Collectors.toList());
|
||||
if(CollUtil.isNotEmpty(articleFolderIds)) {
|
||||
List<FolderEntity> articleFolder = folderMapper.recursiveToParent(articleFolderIds);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(articleFolder));
|
||||
}
|
||||
}
|
||||
|
||||
Optional<DocTreeRes> min = all.stream().min((f1, f2) -> SortUtil.intSort.compare(f1.getS(), f2.getS()));
|
||||
@ -99,72 +114,83 @@ public class DocService {
|
||||
* 只查询公开的的文章和文章文件夹
|
||||
* =============================================================================================== */
|
||||
else if (req.getOnlyOpen()) {
|
||||
ArticleQueryReq articleWhere = req.to(ArticleQueryReq.class);
|
||||
articleWhere.setOpenStatus(YesNo.YES.getValue());
|
||||
List<DocTreeRes> articles = articleService.listTree(articleWhere);
|
||||
all.addAll(articles);
|
||||
|
||||
ArticleEntity where = req.to(ArticleEntity.class);
|
||||
where.setOpenStatus(YesNo.YES.getValue());
|
||||
List<ArticleEntity> articles = articleMapper.listAll(where);
|
||||
all.addAll(DocUtil.toDocTreesByArticles(articles));
|
||||
|
||||
if (CollUtil.isNotEmpty(articles)) {
|
||||
List<Long> pidList = articles.stream().map(DocTreeRes::getP).collect(Collectors.toList());
|
||||
List<DocTreeRes> folders = folderService.recursiveToParentTree(pidList);
|
||||
all.addAll(folders);
|
||||
List<Long> pidList = articles.stream().map(ArticleEntity::getPid).collect(Collectors.toList());
|
||||
List<FolderEntity> folders = folderMapper.recursiveToParent(pidList);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(folders));
|
||||
}
|
||||
}
|
||||
/* ===============================================================================================
|
||||
* 只查询专题的文章和文件夹
|
||||
* =============================================================================================== */
|
||||
else if (req.getOnlySubject()) {
|
||||
FolderQueryReq folderWhere = req.to(FolderQueryReq.class);
|
||||
folderWhere.setTags(TagEnum.subject.name());
|
||||
folderWhere.setType(FolderTypeEnum.ARTICLE.getType());
|
||||
List<DocTreeRes> subjects = folderService.listTree(folderWhere);
|
||||
|
||||
FolderEntity where = req.to(FolderEntity.class);
|
||||
where.setTags(TagEnum.subject.name());
|
||||
where.setType(FolderTypeEnum.ARTICLE.getType());
|
||||
List<FolderEntity> subjects = folderMapper.listAll(where);
|
||||
|
||||
if (CollUtil.isNotEmpty(subjects)) {
|
||||
List<Long> subjectIds = subjects.stream().map(DocTreeRes::getI).collect(Collectors.toList());
|
||||
List<DocTreeRes> foldersTop = folderService.recursiveToParentTree(subjectIds);
|
||||
List<DocTreeRes> foldersBottom = folderService.recursiveToChildrenTree(subjectIds);
|
||||
all.addAll(foldersTop);
|
||||
all.addAll(foldersBottom);
|
||||
List<Long> subjectIds = subjects.stream().map(FolderEntity::getId).collect(Collectors.toList());
|
||||
List<FolderEntity> foldersTop = folderMapper.recursiveToParent(subjectIds);
|
||||
List<FolderEntity> foldersBottom = folderMapper.recursiveToChildren(subjectIds);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(foldersTop));
|
||||
all.addAll(DocUtil.toDocTreesByFolders(foldersBottom));
|
||||
}
|
||||
List<DocTreeRes> articles = articleService.listTree(req.to(ArticleQueryReq.class));
|
||||
all.addAll(articles);
|
||||
|
||||
List<ArticleEntity> articles = articleMapper.listAll(req.to(ArticleEntity.class));
|
||||
all.addAll(DocUtil.toDocTreesByArticles(articles));
|
||||
}
|
||||
/* ===============================================================================================
|
||||
* 只查询关注的
|
||||
* =============================================================================================== */
|
||||
else if (req.getOnlyStar()) {
|
||||
ArticleQueryReq articleWhere = req.to(ArticleQueryReq.class);
|
||||
articleWhere.setStarStatus(YesNo.YES.getValue());
|
||||
List<DocTreeRes> articles = articleService.listTree(articleWhere);
|
||||
all.addAll(articles);
|
||||
|
||||
ArticleEntity where = req.to(ArticleEntity.class);
|
||||
where.setStarStatus(YesNo.YES.getValue());
|
||||
List<ArticleEntity> articles = articleMapper.listAll(where);
|
||||
all.addAll(DocUtil.toDocTreesByArticles(articles));
|
||||
|
||||
if (CollUtil.isNotEmpty(articles)) {
|
||||
List<Long> pidList = articles.stream().map(DocTreeRes::getP).collect(Collectors.toList());
|
||||
List<DocTreeRes> folders = folderService.recursiveToParentTree(pidList);
|
||||
all.addAll(folders);
|
||||
List<Long> pidList = articles.stream().map(ArticleEntity::getPid).collect(Collectors.toList());
|
||||
List<FolderEntity> folders = folderMapper.recursiveToParent(pidList);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(folders));
|
||||
}
|
||||
}
|
||||
/* ===============================================================================================
|
||||
* 只查询指定文章
|
||||
* =============================================================================================== */
|
||||
else if (req.getArticleId() != null && req.getArticleId() > 0) {
|
||||
ArticleQueryReq articleWhere = req.to(ArticleQueryReq.class);
|
||||
articleWhere.setId(req.getArticleId());
|
||||
List<DocTreeRes> articles = articleService.listTree(articleWhere);
|
||||
all.addAll(articles);
|
||||
|
||||
ArticleEntity where = req.to(ArticleEntity.class);
|
||||
where.setId(req.getArticleId());
|
||||
List<ArticleEntity> articles = articleMapper.listAll(where);
|
||||
all.addAll(DocUtil.toDocTreesByArticles(articles));
|
||||
|
||||
if (CollUtil.isNotEmpty(articles)) {
|
||||
List<Long> pidList = articles.stream().map(DocTreeRes::getP).collect(Collectors.toList());
|
||||
List<DocTreeRes> folders = folderService.recursiveToParentTree(pidList);
|
||||
all.addAll(folders);
|
||||
List<Long> pidList = articles.stream().map(ArticleEntity::getPid).collect(Collectors.toList());
|
||||
List<FolderEntity> folders = folderMapper.recursiveToParent(pidList);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(folders));
|
||||
}
|
||||
}
|
||||
/* ===============================================================================================
|
||||
* 默认查询文章文件夹
|
||||
* =============================================================================================== */
|
||||
else {
|
||||
FolderQueryReq folder = req.to(FolderQueryReq.class);
|
||||
FolderEntity folder = req.to(FolderEntity.class);
|
||||
folder.setType(FolderTypeEnum.ARTICLE.getType());
|
||||
List<DocTreeRes> folders = folderService.listTree(folder);
|
||||
List<DocTreeRes> articles = articleService.listTree(req.to(ArticleQueryReq.class));
|
||||
all.addAll(folders);
|
||||
all.addAll(articles);
|
||||
|
||||
List<FolderEntity> folders = folderMapper.listAll(folder);
|
||||
all.addAll(DocUtil.toDocTreesByFolders(folders));
|
||||
|
||||
List<ArticleEntity> articles = articleMapper.listAll(req.to(ArticleEntity.class));
|
||||
all.addAll(DocUtil.toDocTreesByArticles(articles));
|
||||
}
|
||||
|
||||
return DocUtil.treeWrap(all.stream().distinct().collect(Collectors.toList()), priorityType);
|
||||
@ -177,8 +203,64 @@ public class DocService {
|
||||
* @return 最大排序
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public int selectMinSortByPid(Long pid) {
|
||||
return baseMapper.selectMaxSortByPid(pid);
|
||||
public int selectMaxSortByPid(Long pid, Long userId, FolderTypeEnum type) {
|
||||
return baseMapper.selectMaxSortByPid(pid, userId, type.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改排序
|
||||
*
|
||||
* @param docs 需要修改的文档
|
||||
* @since 1.14.0
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updSort(List<DocTreeUpdSortReq.Doc> docs, Long userId, FolderTypeEnum folderType) {
|
||||
// 提取所有需要修改的文档的父文档
|
||||
List<Long> pids = docs.stream().map(DocTreeUpdSortReq.Doc::getP).collect(Collectors.toList());
|
||||
|
||||
List<ArticleEntity> articles = new ArrayList<>();
|
||||
List<FolderEntity> folders = new ArrayList<>();
|
||||
|
||||
// 一次拖拽所修改的文档可能包含文章和文件夹, 需要归类和转为 Entity
|
||||
for (DocTreeUpdSortReq.Doc doc : docs) {
|
||||
if (DocTypeEnum.A.getType().equals(doc.getTy())) {
|
||||
ArticleEntity a = new ArticleEntity();
|
||||
a.setId(doc.getI());
|
||||
a.setPid(doc.getP());
|
||||
a.setName(doc.getN());
|
||||
a.setSort(doc.getS());
|
||||
articles.add(a);
|
||||
}
|
||||
if (DocTypeEnum.FA.getType().equals(doc.getTy())) {
|
||||
FolderEntity f = new FolderEntity();
|
||||
f.setId(doc.getI());
|
||||
f.setPid(doc.getP());
|
||||
f.setName(doc.getN());
|
||||
f.setSort(doc.getS());
|
||||
folders.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
docSortChecker.checkUnique(pids, folders, articles, folderType, userId);
|
||||
|
||||
for (DocTreeUpdSortReq.Doc tree : docs) {
|
||||
if (DocTypeEnum.FA.getType().equals(tree.getTy()) || DocTypeEnum.FP.getType().equals(tree.getTy())) {
|
||||
FolderEntity f = new FolderEntity();
|
||||
f.setId(tree.getI());
|
||||
f.setPid(tree.getP());
|
||||
f.setSort(tree.getS());
|
||||
f.setUserId(AuthContext.getUserId());
|
||||
f.setStorePath(tree.getSp());
|
||||
folderService.update(f);
|
||||
} else if (DocTypeEnum.A.getType().equals(tree.getTy())) {
|
||||
ArticleEntity a = new ArticleEntity();
|
||||
a.setId(tree.getI());
|
||||
a.setPid(tree.getP());
|
||||
a.setSort(tree.getS());
|
||||
a.setUserId(AuthContext.getUserId());
|
||||
articleService.update(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
package com.blossom.backend.server.doc;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.blossom.backend.server.article.draft.ArticleMapper;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleEntity;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeRes;
|
||||
import com.blossom.backend.server.folder.FolderMapper;
|
||||
import com.blossom.backend.server.folder.FolderTypeEnum;
|
||||
import com.blossom.backend.server.folder.pojo.FolderEntity;
|
||||
import com.blossom.backend.server.utils.DocUtil;
|
||||
import com.blossom.common.base.exception.XzException400;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DocSortChecker {
|
||||
|
||||
@Autowired
|
||||
private FolderMapper folderMapper;
|
||||
|
||||
@Autowired
|
||||
private ArticleMapper articleMapper;
|
||||
|
||||
/**
|
||||
* 检查 pid 下是否有重复的文档排序
|
||||
*
|
||||
* @param pids pid
|
||||
* @param newFs 新文件夹
|
||||
* @param newAs 新文章
|
||||
* @return
|
||||
*/
|
||||
public boolean checkUnique(List<Long> pids,
|
||||
List<FolderEntity> newFs,
|
||||
List<ArticleEntity> newAs,
|
||||
FolderTypeEnum folderType,
|
||||
Long userId) {
|
||||
|
||||
Map<Long, FolderEntity> newFMap = CollUtil.isEmpty(newFs) ? new HashMap<>() : newFs.stream().collect(Collectors.toMap(FolderEntity::getId, doc -> doc));
|
||||
Map<Long, ArticleEntity> newAMap = CollUtil.isEmpty(newAs) ? new HashMap<>() : newAs.stream().collect(Collectors.toMap(ArticleEntity::getId, doc -> doc));
|
||||
|
||||
List<DocTreeRes> allDoc = new ArrayList<>();
|
||||
|
||||
// 获取文件夹排序
|
||||
FolderEntity fByPid = new FolderEntity();
|
||||
fByPid.setPids(pids);
|
||||
fByPid.setType(folderType.getType());
|
||||
fByPid.setUserId(userId);
|
||||
List<FolderEntity> folders = folderMapper.listAll(fByPid);
|
||||
|
||||
if (CollUtil.isNotEmpty(folders)) {
|
||||
for (FolderEntity f : folders) {
|
||||
FolderEntity newF = newFMap.get(f.getId());
|
||||
if (newF != null) {
|
||||
f.setPid(newF.getPid());
|
||||
f.setSort(newF.getSort());
|
||||
newFMap.remove(f.getId());
|
||||
}
|
||||
allDoc.add(DocUtil.toDocTree(f));
|
||||
}
|
||||
}
|
||||
|
||||
if (MapUtil.isNotEmpty(newFMap)) {
|
||||
for (FolderEntity f : newFMap.values()) {
|
||||
allDoc.add(DocUtil.toDocTree(f));
|
||||
}
|
||||
}
|
||||
|
||||
// 只有处理文章排序时, 才需要获取文章排序
|
||||
if(FolderTypeEnum.ARTICLE.equals(folderType)) {
|
||||
ArticleEntity aByPid = new ArticleEntity();
|
||||
aByPid.setPids(pids);
|
||||
aByPid.setUserId(userId);
|
||||
List<ArticleEntity> articles = articleMapper.listAll(aByPid);
|
||||
if (CollUtil.isNotEmpty(articles)) {
|
||||
for (ArticleEntity a : articles) {
|
||||
ArticleEntity newA = newAMap.get(a.getId());
|
||||
if (newA != null) {
|
||||
a.setPid(newA.getPid());
|
||||
a.setSort(newA.getSort());
|
||||
newAMap.remove(a.getId());
|
||||
}
|
||||
allDoc.add(DocUtil.toDocTree(a));
|
||||
}
|
||||
}
|
||||
|
||||
if (MapUtil.isNotEmpty(newAMap)) {
|
||||
for (ArticleEntity a : newAMap.values()) {
|
||||
allDoc.add(DocUtil.toDocTree(a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 按 pid 分组, 依次校验各组下数据
|
||||
Map<Long, List<DocTreeRes>> map = allDoc.stream().collect(Collectors.groupingBy(DocTreeRes::getP));
|
||||
|
||||
map.forEach((pid, list) -> {
|
||||
// 获取不重复的排序
|
||||
long distinct = list.stream().map(DocTreeRes::getS).distinct().count();
|
||||
// 不重复的个数小于总数则有重复
|
||||
if (distinct != list.size()) {
|
||||
throw new XzException400("排序重复");
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
package com.blossom.backend.server.doc.pojo;
|
||||
|
||||
import com.blossom.backend.server.doc.DocTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 修改文档排序
|
||||
*
|
||||
* @since 1.14.0
|
||||
*/
|
||||
@Data
|
||||
public class DocTreeUpdSortReq {
|
||||
|
||||
/**
|
||||
* 需要修改排序的文档列表
|
||||
*/
|
||||
private List<Doc> docs;
|
||||
/**
|
||||
* 文件夹类型 1:文章文件夹; 2:图片文件夹
|
||||
*
|
||||
* @see com.blossom.backend.server.folder.FolderTypeEnum
|
||||
*/
|
||||
@NotNull(message = "文件夹类型为必填项")
|
||||
@Min(value = 1, message = "文件夹类型错误")
|
||||
@Max(value = 2, message = "文件夹类型错误")
|
||||
private Integer folderType;
|
||||
/**
|
||||
* [Picture + Article] 只查询图片文件夹, 以及含有图片的文章文件夹
|
||||
*/
|
||||
@NotNull(message = "文件夹类型为必填项")
|
||||
private Boolean onlyPicture;
|
||||
|
||||
/**
|
||||
* 获取查询的文件夹类型
|
||||
*
|
||||
* @return 返回结果不会为 null
|
||||
*/
|
||||
public Boolean getOnlyPicture() {
|
||||
if (onlyPicture == null) {
|
||||
return false;
|
||||
}
|
||||
return onlyPicture;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Doc {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long i;
|
||||
/**
|
||||
* 父id
|
||||
*/
|
||||
private Long p;
|
||||
/**
|
||||
* 名称, 文件夹名称或文章名称
|
||||
*/
|
||||
private String n;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer s;
|
||||
/**
|
||||
* 类型 {@link DocTypeEnum}
|
||||
*
|
||||
* @see com.blossom.backend.server.doc.DocTypeEnum
|
||||
*/
|
||||
private Integer ty;
|
||||
/**
|
||||
* 存储路径
|
||||
*/
|
||||
private String sp;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
@ -44,15 +45,30 @@ public class FolderController {
|
||||
if (userId == null) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
return R.ok(baseService.subjects(userId));
|
||||
return R.ok(baseService.subjects(userId, true, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询专题列表
|
||||
*
|
||||
* @param starStatus 公开状态
|
||||
*/
|
||||
@GetMapping("/subjects")
|
||||
public R<List<FolderSubjectRes>> listSubject() {
|
||||
return R.ok(baseService.subjects(AuthContext.getUserId()));
|
||||
return R.ok(baseService.subjects(AuthContext.getUserId(), false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 星标文件夹
|
||||
*
|
||||
* @param req 目录文件夹
|
||||
* @since 1.14.0
|
||||
*/
|
||||
@PostMapping("/star")
|
||||
public R<Long> star(@Validated @RequestBody FolderStarReq req) {
|
||||
FolderEntity folder = req.to(FolderEntity.class);
|
||||
folder.setUserId(AuthContext.getUserId());
|
||||
return R.ok(baseService.update(folder));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,6 +84,7 @@ public class FolderController {
|
||||
FolderRes res = entity.to(FolderRes.class);
|
||||
res.setTags(DocUtil.toTagList(entity.getTags()));
|
||||
res.setType(entity.getType());
|
||||
res.setStarStatus(entity.getStarStatus());
|
||||
return R.ok(res);
|
||||
}
|
||||
|
||||
@ -81,9 +98,12 @@ public class FolderController {
|
||||
FolderEntity folder = req.to(FolderEntity.class);
|
||||
folder.setTags(DocUtil.toTagStr(req.getTags()));
|
||||
folder.setUserId(AuthContext.getUserId());
|
||||
// 如果新增到顶部, 获取最小的
|
||||
// 如果新增到底部, 获取最大的排序
|
||||
if (BooleanUtil.isTrue(req.getAddToLast())) {
|
||||
folder.setSort(docService.selectMinSortByPid(req.getPid()) + 1);
|
||||
folder.setSort(docService.selectMaxSortByPid(
|
||||
req.getPid(),
|
||||
AuthContext.getUserId(),
|
||||
Objects.requireNonNull(FolderTypeEnum.getType(req.getType()))) + 1);
|
||||
}
|
||||
return R.ok(baseService.insert(folder));
|
||||
}
|
||||
@ -97,6 +117,15 @@ public class FolderController {
|
||||
public R<Long> update(@Validated @RequestBody FolderUpdReq req) {
|
||||
FolderEntity folder = req.to(FolderEntity.class);
|
||||
folder.setTags(DocUtil.toTagStr(req.getTags()));
|
||||
// 检查排序是否重复
|
||||
// if (folder.getSort() != null && folder.getPid() != null) {
|
||||
// final long newPid = folder.getPid();
|
||||
// docSortChecker.checkUnique(CollUtil.newArrayList(newPid),
|
||||
// CollUtil.newArrayList(folder),
|
||||
// null,
|
||||
// FolderTypeEnum.ARTICLE,
|
||||
// AuthContext.getUserId());
|
||||
// }
|
||||
return R.ok(baseService.update(folder));
|
||||
}
|
||||
|
||||
@ -113,7 +142,7 @@ public class FolderController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 为文件夹快速增加/删除标签
|
||||
* 快速增加/删除标签
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
|
||||
@ -5,14 +5,11 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.blossom.backend.server.article.TagEnum;
|
||||
import com.blossom.backend.server.article.draft.ArticleService;
|
||||
import com.blossom.backend.server.article.draft.ArticleMapper;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleEntity;
|
||||
import com.blossom.backend.server.article.draft.pojo.ArticleQueryReq;
|
||||
import com.blossom.backend.server.doc.pojo.DocTreeRes;
|
||||
import com.blossom.backend.server.folder.pojo.FolderEntity;
|
||||
import com.blossom.backend.server.folder.pojo.FolderQueryReq;
|
||||
import com.blossom.backend.server.folder.pojo.FolderSubjectRes;
|
||||
import com.blossom.backend.server.picture.PictureService;
|
||||
import com.blossom.backend.server.picture.PictureMapper;
|
||||
import com.blossom.backend.server.picture.pojo.PictureEntity;
|
||||
import com.blossom.backend.server.utils.DocUtil;
|
||||
import com.blossom.common.base.enums.YesNo;
|
||||
@ -38,18 +35,13 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
private ArticleService articleService;
|
||||
private PictureService pictureService;
|
||||
|
||||
@Autowired
|
||||
public void setArticleService(ArticleService articleService) {
|
||||
this.articleService = articleService;
|
||||
}
|
||||
private PictureMapper picMapper;
|
||||
|
||||
@Autowired
|
||||
public void setPictureService(PictureService pictureService) {
|
||||
this.pictureService = pictureService;
|
||||
}
|
||||
private ArticleMapper articleMapper;
|
||||
|
||||
|
||||
/**
|
||||
* 专题列表
|
||||
@ -59,36 +51,42 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
* <p>4. 相同专题的所有文件夹ID归为一组.
|
||||
* <p>5. 通过文件夹ID获取到专题下的所有文章, 从而统计文章的总字数, 修改时间, 创建时间等.
|
||||
* <p>6. 如果文章包含 TOC 标签, 则该文章为专题的目录, 专题的默认跳转会跳转至该目录
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param starStatus 公开状态
|
||||
*/
|
||||
public List<FolderSubjectRes> subjects(Long userId) {
|
||||
// 1. 查询所有公开的专题
|
||||
public List<FolderSubjectRes> subjects(Long userId, boolean openStatus, boolean starStatus) {
|
||||
// 1. 查询所有专题
|
||||
FolderEntity where = new FolderEntity();
|
||||
where.setTags(TagEnum.subject.name());
|
||||
where.setOpenStatus(YesNo.YES.getValue());
|
||||
where.setUserId(userId);
|
||||
List<FolderEntity> allOpenSubject = baseMapper.listAll(where);
|
||||
if (CollUtil.isEmpty(allOpenSubject)) {
|
||||
if (openStatus) {
|
||||
where.setOpenStatus(YesNo.YES.getValue());
|
||||
}
|
||||
if (starStatus) {
|
||||
where.setStarStatus(YesNo.YES.getValue());
|
||||
}
|
||||
List<FolderEntity> allSubjects = baseMapper.listAll(where);
|
||||
if (CollUtil.isEmpty(allSubjects)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 专题的ID
|
||||
List<Long> allOpenSubjectIds = allOpenSubject.stream().map(FolderEntity::getId).collect(Collectors.toList());
|
||||
List<Long> allSubjectIds = allSubjects.stream().map(FolderEntity::getId).collect(Collectors.toList());
|
||||
|
||||
// 2. 查询全部专题的子文件夹
|
||||
List<FolderEntity> allOpenSubjectChildFolders = baseMapper.recursiveToChildren(CollUtil.newArrayList(allOpenSubjectIds));
|
||||
allOpenSubjectIds.addAll(allOpenSubjectChildFolders.stream().map(FolderEntity::getId).collect(Collectors.toList()));
|
||||
List<FolderEntity> allSubjectChildFolders = baseMapper.recursiveToChildren(CollUtil.newArrayList(allSubjectIds));
|
||||
allSubjectIds.addAll(allSubjectChildFolders.stream().map(FolderEntity::getId).collect(Collectors.toList()));
|
||||
|
||||
// 3. 查询这些文件夹下的所有文章
|
||||
ArticleQueryReq articleWhere = new ArticleQueryReq();
|
||||
articleWhere.setPids(allOpenSubjectIds);
|
||||
ArticleEntity articleWhere = new ArticleEntity();
|
||||
articleWhere.setPids(allSubjectIds);
|
||||
articleWhere.setUserId(userId);
|
||||
// 统计专题信息时, 会包含非公开文章
|
||||
// articleWhere.setOpenStatus(YesNo.YES.getValue());
|
||||
List<ArticleEntity> articles = articleService.listAll(articleWhere);
|
||||
List<ArticleEntity> articles = articleMapper.listAll(articleWhere);
|
||||
|
||||
List<FolderSubjectRes> results = new ArrayList<>();
|
||||
|
||||
for (FolderEntity subject : allOpenSubject) {
|
||||
for (FolderEntity subject : allSubjects) {
|
||||
// 专题对象, 包含字数, 更新日期等信息
|
||||
FolderSubjectRes result = subject.to(FolderSubjectRes.class);
|
||||
// 默认专题字数
|
||||
@ -96,7 +94,7 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
// 默认专题修改时间
|
||||
result.setSubjectUpdTime(subject.getCreTime());
|
||||
// 4. 这个专题下的所有文件夹ID
|
||||
List<Long> subjectAllId = DocUtil.getChildrenIds(subject.getId(), allOpenSubjectChildFolders);
|
||||
List<Long> subjectAllId = DocUtil.getChildrenIds(subject.getId(), allSubjectChildFolders);
|
||||
// 5. 遍历文章, 将文章归属到某个专题下, 并统计相关字数, 日期等信息
|
||||
for (ArticleEntity article : articles) {
|
||||
if (subjectAllId.contains(article.getPid())) {
|
||||
@ -117,34 +115,6 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询全部文件夹, 并转换成 {@link DocTreeRes}
|
||||
*/
|
||||
public List<DocTreeRes> listTree(FolderQueryReq req) {
|
||||
List<FolderEntity> folders = baseMapper.listAll(req.to(FolderEntity.class));
|
||||
return DocUtil.toTreeRes(folders);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取传入ID的所有的父文件夹, 并转换成 {@link DocTreeRes}, 结果会包含自己
|
||||
*
|
||||
* @param ids ID 集合
|
||||
*/
|
||||
public List<DocTreeRes> recursiveToParentTree(List<Long> ids) {
|
||||
List<FolderEntity> folders = baseMapper.recursiveToParent(ids);
|
||||
return DocUtil.toTreeRes(folders);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取传入ID的所有的子文件夹, 并转换成 {@link DocTreeRes}, 结果会包含自己
|
||||
*
|
||||
* @param ids ID 集合
|
||||
*/
|
||||
public List<DocTreeRes> recursiveToChildrenTree(List<Long> ids) {
|
||||
List<FolderEntity> folders = baseMapper.recursiveToChildren(ids);
|
||||
return DocUtil.toTreeRes(folders);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询
|
||||
*
|
||||
@ -194,9 +164,50 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long update(FolderEntity folder) {
|
||||
XzException404.throwBy(folder.getId() == null, "ID不得为空");
|
||||
XzException400.throwBy(folder.getId().equals(folder.getPid()), "上级文件夹不能是自己");
|
||||
// 如果
|
||||
updateParamValid(folder);
|
||||
updateStorePath(folder);
|
||||
baseMapper.updById(folder);
|
||||
return folder.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* <p>1. 文件夹下有子文件夹时, 无法删除</p>
|
||||
* <p>2. 文件夹下有文章时, 无法删除</p>
|
||||
*
|
||||
* @param folderId 文件夹ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long folderId) {
|
||||
// 文件夹下有文件夹, 无法删除
|
||||
if (baseMapper.recursiveToChildren(CollUtil.newArrayList(folderId)).stream().anyMatch(d -> !d.getId().equals(folderId))) {
|
||||
throw new XzException500("文件夹下有子文件夹, 无法删除, 请先删除子文件夹");
|
||||
}
|
||||
|
||||
// 文件夹下有文章, 无法删除
|
||||
ArticleEntity articleWhere = new ArticleEntity();
|
||||
articleWhere.setPids(CollUtil.newArrayList(folderId));
|
||||
if (CollUtil.isNotEmpty(articleMapper.listAll(articleWhere))) {
|
||||
throw new XzException500("文件夹下有文章, 无法删除, 请先删除下属文章");
|
||||
}
|
||||
|
||||
// 文件夹下有图片, 无法删除
|
||||
PictureEntity picReq = new PictureEntity();
|
||||
picReq.setPid(folderId);
|
||||
if (CollUtil.isNotEmpty(picMapper.listAll(picReq))) {
|
||||
throw new XzException500("文件夹下有图片, 无法删除, 请先删除下属图片");
|
||||
}
|
||||
|
||||
baseMapper.deleteById(folderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改文件夹的存储地址
|
||||
*
|
||||
* @param folder
|
||||
*/
|
||||
private void updateStorePath(FolderEntity folder) {
|
||||
// 处理文件夹的存储地址
|
||||
if (StrUtil.isNotBlank(folder.getStorePath())) {
|
||||
final FolderEntity oldFolder = selectById(folder.getId());
|
||||
// 获取所有子文件夹
|
||||
@ -214,8 +225,6 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
}
|
||||
}
|
||||
folder.setStorePath(formatStorePath(folder.getStorePath()));
|
||||
baseMapper.updById(folder);
|
||||
return folder.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,34 +254,12 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* <p>1. 文件夹下有子文件夹时, 无法删除</p>
|
||||
* <p>2. 文件夹下有文章时, 无法删除</p>
|
||||
* 检查修改是否有效
|
||||
*
|
||||
* @param folderId 文件夹ID
|
||||
* @param folder 文件夹
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long folderId) {
|
||||
// 文件夹下有文件夹, 无法删除
|
||||
if (recursiveToChildrenTree(CollUtil.newArrayList(folderId)).stream().anyMatch(d -> !d.getI().equals(folderId))) {
|
||||
throw new XzException500("文件夹下有子文件夹, 无法删除, 请先删除子文件夹");
|
||||
private void updateParamValid(FolderEntity folder) {
|
||||
XzException404.throwBy(folder.getId() == null, "ID不得为空");
|
||||
XzException400.throwBy(folder.getId().equals(folder.getPid()), "上级文件夹不能是自己");
|
||||
}
|
||||
|
||||
// 文件夹下有文章, 无法删除
|
||||
ArticleQueryReq articleReq = new ArticleQueryReq();
|
||||
articleReq.setPids(CollUtil.newArrayList(folderId));
|
||||
if (CollUtil.isNotEmpty(articleService.listTree(articleReq))) {
|
||||
throw new XzException500("文件夹下有文章, 无法删除, 请先删除下属文章");
|
||||
}
|
||||
|
||||
// 文件夹下有图片, 无法删除
|
||||
PictureEntity picReq = new PictureEntity();
|
||||
picReq.setPid(folderId);
|
||||
if (CollUtil.isNotEmpty(pictureService.listAll(picReq))) {
|
||||
throw new XzException500("文件夹下有图片, 无法删除, 请先删除下属图片");
|
||||
}
|
||||
|
||||
baseMapper.deleteById(folderId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -28,4 +28,14 @@ public enum FolderTypeEnum {
|
||||
this.type = type;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static FolderTypeEnum getType(Integer type) {
|
||||
for (FolderTypeEnum value : FolderTypeEnum.values()) {
|
||||
if (value.getType().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
package com.blossom.backend.server.folder.pojo;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.blossom.backend.server.folder.FolderTypeEnum;
|
||||
import com.blossom.common.base.pojo.AbstractPOJO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
@ -18,7 +15,6 @@ import java.util.List;
|
||||
*
|
||||
* @author xzzz
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@TableName("blossom_folder")
|
||||
public class FolderEntity extends AbstractPOJO implements Serializable {
|
||||
@ -44,6 +40,10 @@ public class FolderEntity extends AbstractPOJO implements Serializable {
|
||||
* 标签
|
||||
*/
|
||||
private String tags;
|
||||
/**
|
||||
* star状态
|
||||
*/
|
||||
private Integer starStatus;
|
||||
/**
|
||||
* 开放状态
|
||||
*/
|
||||
@ -102,6 +102,11 @@ public class FolderEntity extends AbstractPOJO implements Serializable {
|
||||
@TableField(exist = false)
|
||||
private List<Long> ids;
|
||||
|
||||
//endregion
|
||||
/**
|
||||
* 父ID集合
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> pids;
|
||||
|
||||
//endregion
|
||||
}
|
||||
|
||||
@ -16,6 +16,10 @@ import java.io.Serializable;
|
||||
public class FolderQueryReq extends PageReq implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* star状态
|
||||
*/
|
||||
private Integer starStatus;
|
||||
|
||||
private String tags;
|
||||
|
||||
|
||||
@ -39,6 +39,10 @@ public class FolderRes extends AbstractPOJO implements Serializable {
|
||||
* 标签
|
||||
*/
|
||||
private List<String> tags;
|
||||
/**
|
||||
* star状态
|
||||
*/
|
||||
private Integer starStatus;
|
||||
/**
|
||||
* 是否公开文件夹 [0:未公开,1:公开]
|
||||
*/
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package com.blossom.backend.server.folder.pojo;
|
||||
|
||||
|
||||
import com.blossom.common.base.enums.YesNo;
|
||||
import com.blossom.common.base.pojo.AbstractPOJO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 文章 star
|
||||
*
|
||||
* @author xzzz
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class FolderStarReq extends AbstractPOJO {
|
||||
|
||||
/**
|
||||
* 目录ID
|
||||
*/
|
||||
@Min(value = 0, message = "[目录ID] 不能小于0")
|
||||
@NotNull(message = "[目录ID] 为必填项")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* star 状态 {@link YesNo}
|
||||
* @see YesNo
|
||||
*/
|
||||
@Min(value = 0, message = "[star 状态] 不能小于0")
|
||||
@Max(value = 1, message = "[star 状态] 不能大于1")
|
||||
@NotNull(message = "[star 状态] 为必填项")
|
||||
private Integer starStatus;
|
||||
}
|
||||
@ -33,6 +33,10 @@ public class FolderUpdReq extends AbstractPOJO implements Serializable {
|
||||
private String name;
|
||||
/** 图标 */
|
||||
private String icon;
|
||||
/**
|
||||
* star状态
|
||||
*/
|
||||
private Integer starStatus;
|
||||
/** 标签 */
|
||||
private List<String> tags;
|
||||
/** 排序 */
|
||||
|
||||
@ -43,8 +43,11 @@ public class TodoService extends ServiceImpl<TodoMapper, TodoEntity> {
|
||||
TodoGroupRes res = TodoGroupRes.build();
|
||||
|
||||
Map<String, List<TodoEntity>> map = todos.stream().collect(Collectors.groupingBy(TodoEntity::getTodoId));
|
||||
|
||||
// 遍历每个待办事项的所有任务
|
||||
map.forEach((todoId, data) -> {
|
||||
TodoGroupRes.TodoGroup group = data.get(0).to(TodoGroupRes.TodoGroup.class);
|
||||
// 根据状态分组
|
||||
Map<String, List<TodoEntity>> taskStatusMap = data.stream().collect(Collectors.groupingBy(TodoEntity::getTaskStatus));
|
||||
int w = 0, p = 0, c = 0;
|
||||
if (CollUtil.isNotEmpty(taskStatusMap.get(TaskStatusEnum.WAITING.name()))) {
|
||||
@ -56,7 +59,8 @@ public class TodoService extends ServiceImpl<TodoMapper, TodoEntity> {
|
||||
if (CollUtil.isNotEmpty(taskStatusMap.get(TaskStatusEnum.COMPLETED.name()))) {
|
||||
c = taskStatusMap.get(TaskStatusEnum.COMPLETED.name()).get(0).getTaskCount();
|
||||
}
|
||||
group.setTaskCountStat(String.format("%d|%d|%d", w, p, c));
|
||||
group.setTaskCountStat(String.format("%d/%d/%d", w, p, c));
|
||||
group.setTaskCount(w + p + c);
|
||||
|
||||
if (TodoTypeEnum.DAY.getType().equals(data.get(0).getTodoType())) {
|
||||
res.getTodoDays().put(group.getTodoId(), group);
|
||||
|
||||
@ -60,7 +60,6 @@ public class DocUtil {
|
||||
return SortUtil.intSort.compare(d1.getS(), d2.getS());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return rootLevel;
|
||||
}
|
||||
|
||||
@ -122,7 +121,7 @@ public class DocUtil {
|
||||
tree.setTy(DocTypeEnum.A.getType());
|
||||
|
||||
// 判断文章的版本与公开版本是否有差异
|
||||
if (article.getOpenStatus().equals(YesNo.YES.getValue()) && article.getVersion() > article.getOpenVersion()) {
|
||||
if (YesNo.YES.getValue().equals(article.getOpenStatus()) && article.getVersion() > article.getOpenVersion()) {
|
||||
tree.setVd(YesNo.YES.getValue());
|
||||
}
|
||||
|
||||
@ -134,6 +133,20 @@ public class DocUtil {
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章集合转 docTree集合
|
||||
*
|
||||
* @param articles 文章集合
|
||||
* @return docTree
|
||||
*/
|
||||
public static List<DocTreeRes> toDocTreesByArticles(List<ArticleEntity> articles) {
|
||||
List<DocTreeRes> articleTrees = new ArrayList<>(articles.size());
|
||||
for (ArticleEntity folder : articles) {
|
||||
articleTrees.add(toDocTree(folder));
|
||||
}
|
||||
return articleTrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件夹转 docTree
|
||||
*
|
||||
@ -148,7 +161,7 @@ public class DocUtil {
|
||||
tree.setS(folder.getSort());
|
||||
tree.setN(folder.getName());
|
||||
tree.setSp(folder.getStorePath());
|
||||
tree.setStar(0);
|
||||
tree.setStar(folder.getStarStatus());
|
||||
tree.setTy(folder.getType());
|
||||
tree.setIcon(folder.getIcon());
|
||||
if (StrUtil.isBlank(folder.getTags())) {
|
||||
@ -165,8 +178,11 @@ public class DocUtil {
|
||||
* @param folders 文件夹集合
|
||||
* @return docTree
|
||||
*/
|
||||
public static List<DocTreeRes> toTreeRes(List<FolderEntity> folders) {
|
||||
List<DocTreeRes> folderTrees = new ArrayList(folders.size());
|
||||
public static List<DocTreeRes> toDocTreesByFolders(List<FolderEntity> folders) {
|
||||
if (CollUtil.isEmpty(folders)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<DocTreeRes> folderTrees = new ArrayList<>(folders.size());
|
||||
for (FolderEntity folder : folders) {
|
||||
folderTrees.add(toDocTree(folder));
|
||||
}
|
||||
|
||||
@ -58,9 +58,16 @@ public class TodoUtil {
|
||||
if (CollUtil.isEmpty(todos)) {
|
||||
return "无内容";
|
||||
}
|
||||
Map<String, List<TodoEntity>> maps = todos.stream().collect(Collectors.groupingBy(TodoEntity::getTodoName));
|
||||
|
||||
List<Map.Entry<String, List<TodoEntity>>> entryList = todos.stream()
|
||||
.collect(Collectors.groupingBy(TodoEntity::getTodoName))
|
||||
// 增加根据Todo name的排序
|
||||
.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
maps.forEach((todoName, tasks) -> {
|
||||
entryList.forEach(entry -> {
|
||||
String todoName = entry.getKey();
|
||||
List<TodoEntity> tasks = entry.getValue();
|
||||
sb.append(String.format("# %s \n\n", todoName));
|
||||
|
||||
for (TodoEntity task : tasks) {
|
||||
|
||||
@ -37,6 +37,7 @@ spring:
|
||||
init:
|
||||
mode: always
|
||||
platform: mysql
|
||||
continue-on-error: true
|
||||
|
||||
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ mybatis ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/blossom?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&useSSL=false&&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://192.168.31.99:3306/xzzz-blossom?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&useSSL=false&&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: jasmine888
|
||||
hikari:
|
||||
|
||||
@ -92,12 +92,24 @@
|
||||
html = #{html},
|
||||
words = #{words},
|
||||
toc = #{toc},
|
||||
version = version + 1
|
||||
version = version + 1,
|
||||
upd_markdown_time = #{updMarkdownTime}
|
||||
where id = #{id}
|
||||
and user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<!-- ======================================== 统计 ======================================== -->
|
||||
<select id="statUpdArticleCount" resultType="com.blossom.backend.server.article.draft.pojo.ArticleStatRes">
|
||||
select count(*) as articleCount
|
||||
from blossom_article
|
||||
where user_id = #{userId}
|
||||
<if test="beginUpdTime != null and beginUpdTime != ''">
|
||||
and upd_markdown_time >= #{beginUpdTime}
|
||||
</if>
|
||||
<if test="endUpdTime != null and endUpdTime != ''">
|
||||
and #{endUpdTime} >= upd_markdown_time
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="statCount" resultType="com.blossom.backend.server.article.draft.pojo.ArticleStatRes">
|
||||
select count(*) as articleCount, sum(words) as articleWords
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
<select id="selectMaxSortByPid" resultType="java.lang.Integer">
|
||||
select IFNULL(max(h.sort), 0)
|
||||
from (
|
||||
select sort from blossom_article where pid = #{pid}
|
||||
select sort from blossom_article where pid = #{pid} and user_id = #{userId}
|
||||
union
|
||||
select sort from blossom_folder where pid = #{pid}
|
||||
select sort from blossom_folder where pid = #{pid} and user_id = #{userId} and type = #{type}
|
||||
) h;
|
||||
</select>
|
||||
</mapper>
|
||||
@ -9,6 +9,7 @@
|
||||
`name`,
|
||||
icon,
|
||||
tags,
|
||||
star_status,
|
||||
open_status,
|
||||
sort,
|
||||
cover,
|
||||
@ -21,9 +22,14 @@
|
||||
cre_time
|
||||
from blossom_folder
|
||||
<where>
|
||||
<if test="starStatus != null ">and star_status = #{starStatus}</if>
|
||||
<if test="openStatus != null ">and open_status = #{openStatus}</if>
|
||||
<if test="type != null ">and type = #{type}</if>
|
||||
<if test="tags != null and tags != ''">and tags like concat('%',#{tags},'%')</if>
|
||||
<if test="pid != null">and pid = #{pid}</if>
|
||||
<if test="pids != null and pids.size() != 0">and pid in
|
||||
<foreach collection="pids" item="item" open="(" close=")" separator=",">#{item}</foreach>
|
||||
</if>
|
||||
<if test="userId != null">and user_id = #{userId}</if>
|
||||
</where>
|
||||
</select>
|
||||
@ -62,6 +68,7 @@
|
||||
<if test="name != null">`name` = #{name},</if>
|
||||
<if test="icon != null">icon = #{icon},</if>
|
||||
<if test="tags != null">tags = #{tags},</if>
|
||||
<if test="starStatus != null">star_status = #{starStatus},</if>
|
||||
<if test="sort != null">sort = #{sort},</if>
|
||||
<if test="cover != null">cover = #{cover},</if>
|
||||
<if test="color != null">color = #{color},</if>
|
||||
|
||||
@ -639,3 +639,9 @@ CREATE TABLE IF NOT EXISTS `base_user_param`
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4 COMMENT = '用户参数'
|
||||
COLLATE = utf8mb4_bin;
|
||||
|
||||
-- Code that might be wrong goes last
|
||||
-- since 1.14.0
|
||||
alter table blossom_folder add column star_status tinyint(1) NOT NULL DEFAULT 0 COMMENT '收藏 0:否,1:是';
|
||||
-- since 1.15.0
|
||||
alter table blossom_article add column upd_markdown_time datetime COMMENT '文章内容的修改时间';
|
||||
@ -1 +1,7 @@
|
||||
客户端静态文件放置位置。
|
||||
|
||||
在 blossom-editor 工程下执行如下语句, 打包成网页后复制到后台 static/editor 资源目录下
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@ -42,4 +42,13 @@ public enum YesNo {
|
||||
}
|
||||
return NO.aBoolean;
|
||||
}
|
||||
|
||||
public static YesNo getValue(Integer value) {
|
||||
for (YesNo yesNo : YesNo.values()) {
|
||||
if (yesNo.getValue().equals(value)) {
|
||||
return yesNo;
|
||||
}
|
||||
}
|
||||
return YesNo.NO;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
<properties>
|
||||
<!-- 数据库连接 -->
|
||||
<mysql.version>8.0.21</mysql.version>
|
||||
<mysql.version>8.0.28</mysql.version>
|
||||
<!-- 分页插件 -->
|
||||
<pagehelper.version>5.3.2</pagehelper.version>
|
||||
<pagehelper-springboot.version>1.4.6</pagehelper-springboot.version>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -173,7 +173,11 @@ public class IaasProperties {
|
||||
public static class BLOS {
|
||||
/**
|
||||
* BLOS 查看图片的接口的地址, 默认在 PictureController#getFile() 方法中, 末尾带有 "/" 会自动清除
|
||||
*
|
||||
* @deprecated 该配置项已转移至系统配置 base_sys_param {@link ParamEnum#BLOSSOM_OBJECT_STORAGE_DOMAIN},
|
||||
* 但在 base_sys_param 为配置时仍然生效
|
||||
*/
|
||||
@Deprecated
|
||||
private String domain;
|
||||
/**
|
||||
* BLOS 默认上传地址, 不能为空, 注意不同系统的区分, 末尾带有 "/" 会自动清除
|
||||
@ -202,7 +206,7 @@ public class IaasProperties {
|
||||
}
|
||||
if (blos != null) {
|
||||
String domain = formatDomain(blos.getDomain());
|
||||
if (!StrUtil.endWith(domain,"/pic")) {
|
||||
if (!StrUtil.endWith(domain, "/pic")) {
|
||||
domain = domain + "/pic";
|
||||
}
|
||||
blos.setDomain(domain);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.blossom</groupId>
|
||||
<artifactId>blossom-backend</artifactId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.blossom</groupId>
|
||||
<artifactId>expand-sentinel</artifactId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.blossom</groupId>
|
||||
<artifactId>expand-sentinel</artifactId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>blossom-backend</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>expand-tracker</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>blossom-backend</artifactId>
|
||||
<groupId>com.blossom</groupId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -8,17 +8,17 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.11</version>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.blossom</groupId>
|
||||
<artifactId>blossom-backend</artifactId>
|
||||
<version>1.13.0-SNAPSHOT</version>
|
||||
<version>1.15.0-SNAPSHOT</version>
|
||||
|
||||
<!-- 版本控制 -->
|
||||
<properties>
|
||||
<revision>1.13.0-SNAPSHOT</revision>
|
||||
<revision>1.15.0-SNAPSHOT</revision>
|
||||
|
||||
<!-- 编译设置 -->
|
||||
<java.version>1.8</java.version>
|
||||
@ -30,7 +30,7 @@
|
||||
SpringBoot 版本
|
||||
https://spring.io/projects/spring-boot#support
|
||||
-->
|
||||
<spring-boot.version>2.7.11</spring-boot.version>
|
||||
<spring-boot.version>2.7.18</spring-boot.version>
|
||||
|
||||
<!-- 其他三方包版本 -->
|
||||
<lombok.version>1.18.8</lombok.version>
|
||||
|
||||
5252
blossom-editor/package-lock.json
generated
5252
blossom-editor/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "blossom",
|
||||
"productName": "Blossom",
|
||||
"version": "1.13.0",
|
||||
"version": "1.15.0",
|
||||
"description": "A markdown editor",
|
||||
"license": "MIT",
|
||||
"main": "./out/main/index.js",
|
||||
@ -29,50 +29,50 @@
|
||||
"build:linux": "npm run build && electron-builder --linux --config"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-markdown": "^6.2.0",
|
||||
"@codemirror/language-data": "^6.3.1",
|
||||
"@electron-toolkit/preload": "^2.0.0",
|
||||
"@electron-toolkit/utils": "^1.0.2",
|
||||
"@codemirror/lang-markdown": "^6.2.4",
|
||||
"@codemirror/language-data": "^6.4.1",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@types/marked": "^5.0.1",
|
||||
"axios": "^1.4.0",
|
||||
"axios": "^1.6.8",
|
||||
"codemirror": "^6.0.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"electron-updater": "^5.3.0",
|
||||
"element-plus": "^2.4.4",
|
||||
"echarts": "^5.5.0",
|
||||
"electron-updater": "^6.1.8",
|
||||
"element-plus": "^2.6.3",
|
||||
"highlight.js": "^11.8.0",
|
||||
"hotkeys-js": "^3.12.0",
|
||||
"katex": "^0.16.8",
|
||||
"hotkeys-js": "^3.13.7",
|
||||
"katex": "^0.16.10",
|
||||
"marked": "^7.0.5",
|
||||
"marked-highlight": "^2.0.4",
|
||||
"marked-katex-extension": "^4.0.5",
|
||||
"markmap-lib": "^0.15.4",
|
||||
"markmap-view": "^0.15.4",
|
||||
"mermaid": "^10.4.0",
|
||||
"markmap-lib": "^0.16.1",
|
||||
"markmap-view": "^0.16.0",
|
||||
"mermaid": "^10.9.0",
|
||||
"pinia": "^2.1.6",
|
||||
"prettier": "^3.0.2",
|
||||
"sass": "^1.66.1",
|
||||
"vue-router": "^4.2.4"
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.74.1",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@electron/notarize": "^1.2.4",
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@types/node": "^18.17.8",
|
||||
"@vitejs/plugin-vue": "^4.6.2",
|
||||
"@electron/notarize": "^2.3.0",
|
||||
"@rushstack/eslint-patch": "^1.10.1",
|
||||
"@types/node": "^18.19.29",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"electron": "^24.8.0",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-vite": "^1.0.27",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"typescript": "^5.1.6",
|
||||
"unplugin-auto-import": "^0.16.7",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "^4.5.1",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"electron": "^28.2.10",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-vite": "^2.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.24.0",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"typescript": "^5.4.4",
|
||||
"unplugin-auto-import": "^0.17.5",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.2.8",
|
||||
"vue": "^3.4.5",
|
||||
"vue-tsc": "^1.8.27"
|
||||
"vue-tsc": "^2.0.10"
|
||||
}
|
||||
}
|
||||
@ -168,6 +168,8 @@ const initTray = () => {
|
||||
console.log('1. 创建托盘 Tray')
|
||||
tray = new Tray(icon)
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Blossom 官网 ', click: () => shell.openExternal('https://www.wangyunf.com/blossom-doc/index') },
|
||||
{ type: 'separator' },
|
||||
{ label: '显示', click: () => mainWindow!.show() },
|
||||
{ label: '退出', click: () => app.quit() }
|
||||
])
|
||||
@ -432,9 +434,9 @@ export const initOnWindow = (window: BrowserWindow) => {
|
||||
/**
|
||||
* 拦截页面链接
|
||||
*/
|
||||
window.webContents.on('will-navigate', (event: Event, url: string) => {
|
||||
interceptorATag(event, url)
|
||||
})
|
||||
// window.webContents.on('will-navigate', (event: Event, url: string) => {
|
||||
// interceptorATag(event, url)
|
||||
// })
|
||||
|
||||
/**
|
||||
* 打开链接, 如果打开链接于服务器域名相同, 则在新窗口中打开
|
||||
@ -451,19 +453,19 @@ export const initOnWindow = (window: BrowserWindow) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截 a 标签
|
||||
* @param e
|
||||
* @param url
|
||||
*/
|
||||
const interceptorATag = (e: Event, url: string): boolean => {
|
||||
e.preventDefault()
|
||||
let innerUrl = url
|
||||
if (blossomUserinfo && innerUrl.startsWith(blossomUserinfo.userParams.WEB_ARTICLE_URL)) {
|
||||
let articleId: string = innerUrl.replaceAll(blossomUserinfo.userParams.WEB_ARTICLE_URL, '')
|
||||
createNewWindow('article', articleId, Number(articleId))
|
||||
} else if (!is.dev) {
|
||||
shell.openExternal(url)
|
||||
}
|
||||
return true
|
||||
}
|
||||
// /**
|
||||
// * 拦截 a 标签
|
||||
// * @param e
|
||||
// * @param url
|
||||
// */
|
||||
// const interceptorATag = (e: Event, url: string): boolean => {
|
||||
// e.preventDefault()
|
||||
// let innerUrl = url
|
||||
// if (blossomUserinfo && innerUrl.startsWith(blossomUserinfo.userParams.WEB_ARTICLE_URL)) {
|
||||
// let articleId: string = innerUrl.replaceAll(blossomUserinfo.userParams.WEB_ARTICLE_URL, '')
|
||||
// createNewWindow('article', articleId, Number(articleId))
|
||||
// } else if (!is.dev) {
|
||||
// shell.openExternal(url)
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
|
||||
@ -14,8 +14,8 @@
|
||||
img-src * blob: data:;
|
||||
connect-src *;
|
||||
frame-src *;
|
||||
font-src * data:;
|
||||
" />
|
||||
<!-- 加载动画 -->
|
||||
<style>
|
||||
:root {
|
||||
--index-loading-color: #859e6052;
|
||||
@ -336,7 +336,6 @@
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
/* 骨架屏的动画 */
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
@ -346,7 +345,6 @@
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- 首页加载正常运行在这里 -->
|
||||
<div class="html-loading">
|
||||
<div class="header">
|
||||
<div class="logo"></div>
|
||||
@ -387,7 +385,7 @@
|
||||
<br />
|
||||
<span style="font-size: 13px">加载中...</span>
|
||||
</div>
|
||||
<div class="html-loading-version">Blossom | v1.13.0</div>
|
||||
<div class="html-loading-version">Blossom | v1.15.0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElConfigProvider } from 'element-plus'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -94,6 +94,15 @@ export const docTreeApi = (params?: object): Promise<R<any>> => {
|
||||
return rq.get<R<any>>('/doc/trees', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改文档的排序
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const docUpdSortApi = (data: object): Promise<R<any>> => {
|
||||
return rq.post<R<any>>('/doc/upd/sort', data)
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region ====================================================< folder >====================================================
|
||||
@ -274,6 +283,15 @@ export const articleStarApi = (data?: object): Promise<R<any>> => {
|
||||
return rq.post<R<any>>('/article/star', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* star 或取消 star
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const folderStarApi = (data?: object): Promise<R<any>> => {
|
||||
return rq.post<R<any>>('/folder/star', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文章 markdown
|
||||
* @param params
|
||||
@ -364,6 +382,15 @@ export const articleOpenApi = (data?: object): Promise<R<any>> => {
|
||||
return rq.post<R<any>>('/article/open', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章公开或取消公开
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const articleOpenBatchApi = (data?: object): Promise<R<any>> => {
|
||||
return rq.post<R<any>>('/article/open/batch', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章同步
|
||||
* @param data
|
||||
@ -431,16 +458,6 @@ export const articleBackupListApi = (): Promise<R<BackupFile[]>> => {
|
||||
return rq.get<BackupFile[]>('/article/backup/list')
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载备份文件
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
export const articleBackupDownloadApi = (params?: object): Promise<any> => {
|
||||
let config = { params: params, responseType: 'blob' }
|
||||
return rq.get('/article/backup/download', config)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载文件信息
|
||||
* @param data
|
||||
|
||||
@ -90,7 +90,7 @@ export class Request {
|
||||
/* 其他接口报错, 直接拒绝并提示错误信息 */
|
||||
let errorResponse = data
|
||||
errorResponse['url'] = res.config.url
|
||||
Notify.error(data.msg, '请求失败')
|
||||
Notify.error(data.msg, '处理失败')
|
||||
return Promise.reject(res)
|
||||
}
|
||||
},
|
||||
@ -106,22 +106,23 @@ export class Request {
|
||||
}
|
||||
let code = err.code
|
||||
let resp = err.response
|
||||
if (resp && resp.data) {
|
||||
Notify.error(resp.data.msg, '请求失败')
|
||||
return Promise.reject(err)
|
||||
}
|
||||
console.log("🚀 ~ Request ~ constructor ~ resp:123123123", resp)
|
||||
if (code === 'ERR_NETWORK') {
|
||||
Notify.error('网络错误,请检查您的网络是否通畅', '请求失败')
|
||||
Notify.error('网络错误, 请检查您的网络是否通畅', '请求失败')
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (err.request && err.request.status === 404) {
|
||||
Notify.error('未找到您的请求, 请您检查服务器地址!', '请求失败(404)')
|
||||
Notify.error('未找到您的请求', '请求失败(404)')
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (err.request && err.request.status === 405) {
|
||||
Notify.error(`您的请求地址可能有误, 请检查请求地址${url}`, '请求失败(405)')
|
||||
return Promise.reject(err)
|
||||
}
|
||||
if (resp && resp.data) {
|
||||
Notify.error(resp.data.msg, '请求失败')
|
||||
return Promise.reject(err)
|
||||
}
|
||||
return Promise.reject(err)
|
||||
}
|
||||
)
|
||||
|
||||
@ -5,7 +5,7 @@ const blossom = {
|
||||
SYS: {
|
||||
NAME: 'Blossom',
|
||||
FULL_NAME: 'BLOSSOM-EDITOR',
|
||||
VERSION: 'v1.13.0',
|
||||
VERSION: 'v1.15.0',
|
||||
|
||||
//
|
||||
DOC: 'https://www.wangyunf.com/blossom-doc/index',
|
||||
|
||||
@ -31,14 +31,14 @@ img {
|
||||
/* 定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸 */
|
||||
::-webkit-scrollbar {
|
||||
/* 滚动条宽度 */
|
||||
width: 7px;
|
||||
width: 5px;
|
||||
/* 滚动条高度 */
|
||||
height: 7px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar:hover {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
/* 定义滑块 内阴影+圆角 */
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconbl"; /* Project id 4118609 */
|
||||
src: url('iconfont.woff2?t=1706597865404') format('woff2'),
|
||||
url('iconfont.woff?t=1706597865404') format('woff'),
|
||||
url('iconfont.ttf?t=1706597865404') format('truetype');
|
||||
src: url('iconfont.woff2?t=1712427980711') format('woff2'),
|
||||
url('iconfont.woff?t=1712427980711') format('woff'),
|
||||
url('iconfont.ttf?t=1712427980711') format('truetype');
|
||||
}
|
||||
|
||||
.iconbl {
|
||||
@ -13,6 +13,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.bl-collimation:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.bl-search-item:before {
|
||||
content: "\e753";
|
||||
}
|
||||
|
||||
.bl-fileadd-line:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.bl-folderadd-line:before {
|
||||
content: "\ea51";
|
||||
}
|
||||
|
||||
.bl-collapse:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.bl-brush-line:before {
|
||||
content: "\ea4f";
|
||||
}
|
||||
@ -237,10 +257,6 @@
|
||||
content: "\ea17";
|
||||
}
|
||||
|
||||
.bl-a-leftdirection-fill:before {
|
||||
content: "\ea18";
|
||||
}
|
||||
|
||||
.bl-record-line:before {
|
||||
content: "\ea19";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -5,6 +5,41 @@
|
||||
"css_prefix_text": "bl-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "5203274",
|
||||
"name": "瞄准",
|
||||
"font_class": "collimation",
|
||||
"unicode": "e62c",
|
||||
"unicode_decimal": 58924
|
||||
},
|
||||
{
|
||||
"icon_id": "577366",
|
||||
"name": "搜索类目-fill",
|
||||
"font_class": "search-item",
|
||||
"unicode": "e753",
|
||||
"unicode_decimal": 59219
|
||||
},
|
||||
{
|
||||
"icon_id": "7440601",
|
||||
"name": "新增文件",
|
||||
"font_class": "fileadd-line",
|
||||
"unicode": "e659",
|
||||
"unicode_decimal": 58969
|
||||
},
|
||||
{
|
||||
"icon_id": "24341715",
|
||||
"name": "folder add-line",
|
||||
"font_class": "folderadd-line",
|
||||
"unicode": "ea51",
|
||||
"unicode_decimal": 59985
|
||||
},
|
||||
{
|
||||
"icon_id": "11855598",
|
||||
"name": "收起",
|
||||
"font_class": "collapse",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "24341231",
|
||||
"name": "brush-line",
|
||||
@ -397,13 +432,6 @@
|
||||
"unicode": "ea17",
|
||||
"unicode_decimal": 59927
|
||||
},
|
||||
{
|
||||
"icon_id": "24341919",
|
||||
"name": "left direction-fill",
|
||||
"font_class": "a-leftdirection-fill",
|
||||
"unicode": "ea18",
|
||||
"unicode_decimal": 59928
|
||||
},
|
||||
{
|
||||
"icon_id": "24342162",
|
||||
"name": "record-line",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
blossom-editor/src/renderer/src/assets/imgs/transparent.png
Normal file
BIN
blossom-editor/src/renderer/src/assets/imgs/transparent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 B |
@ -1,10 +1,17 @@
|
||||
.el-dialog {
|
||||
--el-dialog-border-radius: 8px !important;
|
||||
--el-dialog-padding-primary: 0 !important;
|
||||
--el-dialog-padding-primary: 10px 0 0 0 !important;
|
||||
--el-dialog-bg-color: var(--bl-dialog-bg-color) !important;
|
||||
--el-dialog-box-shadow: var(--bl-dialog-box-shadow) !important;
|
||||
}
|
||||
|
||||
.bl-dialog-draggable-header {
|
||||
--el-dialog-padding-primary: 0 !important;
|
||||
.el-dialog__header {
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// 更大的 header close 按钮
|
||||
.bl-dialog-bigger-headerbtn {
|
||||
.el-dialog__headerbtn {
|
||||
|
||||
@ -61,24 +61,28 @@
|
||||
|
||||
td {
|
||||
.cell {
|
||||
border-radius: 0;
|
||||
width: 44px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-year-table {
|
||||
td {
|
||||
width: auto;
|
||||
padding: 7px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-month-table {
|
||||
td {
|
||||
width: auto;
|
||||
padding: 1px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-date-table {
|
||||
td {
|
||||
width: auto;
|
||||
padding: 2px 0;
|
||||
}
|
||||
}
|
||||
@ -121,4 +125,9 @@
|
||||
.el-popper.is-small {
|
||||
padding: 0 8px;
|
||||
box-shadow: 1px 1px 3px #f4f4f4;
|
||||
transition: none;
|
||||
|
||||
.el-popper__arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
.resize-divider-vertical {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: var(--el-border-color);
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
cursor: ew-resize;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
display: block;
|
||||
transition: 0.3s;
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
z-index: 1002;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
@import './bl-image.scss';
|
||||
@import './bl-message.scss';
|
||||
@import './bl-notification.scss';
|
||||
@import './bl-popper.scss';
|
||||
@import './bl-tip.scss';
|
||||
@import './bl-tooltip.scss';
|
||||
@import './bl-tree.scss';
|
||||
@import '../../views/doc/tree-docs-right-menu.scss';
|
||||
@import '../../views/doc/doc-tree-right-menu.scss';
|
||||
9
blossom-editor/src/renderer/src/assets/utils/test.ts
Normal file
9
blossom-editor/src/renderer/src/assets/utils/test.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const uuid = (): string => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
console.log(uuid())
|
||||
@ -433,3 +433,11 @@ export const isBase64Img = (image: string) => {
|
||||
let prefix = image.substring(0, Math.max(image.indexOf(','), 0))
|
||||
return prefix.startsWith('data:image') && prefix.endsWith('base64')
|
||||
}
|
||||
|
||||
export const uuid = (): string => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
fontWeight: props.weight
|
||||
}">
|
||||
<!-- {{ !!slots.default }}| -->
|
||||
<span v-if="props.icon" :class="['tag-iconbl iconbl', props.icon, !!slots.default ? 'tag-icon-margin' : '']" />
|
||||
<span v-if="props.icon" :class="['tag-iconbl iconbl', props.icon]" />
|
||||
<span class="tag-content">
|
||||
<slot />
|
||||
</span>
|
||||
@ -16,10 +16,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue'
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* background-color
|
||||
@ -54,7 +50,7 @@ const props = defineProps({
|
||||
<style scoped lang="scss">
|
||||
.tag-root {
|
||||
@include flex(row, center, center);
|
||||
@include themeShadow(2px 2px 3px 0 #bbbbbb, 1px 2px 3px #0F0F0F);
|
||||
@include themeShadow(2px 1px 3px 0 #bbbbbb, 1px 1px 3px #0f0f0f);
|
||||
border-radius: 4px;
|
||||
padding: 1px 4px;
|
||||
margin: 3px;
|
||||
@ -67,10 +63,6 @@ const props = defineProps({
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tag-icon-margin {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.tag-content {
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ import blossomIcons from '@renderer/assets/iconfont/blossom/iconfont.json'
|
||||
import weblogIcons from '@renderer/assets/iconfont/weblogo/iconfont.json'
|
||||
|
||||
onMounted(() => {
|
||||
document.title = 'Blossom 图标库'
|
||||
blossom.value = blossomIcons.glyphs
|
||||
weblogo.value = weblogIcons.glyphs.sort((w1, w2) => {
|
||||
return w1.font_class.localeCompare(w2.font_class)
|
||||
@ -125,8 +126,7 @@ const copyIcon = (icon: string) => {
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
background-color: var(--bl-html-color);
|
||||
overflow: scroll;
|
||||
overflow-y: overlay;
|
||||
overflow-y: scroll;
|
||||
|
||||
.icon-item {
|
||||
@include flex(column, space-between, center);
|
||||
|
||||
@ -242,7 +242,7 @@ const showWebCollectCard = (card: boolean) => {
|
||||
|
||||
.web-item-container {
|
||||
@include box(100%, calc(100% - 31px));
|
||||
overflow-y: overlay;
|
||||
overflow-y: scroll;
|
||||
|
||||
.placeholder {
|
||||
@include font(15px, 300);
|
||||
|
||||
@ -36,19 +36,19 @@ router.addRoute({
|
||||
component: Index,
|
||||
meta: { keepAlive: true },
|
||||
children: [
|
||||
{ path: '/home', name: 'Home', component: Home, meta: { keepAlive: true } },
|
||||
{ path: '/settingIndex', name: 'SettingIndex', component: SettingIndex, meta: { keepAlive: false } },
|
||||
{ path: '/home', name: 'Home', component: Home, meta: { keepAlive: true, title: 'Blossom 首页' } },
|
||||
{ path: '/settingIndex', name: 'SettingIndex', component: SettingIndex, meta: { keepAlive: false, title: 'Blossom 设置' } },
|
||||
// 功能页面
|
||||
{ path: '/articleIndex', name: 'ArticleIndex', component: ArticleIndex, meta: { keepAlive: true } },
|
||||
{ path: '/pictureIndex', name: 'PictureIndex', component: PictureIndex, meta: { keepAlive: true } },
|
||||
{ path: '/todoIndex', name: 'TodoIndex', component: TodoIndex, meta: { keepAlive: true } },
|
||||
{ path: '/noteIndex', name: 'NoteIndex', component: NoteIndex, meta: { keepAlive: false } },
|
||||
{ path: '/planIndex', name: 'PlanIndex', component: PlanIndex, meta: { keepAlive: false } },
|
||||
{ path: '/articleIndex', name: 'ArticleIndex', component: ArticleIndex, meta: { keepAlive: true, title: 'Blossom 文章编辑' } },
|
||||
{ path: '/pictureIndex', name: 'PictureIndex', component: PictureIndex, meta: { keepAlive: true, title: 'Blossom 资源库' } },
|
||||
{ path: '/todoIndex', name: 'TodoIndex', component: TodoIndex, meta: { keepAlive: true, title: 'Blossom 待办事项' } },
|
||||
{ path: '/noteIndex', name: 'NoteIndex', component: NoteIndex, meta: { keepAlive: false, title: 'Blossom 便签' } },
|
||||
{ path: '/planIndex', name: 'PlanIndex', component: PlanIndex, meta: { keepAlive: false, title: 'Blossom 日历计划' } },
|
||||
{
|
||||
path: '/iconListIndex',
|
||||
name: 'IconListIndex',
|
||||
component: IconListIndex,
|
||||
meta: { keepAlive: false },
|
||||
meta: { keepAlive: false, title: 'Blossom 图标库' },
|
||||
props: {
|
||||
window: false
|
||||
}
|
||||
@ -58,5 +58,10 @@ router.addRoute({
|
||||
|
||||
router.addRoute({ path: '/articleViewWindow', name: 'ArticleViewWindow', component: ArticleViewWindow, meta: { keepAlive: true } })
|
||||
router.addRoute({ path: '/iconListIndexWindow', name: 'IconListIndexWindow', component: IconListIndex, meta: { keepAlive: true } })
|
||||
router.addRoute({ path: '/articleReferenceWindow', name: 'ArticleReferenceWindow', component: ArticleReference, meta: { keepAlive: true } })
|
||||
router.addRoute({
|
||||
path: '/articleReferenceWindow',
|
||||
name: 'ArticleReferenceWindow',
|
||||
component: ArticleReference,
|
||||
meta: { keepAlive: true }
|
||||
})
|
||||
router.addRoute({ path: '/articleHistory', name: 'ArticleHistory', component: ArticleHistory, meta: { keepAlive: true } })
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
import { onBeforeUnmount, onMounted, watchEffect } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
/**
|
||||
* 元素拖拽
|
||||
* @param targetRef 拖拽时移动的元素
|
||||
* @param dragRef 拖拽时拖动的元素
|
||||
* @param regionRef 元素可以拖拽的范围
|
||||
*/
|
||||
export const useDraggable = (
|
||||
targetRef: Ref<HTMLElement | undefined>,
|
||||
dragRef: Ref<HTMLElement | undefined>,
|
||||
regionRef?: Ref<HTMLElement | undefined>
|
||||
// draggable: ComputedRef<boolean>
|
||||
) => {
|
||||
let transform = {
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
offsetY: 0
|
||||
}
|
||||
|
||||
const onMousedown = (e: MouseEvent) => {
|
||||
@ -22,27 +29,33 @@ export const useDraggable = (
|
||||
const targetWidth = targetRect.width
|
||||
const targetHeight = targetRect.height
|
||||
|
||||
const clientWidth = document.documentElement.clientWidth
|
||||
const clientHeight = document.documentElement.clientHeight
|
||||
let clientWidth = document.documentElement.clientWidth
|
||||
let clientHeight = document.documentElement.clientHeight
|
||||
let minLeft = -targetLeft + offsetX
|
||||
let minTop = -targetTop + offsetY
|
||||
|
||||
if (regionRef) {
|
||||
console.log(regionRef.value!.getBoundingClientRect())
|
||||
const rect = regionRef.value!.getBoundingClientRect()
|
||||
clientWidth = rect.width + rect.x
|
||||
clientHeight = rect.height + rect.y
|
||||
minLeft += rect.x
|
||||
minTop += rect.y
|
||||
} else {
|
||||
clientWidth = document.documentElement.clientWidth
|
||||
clientHeight = document.documentElement.clientHeight
|
||||
}
|
||||
|
||||
const minLeft = -targetLeft + offsetX
|
||||
const minTop = -targetTop + offsetY
|
||||
const maxLeft = clientWidth - targetLeft - targetWidth + offsetX
|
||||
const maxTop = clientHeight - targetTop - targetHeight + offsetY
|
||||
|
||||
const onMousemove = (e: MouseEvent) => {
|
||||
const moveX = Math.min(
|
||||
Math.max(offsetX + e.clientX - downX, minLeft),
|
||||
maxLeft
|
||||
)
|
||||
const moveY = Math.min(
|
||||
Math.max(offsetY + e.clientY - downY, minTop),
|
||||
maxTop
|
||||
)
|
||||
const moveX = Math.min(Math.max(offsetX + e.clientX - downX, minLeft), maxLeft)
|
||||
const moveY = Math.min(Math.max(offsetY + e.clientY - downY, minTop), maxTop)
|
||||
|
||||
transform = {
|
||||
offsetX: moveX,
|
||||
offsetY: moveY,
|
||||
offsetY: moveY
|
||||
}
|
||||
targetRef.value!.style.transform = `translate(${moveX}px, ${moveY}px)`
|
||||
}
|
||||
|
||||
@ -45,8 +45,8 @@ const DEFAULT_DARK = {
|
||||
'--el-color-primary-light-7': 'rgba(165, 184, 20, 0.3)',
|
||||
'--el-color-primary-light-8': 'rgba(165, 184, 20, 0.2)',
|
||||
'--el-color-primary-light-9': 'rgba(165, 184, 20, 0.1)',
|
||||
'--bl-tag-color-open': '#476716',
|
||||
'--bl-tag-color-subject': '#D9B049',
|
||||
'--bl-tag-color-open': '#B3ADA0',
|
||||
'--bl-tag-color-subject': '#B3ADA0',
|
||||
'--bl-tag-color-toc': '#8E8C8E',
|
||||
'--bl-todo-wait-color': '#505050A5',
|
||||
'--bl-todo-proc-color': '#fba85f6b',
|
||||
|
||||
@ -2,6 +2,15 @@ import { onBeforeUnmount, onMounted, watchEffect } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { Local } from '@renderer/assets/utils/storage'
|
||||
|
||||
/**
|
||||
* persistent: 是否持久化
|
||||
* keyOne: 组件1持久化 key,
|
||||
* keyTwo: 组件2持久化 key,
|
||||
* defaultOne: 组件1默认宽度,
|
||||
* defaultTwo: 组件2默认宽度,
|
||||
* maxOne: 组件1最大宽度,
|
||||
* minOne: 组件1最小宽度
|
||||
*/
|
||||
type Option = {
|
||||
persistent: boolean
|
||||
keyOne: string
|
||||
@ -13,18 +22,15 @@ type Option = {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拖转改变布局的功能
|
||||
* 左右拖动改变布局的功能
|
||||
*
|
||||
* @param oneRef 拖转影响到的一个组件
|
||||
* @param twoRef 拓展影响到的另一个组件
|
||||
* @param resizeDividerRef 拖动条
|
||||
* @param oneRef 拖动影响到组件1
|
||||
* @param twoRef 拖动影响到组件2
|
||||
* @param resizeDividerRef 拖动条对象
|
||||
* @param twoPendantRef 另一个组件可能存在的挂件
|
||||
* @param options 拖转是否持久化 {
|
||||
* persistent: 是否持久化
|
||||
*
|
||||
* }
|
||||
* @param options 拖动配置
|
||||
*/
|
||||
export const useResize = (
|
||||
export const useResizeVertical = (
|
||||
oneRef: Ref<HTMLElement | undefined>,
|
||||
twoRef: Ref<HTMLElement | undefined>,
|
||||
resizeDividerRef: Ref<HTMLElement | undefined>,
|
||||
@ -16,6 +16,8 @@ export interface ViewStyle {
|
||||
todoStatExpand: boolean
|
||||
// 展开收起首页网页收藏
|
||||
webCollectExpand: boolean
|
||||
// 是否显示文件夹收藏图标
|
||||
isShowFolderStarTag: boolean
|
||||
// 是否显示专题样式
|
||||
isShowSubjectStyle: boolean
|
||||
// 是否以卡片方式显示文章收藏
|
||||
@ -121,9 +123,10 @@ export const useConfigStore = defineStore('configStore', {
|
||||
treeDocsFontSize: '13px',
|
||||
todoStatExpand: true,
|
||||
webCollectExpand: true,
|
||||
isShowSubjectStyle: true,
|
||||
isShowFolderStarTag: true,
|
||||
isShowSubjectStyle: false,
|
||||
isHomeStarCard: true,
|
||||
isHomeSubjectCard: true,
|
||||
isHomeSubjectCard: false,
|
||||
isWebCollectCard: true,
|
||||
isGlobalShadow: false,
|
||||
isShowTryuseBtn: true,
|
||||
|
||||
@ -97,7 +97,14 @@ export const DEFAULT_USER_INFO = {
|
||||
WEB_GONG_WANG_AN_BEI: '',
|
||||
WEB_BLOG_URL_ERROR_TIP_SHOW: 1,
|
||||
WEB_BLOG_LINKS: '',
|
||||
WEB_BLOG_SUBJECT_TITLE: false
|
||||
WEB_BLOG_SUBJECT_TITLE: false,
|
||||
WEB_BLOG_COLOR: '',
|
||||
WEB_BLOG_SHOW_ARTICLE_NAME: true,
|
||||
WEB_BLOG_WATERMARK_ENABLED: false,
|
||||
WEB_BLOG_WATERMARK_CONTENT: '',
|
||||
WEB_BLOG_WATERMARK_FONTSIZE: 15,
|
||||
WEB_BLOG_WATERMARK_COLOR: '',
|
||||
WEB_BLOG_WATERMARK_GAP: 100
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,6 +134,9 @@ export const useUserStore = defineStore('userStore', {
|
||||
userinfo: (Local.get(userinfoKey) as Userinfo) || initUserinfo()
|
||||
}),
|
||||
getters: {
|
||||
currentUserId(state) {
|
||||
return state.userinfo.id
|
||||
},
|
||||
/**
|
||||
* 是否登录
|
||||
*/
|
||||
|
||||
@ -38,6 +38,7 @@ const includeRouter = ref<any>(['settingIndex'])
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
(newRoute) => {
|
||||
document.title = newRoute.meta.title as string
|
||||
if (newRoute.meta.keepAlive && includeRouter.value.indexOf(newRoute.name) === -1) {
|
||||
includeRouter.value.push(newRoute.name)
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ const cancelDownload = async () => {
|
||||
@include flex(column, flex-start, flex-start);
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
overflow-x: overlay;
|
||||
overflow-x: scroll;
|
||||
padding: 10px;
|
||||
|
||||
.bak-item {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="duration">有效时长(分钟) <el-input-number v-model="duration" min="1" controls-position="right"></el-input-number></div>
|
||||
<div class="duration">有效时长(分钟) <el-input-number v-model="duration" :min="1" controls-position="right"></el-input-number></div>
|
||||
<div class="expire">将在 {{ expire }} 后失效。</div>
|
||||
<div class="btns">
|
||||
<el-button size="default" type="primary" @click="create">创建访问链接</el-button>
|
||||
|
||||
@ -64,6 +64,7 @@ const initEditor = (_doc?: string) => {
|
||||
const getLogs = (articleId: string | number) => {
|
||||
articleInfoApi({ id: articleId, showToc: false, showMarkdown: true, showHtml: false }).then((resp) => {
|
||||
article.value = resp.data
|
||||
document.title = `《${resp.data.name}》编辑历史`
|
||||
})
|
||||
articleLogsApi({ articleId: articleId }).then((resp) => {
|
||||
historyList.value = resp.data
|
||||
@ -106,8 +107,8 @@ onMounted(() => {
|
||||
|
||||
.history-list {
|
||||
@include box(100%, calc(100% - 80px - 21px));
|
||||
overflow-y: overlay;
|
||||
padding: 10px 15px 10px 10px;
|
||||
overflow-y: scroll;
|
||||
|
||||
.history-item {
|
||||
@include box(100%, 55px);
|
||||
|
||||
@ -8,14 +8,14 @@
|
||||
|
||||
<div class="content">
|
||||
<el-upload
|
||||
multiple
|
||||
class="article-upload"
|
||||
ref="uploadRef"
|
||||
name="file"
|
||||
multiple
|
||||
:action="serverStore.serverUrl + articleImportApiUrl"
|
||||
:data="{ pid: porps.doc.i }"
|
||||
:data="{ pid: porps.doc.i, batchId: importBatch }"
|
||||
:headers="{ Authorization: 'Bearer ' + userStore.auth.token }"
|
||||
:on-change="onChange"
|
||||
:show-file-list="true"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="onUploadSeccess"
|
||||
:on-error="onError"
|
||||
@ -38,6 +38,7 @@ import type { UploadInstance } from 'element-plus'
|
||||
import { articleImportApiUrl } from '@renderer/api/blossom'
|
||||
import { useUserStore } from '@renderer/stores/user'
|
||||
import { useServerStore } from '@renderer/stores/server'
|
||||
import { uuid } from '@renderer/assets/utils/util'
|
||||
import { onChange, beforeUpload, onUploadSeccess, onError } from './scripts/article-import'
|
||||
|
||||
const userStore = useUserStore()
|
||||
@ -50,6 +51,8 @@ const porps = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const importBatch = ref(uuid())
|
||||
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
|
||||
const submitUpload = () => {
|
||||
@ -66,21 +69,21 @@ const submitUpload = () => {
|
||||
.content {
|
||||
padding: 20px;
|
||||
|
||||
.upload-tip {
|
||||
border: 1px solid var(--el-border-color);
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: rgb(188, 55, 55);
|
||||
}
|
||||
// .upload-tip {
|
||||
// border: 1px solid var(--el-border-color);
|
||||
// padding: 5px 10px;
|
||||
// border-radius: 5px;
|
||||
// color: rgb(188, 55, 55);
|
||||
// }
|
||||
|
||||
.article-upload {
|
||||
:deep(.el-upload-list) {
|
||||
li {
|
||||
transition: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// .article-upload {
|
||||
// :deep(.el-upload-list) {
|
||||
// li {
|
||||
// transition: none;
|
||||
// margin-bottom: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
</bl-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resize-docs-divider" ref="ResizeDocsDividerRef"></div>
|
||||
<div class="resize-divider-vertical" ref="ResizeDocsDividerRef"></div>
|
||||
<!-- editor -->
|
||||
<div class="editor-container" ref="EditorContainerRef" v-loading="editorLoading" element-loading-text="正在读取文章内容...">
|
||||
<div class="editor-tools">
|
||||
@ -93,7 +93,7 @@
|
||||
</div>
|
||||
<div class="gutter-holder" ref="GutterHolderRef"></div>
|
||||
<div class="editor-codemirror" ref="EditorRef" @click.right="handleEditorClickRight"></div>
|
||||
<div class="resize-divider" ref="ResizeEditorDividerRef"></div>
|
||||
<div class="resize-divider-vertical editor-resize-divider" ref="ResizeEditorDividerRef"></div>
|
||||
<div class="preview-marked bl-preview" ref="PreviewRef" v-html="articleHtml"></div>
|
||||
</div>
|
||||
|
||||
@ -207,7 +207,7 @@ import { treeToInfo, provideKeyDocInfo, provideKeyCurArticleInfo, isArticle } fr
|
||||
import { TempTextareaKey, ArticleReference, parseTocAsync } from './scripts/article'
|
||||
import type { Toc } from './scripts/article'
|
||||
import { beforeUpload, onError, picCacheWrapper, picCacheRefresh, uploadForm, uploadDate } from '@renderer/views/picture/scripts/picture'
|
||||
import { useResize } from './scripts/editor-preview-resize'
|
||||
import { useResizeVertical } from '@renderer/scripts/resize-devider-vertical'
|
||||
// codemirror
|
||||
import { CmWrapper } from './scripts/codemirror'
|
||||
// marked
|
||||
@ -354,7 +354,7 @@ const exitView = () => {
|
||||
autoSave()
|
||||
}
|
||||
|
||||
const { hideOne, resotreOne } = useResize(DocsRef, EditorContainerRef, ResizeDocsDividerRef, undefined, {
|
||||
const { hideOne, resotreOne } = useResizeVertical(DocsRef, EditorContainerRef, ResizeDocsDividerRef, undefined, {
|
||||
persistent: true,
|
||||
keyOne: 'article_docs_width',
|
||||
keyTwo: 'article_editor_preview_width',
|
||||
@ -363,7 +363,7 @@ const { hideOne, resotreOne } = useResize(DocsRef, EditorContainerRef, ResizeDoc
|
||||
maxOne: 700,
|
||||
minOne: 250
|
||||
})
|
||||
useResize(EditorRef, PreviewRef, ResizeEditorDividerRef, EditorOperatorRef)
|
||||
useResizeVertical(EditorRef, PreviewRef, ResizeEditorDividerRef, EditorOperatorRef)
|
||||
//#endregion
|
||||
|
||||
//#region ----------------------------------------< 图片管理 >--------------------------------------
|
||||
@ -939,6 +939,7 @@ const unbindKeys = () => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@renderer/assets/styles/bl-resize-divider.scss';
|
||||
@import '@renderer/assets/styles/bl-loading-spinner.scss';
|
||||
@import './styles/article-index.scss';
|
||||
@import './styles/article-view-absolute.scss';
|
||||
|
||||
@ -249,7 +249,6 @@ import { treeToInfo, provideKeyDocInfo, provideKeyCurArticleInfo, isArticle } fr
|
||||
import { TempTextareaKey, ArticleReference, DocsEditorStyle, parseTocAsync } from './scripts/article'
|
||||
import type { Toc } from './scripts/article'
|
||||
import { beforeUpload, onError, picCacheWrapper, picCacheRefresh, uploadForm, uploadDate } from '@renderer/views/picture/scripts/picture'
|
||||
import { useResize } from './scripts/editor-preview-resize'
|
||||
// codemirror
|
||||
import { CmWrapper } from './scripts/codemirror'
|
||||
// marked
|
||||
@ -401,8 +400,6 @@ const enterView = () => {
|
||||
const exitView = () => {
|
||||
autoSave()
|
||||
}
|
||||
|
||||
useResize(EditorRef, PreviewRef, ResizeDividerRef, EditorOperatorRef)
|
||||
//#endregion
|
||||
|
||||
//#region ----------------------------------------< 图片管理 >--------------------------------------
|
||||
|
||||
@ -218,7 +218,6 @@ import { treeToInfo, provideKeyDocInfo, provideKeyCurArticleInfo, isArticle } fr
|
||||
import { TempTextareaKey, ArticleReference, DocsEditorStyle, parseTocAsync } from './scripts/article'
|
||||
import type { Toc } from './scripts/article'
|
||||
import { beforeUpload, onError, picCacheWrapper, picCacheRefresh, uploadForm, uploadDate } from '@renderer/views/picture/scripts/picture'
|
||||
import { useResize } from './scripts/editor-preview-resize'
|
||||
// codemirror
|
||||
import { CmWrapper } from './scripts/codemirror'
|
||||
// marked
|
||||
@ -367,8 +366,6 @@ const enterView = () => {
|
||||
const exitView = () => {
|
||||
autoSave()
|
||||
}
|
||||
|
||||
useResize(EditorRef, PreviewRef, ResizeDividerRef, EditorOperatorRef)
|
||||
//#endregion
|
||||
|
||||
//#region ----------------------------------------< 图片管理 >--------------------------------------
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
|
||||
<!-- 星标 -->
|
||||
<div class="stat-star">
|
||||
<div v-if="!curIsStar" :class="['iconbl bl-star-line', curDocDialogType == 'add' || curIsFolder ? 'disabled' : '']" @click="star(1)"></div>
|
||||
<div v-if="!curIsStar" :class="['iconbl bl-star-line', curDocDialogType == 'add']" @click="star(1)"></div>
|
||||
<div v-else class="iconbl bl-star-fill" @click="star(0)"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -141,9 +141,9 @@
|
||||
|
||||
<!-- -->
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="docForm.type" style="width: 106px" :disabled="curDocDialogType == 'upd'">
|
||||
<el-radio-button :label="1">文件夹</el-radio-button>
|
||||
<el-radio-button :label="3">文章</el-radio-button>
|
||||
<el-radio-group v-model="docForm.type" style="width: 110px" :disabled="curDocDialogType == 'upd'">
|
||||
<el-radio-button :value="1">文件夹</el-radio-button>
|
||||
<el-radio-button :value="3">文章</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
@ -151,13 +151,13 @@
|
||||
<el-form-item label="主色调">
|
||||
<el-input
|
||||
v-model="docForm.color"
|
||||
:style="{ width: '245px', '--el-input-text-color': '#000000', '--el-input-bg-color': docForm.color }"
|
||||
:style="{ width: '242px', '--el-input-text-color': '#000000', '--el-input-bg-color': docForm.color }"
|
||||
placeholder="主色调 #FFFFFF">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="docForm.sort" style="width: 106px" />
|
||||
<el-input-number v-model="docForm.sort" style="width: 110px" />
|
||||
</el-form-item>
|
||||
<!-- -->
|
||||
<!-- <el-form-item label="封面">
|
||||
@ -165,7 +165,7 @@
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="图标">
|
||||
<el-input v-model="docForm.icon" style="width: 245px" placeholder="图标/图片http地址">
|
||||
<el-input v-model="docForm.icon" style="width: 242px" placeholder="图标/图片http地址">
|
||||
<template #append>
|
||||
<el-tooltip content="查看所有图标" effect="light" placement="top" :hide-after="0">
|
||||
<div style="cursor: pointer; font-size: 20px" @click="openNewIconWindow()">
|
||||
@ -207,14 +207,12 @@
|
||||
</div>
|
||||
|
||||
<div class="info-footer">
|
||||
<div>
|
||||
<el-button size="default" type="primary" :disabled="saveLoading" @click="saveDoc(DocFormRef)">
|
||||
<span class="iconbl bl-a-filechoose-line" /> 保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -222,9 +220,10 @@ import { ref, nextTick, inject, computed, watch, Ref } from 'vue'
|
||||
import { ElInput, ElMessageBox, FormInstance } from 'element-plus'
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { Document } from '@element-plus/icons-vue'
|
||||
import { provideKeyDocTree, getCDocsByPid, getDocById, checkLevel } from '@renderer/views/doc/doc'
|
||||
import { provideKeyDocTree, getCDocsByPid, getDocById } from '@renderer/views/doc/doc'
|
||||
import { useUserStore } from '@renderer/stores/user'
|
||||
import {
|
||||
folderStarApi,
|
||||
folderInfoApi,
|
||||
folderAddApi,
|
||||
folderUpdApi,
|
||||
@ -387,6 +386,10 @@ const formatStorePath = () => {
|
||||
}
|
||||
|
||||
const showStorePathWarning = ref(false)
|
||||
|
||||
/**
|
||||
* 填充文件夹路径
|
||||
*/
|
||||
const fillStorePath = (id: string, path: string = ''): void => {
|
||||
let doc = getDocById(id, docTreeData!.value)
|
||||
if (!doc) {
|
||||
@ -484,6 +487,12 @@ const star = (changeStarStatus: number) => {
|
||||
Notify.success(docForm.value.starStatus === 0 ? '取消 Star 成功' : 'Star 成功')
|
||||
})
|
||||
}
|
||||
if (curIsFolder.value) {
|
||||
docForm.value.starStatus = changeStarStatus
|
||||
folderStarApi({ id: docForm.value.id, starStatus: docForm.value.starStatus }).then(() => {
|
||||
Notify.success(docForm.value.starStatus === 0 ? '取消 Star 成功' : 'Star 成功')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@ -535,13 +544,9 @@ const saveDoc = async (formEl: FormInstance | undefined) => {
|
||||
await formEl.validate((valid, _fields) => {
|
||||
if (valid) {
|
||||
saveLoading.value = true
|
||||
if (!checkLevel(docForm.value.pid, docTreeData!.value)) {
|
||||
saveLoading.value = false
|
||||
return
|
||||
}
|
||||
const handleResp = (_: any) => {
|
||||
Notify.success(curDocDialogType.value === 'upd' ? `修改《${docForm.value.name}》成功` : `新增《${docForm.value.name}》成功`)
|
||||
emits('saved', curDocDialogType.value)
|
||||
emits('saved', curDocDialogType.value, docForm.value)
|
||||
}
|
||||
const handleFinally = () => setTimeout(() => (saveLoading.value = false), 300)
|
||||
// 新增文件夹
|
||||
@ -816,7 +821,7 @@ $height-form: calc(100% - #{$height-title} - #{$height-img} - #{$height-stat} -
|
||||
|
||||
.info-footer {
|
||||
@include box(100%, $height-footer);
|
||||
@include flex(row, space-between, center);
|
||||
@include flex(row, flex-end, center);
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
padding: 10px;
|
||||
text-align: right;
|
||||
|
||||
@ -111,7 +111,7 @@ const download = (id: string) => {
|
||||
@include flex(column, flex-start, flex-start);
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
overflow-x: overlay;
|
||||
overflow-x: scroll;
|
||||
padding: 10px;
|
||||
|
||||
.recycle-item {
|
||||
|
||||
@ -321,6 +321,7 @@ const windowResize = () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.title = 'Blossom 双链图表'
|
||||
init()
|
||||
windowResize()
|
||||
articleId = route.query.articleId as string
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<div :class="[viewStyle.isShowSubjectStyle ? (isSubject ? 'subject-title' : 'doc-title') : 'doc-title']">
|
||||
<bl-tag class="sort" v-show="props.trees.showSort" :bgColor="levelColor">
|
||||
{{ props.trees.s }}
|
||||
</bl-tag>
|
||||
<div class="doc-name">
|
||||
<img
|
||||
class="menu-icon-img"
|
||||
v-if="
|
||||
viewStyle.isShowArticleIcon &&
|
||||
isNotBlank(props.trees.icon) &&
|
||||
(props.trees.icon.startsWith('http') || props.trees.icon.startsWith('https')) &&
|
||||
!props.trees?.updn
|
||||
"
|
||||
:src="props.trees.icon" />
|
||||
<svg v-else-if="viewStyle.isShowArticleIcon && isNotBlank(props.trees.icon) && !props.trees?.updn" class="icon menu-icon" aria-hidden="true">
|
||||
<use :xlink:href="'#' + props.trees.icon"></use>
|
||||
</svg>
|
||||
<el-input
|
||||
v-if="props.trees?.updn"
|
||||
v-model="props.trees.n"
|
||||
:id="'article-doc-name-' + props.trees.i"
|
||||
@blur="blurArticleNameInput"
|
||||
@keyup.enter="blurArticleNameInput"
|
||||
style="width: 95%"></el-input>
|
||||
<div v-else class="name-wrapper" :style="nameWrapperStyle">
|
||||
{{ props.trees.n }}
|
||||
</div>
|
||||
<bl-tag v-for="tag in tags" style="margin-top: 5px" :bg-color="tag.bgColor" :icon="tag.icon">
|
||||
{{ tag.content }}
|
||||
</bl-tag>
|
||||
</div>
|
||||
<div v-if="level >= 2" class="folder-level-line" style="left: -20px"></div>
|
||||
<div v-if="level >= 3" class="folder-level-line" style="left: -30px"></div>
|
||||
<div v-if="level >= 4" class="folder-level-line" style="left: -40px"></div>
|
||||
<div
|
||||
v-if="viewStyle.isShowArticleType"
|
||||
v-for="(line, index) in tagLins"
|
||||
:key="line"
|
||||
:class="[line]"
|
||||
:style="{ left: -1 * (index + 1.5) * 4 + 'px' }"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { useConfigStore } from '@renderer/stores/config'
|
||||
import { isNotBlank } from '@renderer/assets/utils/obj'
|
||||
import { computedDocTitleColor } from '@renderer/views/doc/doc'
|
||||
import { articleUpdNameApi, folderUpdNameApi } from '@renderer/api/blossom'
|
||||
|
||||
const { viewStyle } = useConfigStore()
|
||||
|
||||
const props = defineProps({
|
||||
trees: { type: Object as PropType<DocTree>, default: {} },
|
||||
size: { type: Number, default: 14 },
|
||||
level: { type: Number, required: true }
|
||||
})
|
||||
|
||||
const levelColor = computed(() => {
|
||||
return computedDocTitleColor(props.level)
|
||||
})
|
||||
|
||||
const nameWrapperStyle = computed(() => {
|
||||
return {
|
||||
maxWidth: isNotBlank(props.trees.icon) ? 'calc(100% - 25px)' : '100%'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 是否是专题
|
||||
*/
|
||||
const isSubject = computed(() => {
|
||||
return props.trees.t?.includes('subject')
|
||||
})
|
||||
|
||||
/**
|
||||
* 计算标签, 并返回便签对象集合
|
||||
*/
|
||||
const tags = computed(() => {
|
||||
let icons: any = []
|
||||
props.trees.t?.forEach((tag) => {
|
||||
if (tag.toLocaleLowerCase() === 'subject') {
|
||||
icons.unshift({ content: '专题', bgColor: 'var(--bl-tag-color-subject)', icon: 'bl-a-lowerrightpage-line' })
|
||||
} else if (tag.toLocaleLowerCase() === 'toc') {
|
||||
if (!viewStyle.isShowArticleTocTag) {
|
||||
return
|
||||
}
|
||||
icons.push({ content: 'TOC', bgColor: 'var(--bl-tag-color-toc)' })
|
||||
} else if (viewStyle.isShowArticleCustomTag) {
|
||||
icons.push({ content: tag })
|
||||
}
|
||||
})
|
||||
if (props.trees.o === 1 && props.trees.ty != 3) {
|
||||
if (viewStyle.isShowFolderOpenTag) {
|
||||
icons.unshift({ bgColor: 'var(--bl-tag-color-open)', icon: 'bl-cloud-line' })
|
||||
}
|
||||
}
|
||||
return icons
|
||||
})
|
||||
|
||||
// 竖条状态标识
|
||||
const tagLins = computed(() => {
|
||||
let lines: string[] = []
|
||||
if (props.trees.star === 1) {
|
||||
lines.push('star-line')
|
||||
}
|
||||
if (props.trees.o === 1 && props.trees.ty === 3) {
|
||||
lines.push('open-line')
|
||||
}
|
||||
if (props.trees.vd === 1) {
|
||||
lines.push('sync-line')
|
||||
}
|
||||
return lines
|
||||
})
|
||||
|
||||
/**
|
||||
* 重命名文章
|
||||
*/
|
||||
const blurArticleNameInput = () => {
|
||||
if (props.trees.ty === 3) {
|
||||
articleUpdNameApi({ id: props.trees.i, name: props.trees.n }).then((_resp) => {
|
||||
props.trees.updn = false
|
||||
})
|
||||
} else {
|
||||
folderUpdNameApi({ id: props.trees.i, name: props.trees.n }).then((_resp) => {
|
||||
props.trees.updn = false
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$icon-size: 17px;
|
||||
|
||||
.doc-title {
|
||||
@include flex(row, flex-start, flex-start);
|
||||
width: 100%;
|
||||
padding-bottom: 1px;
|
||||
position: relative;
|
||||
|
||||
.doc-name {
|
||||
@include flex(row, flex-start, flex-start);
|
||||
@include themeBrightness(100%, 90%);
|
||||
@include ellipsis();
|
||||
font-size: inherit;
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
.menu-icon,
|
||||
.menu-icon-img {
|
||||
@include box($icon-size, $icon-size, $icon-size, $icon-size, $icon-size, $icon-size);
|
||||
margin-top: 5px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.menu-icon-img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.name-wrapper {
|
||||
@include ellipsis();
|
||||
}
|
||||
}
|
||||
|
||||
.sort {
|
||||
position: absolute;
|
||||
padding: 0 2px;
|
||||
right: 0px;
|
||||
top: 2px;
|
||||
z-index: 10;
|
||||
}
|
||||
.folder-level-line {
|
||||
@include box(1px, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.folder-level-line {
|
||||
background-color: var(--el-border-color);
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
// 专题样式, 包括边框和文字样式
|
||||
.subject-title {
|
||||
@include flex(row, flex-start, flex-start);
|
||||
@include themeShadow(2px 2px 10px 1px var(--el-color-primary-light-8), 2px 2px 10px 1px #131313);
|
||||
background: linear-gradient(135deg, var(--el-color-primary-light-7), var(--el-color-primary-light-8), var(--bl-html-color));
|
||||
max-width: calc(100% - 15px);
|
||||
min-width: calc(100% - 15px);
|
||||
padding: 2px 5px;
|
||||
margin: 5px 0 10px 0;
|
||||
border-radius: 7px;
|
||||
position: relative;
|
||||
|
||||
.doc-name {
|
||||
@include flex(row, flex-start, flex-start);
|
||||
@include themeBrightness(100%, 100%);
|
||||
color: var(--el-color-primary);
|
||||
align-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
.menu-icon,
|
||||
.menu-icon-img {
|
||||
@include box($icon-size, $icon-size, $icon-size, $icon-size, $icon-size, $icon-size);
|
||||
margin-top: 5px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.menu-icon-img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.name-wrapper {
|
||||
@include ellipsis();
|
||||
max-width: calc(100% - 25px);
|
||||
min-width: calc(100% - 25px);
|
||||
}
|
||||
}
|
||||
|
||||
.sort {
|
||||
position: absolute;
|
||||
right: -15px;
|
||||
}
|
||||
|
||||
.folder-level-line {
|
||||
@include box(1px, calc(100% + 15px));
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
// 在左侧显示
|
||||
.open-line,
|
||||
.star-line,
|
||||
.sync-line {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 60%;
|
||||
top: 20%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.star-line {
|
||||
@include themeBg(rgb(237, 204, 11), rgba(228, 195, 5, 0.724));
|
||||
}
|
||||
|
||||
.open-line {
|
||||
background: #79c20c71;
|
||||
}
|
||||
|
||||
.sync-line {
|
||||
background: #e8122479;
|
||||
}
|
||||
</style>
|
||||
@ -1,154 +1,26 @@
|
||||
<template>
|
||||
<div class="doc-workbench-root">
|
||||
<bl-col class="workbench-name" just="flex-start" align="flex-end" height="46px" v-show="curArticle !== undefined">
|
||||
<span>《{{ curArticle?.name }}》</span>
|
||||
<span style="font-size: 9px; padding-right: 5px">{{ curArticle?.id }}</span>
|
||||
</bl-col>
|
||||
|
||||
<bl-row class="wb-page-container">
|
||||
<bl-row class="wb-page-item" just="flex-start" align="flex-end" width="calc(100% - 16px)" height="44px">
|
||||
<el-tooltip
|
||||
content="文章引用网络"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="-5"
|
||||
:hide-after="0">
|
||||
<bl-row class="wb-page-item" just="flex-start" align="flex-end" height="44px">
|
||||
<el-tooltip content="文章引用网络" effect="light" popper-class="is-small" placement="top" :offset="-5" :hide-after="0">
|
||||
<div class="iconbl bl-correlation-line" @click="openArticleReferenceWindow()"></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="新增文件夹或文章"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<div class="iconbl bl-a-fileadd-line" @click="handleShowAddDocInfoDialog()"></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="刷新"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<div class="iconbl bl-a-cloudrefresh-line" @click="refreshDocTree()"></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="全文搜索"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="9"
|
||||
:hide-after="0">
|
||||
<el-tooltip content="全文搜索" effect="light" popper-class="is-small" placement="top" :offset="9" :hide-after="0">
|
||||
<div class="iconbl bl-search-line" @click="showSearch()"></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" popper-class="is-small" transition="none" placement="top" :show-arrow="false" :offset="5" :hide-after="0">
|
||||
<div class="iconbl bl-a-leftdirection-line" @click="emits('show-sort')"></div>
|
||||
<template #content>
|
||||
显示排序<br />
|
||||
<bl-row>
|
||||
<bl-tag :bgColor="SortLevelColor.ONE">一级</bl-tag>
|
||||
<bl-tag :bgColor="SortLevelColor.TWO">二级</bl-tag>
|
||||
</bl-row>
|
||||
<bl-row style="padding-bottom: 5px">
|
||||
<bl-tag :bgColor="SortLevelColor.THREE">三级</bl-tag>
|
||||
<bl-tag :bgColor="SortLevelColor.FOUR">四级</bl-tag>
|
||||
</bl-row>
|
||||
</template>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="备份记录"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<el-tooltip content="备份记录" effect="light" popper-class="is-small" placement="top" :offset="8" :hide-after="0">
|
||||
<div class="iconbl bl-a-cloudstorage-line" @click="handleShowBackupDialog"></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="文章回收站"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<el-tooltip content="文章回收站" effect="light" popper-class="is-small" placement="top" :offset="8" :hide-after="0">
|
||||
<div class="iconbl bl-delete-line" @click="handleShowRecycleDialog"></div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="查看收藏"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<div v-if="props.showStar">
|
||||
<div v-if="onlyStars" class="iconbl bl-star-fill" @click="changeOnlyStar()"></div>
|
||||
<div v-else class="iconbl bl-star-line" @click="changeOnlyStar()"></div>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="查看专题"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<div v-if="props.showSubject">
|
||||
<div v-if="onlySubject" class="iconbl bl-a-lowerrightpage-fill" @click="changeOnlySubject()"></div>
|
||||
<div v-else class="iconbl bl-a-lowerrightpage-line" @click="changeOnlySubject()"></div>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
content="查看公开"
|
||||
effect="light"
|
||||
popper-class="is-small"
|
||||
transition="none"
|
||||
placement="top"
|
||||
:show-arrow="false"
|
||||
:offset="8"
|
||||
:hide-after="0">
|
||||
<div v-if="props.showOpen">
|
||||
<div v-if="onlyOpen" class="iconbl bl-cloud-fill" @click="changeOnlyOpen()"></div>
|
||||
<div v-else class="iconbl bl-cloud-line" @click="changeOnlyOpen()"></div>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<!-- <el-tooltip content="文档快速编辑" effect="light" popper-class="is-small" placement="top" :offset="8" :hide-after="0">
|
||||
<div class="iconbl bl-article-line" @click=""></div>
|
||||
</el-tooltip> -->
|
||||
</bl-row>
|
||||
|
||||
<bl-col width="12px" height="30px" just="end" class="workbench-more" style="">
|
||||
<div class="iconbl bl-a-morevertical-line" @click="showMoreMenu"></div>
|
||||
</bl-col>
|
||||
</bl-row>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="isShowDocInfoDialog"
|
||||
width="535"
|
||||
top="100px"
|
||||
style="margin-left: 320px"
|
||||
:append-to-body="true"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
draggable>
|
||||
<ArticleInfo ref="ArticleInfoRef" @saved="savedCallback"></ArticleInfo>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
class="bl-dialog-fixed-body"
|
||||
v-model="isShowBackupDialog"
|
||||
@ -174,25 +46,13 @@
|
||||
draggable>
|
||||
<ArticleRecycle ref="ArticleRecycleRef"></ArticleRecycle>
|
||||
</el-dialog>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-show="moreMenu.show" class="tree-menu" :style="{ left: moreMenu.clientX + 'px', top: moreMenu.clientY + 'px', width: '120px' }">
|
||||
<div class="menu-content" style="border: none">
|
||||
<div @click="changeOnlyOpen"><span class="iconbl bl-cloud-line"></span>查看公开</div>
|
||||
<div @click="changeOnlySubject"><span class="iconbl bl-a-lowerrightpage-line"></span>查看专题</div>
|
||||
<div @click="changeOnlyStar"><span class="iconbl bl-star-line"></span>查看收藏</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, inject, onDeactivated } from 'vue'
|
||||
import { provideKeyCurArticleInfo, SortLevelColor } from '@renderer/views/doc/doc'
|
||||
import { ref, nextTick, onDeactivated } from 'vue'
|
||||
import { openNewArticleReferenceWindow } from '@renderer/assets/utils/electron'
|
||||
import { useLifecycle } from '@renderer/scripts/lifecycle'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import ArticleInfo from './ArticleInfo.vue'
|
||||
import ArticleBackup from './ArticleBackup.vue'
|
||||
import ArticleRecycle from './ArticleRecycle.vue'
|
||||
|
||||
@ -205,43 +65,9 @@ onDeactivated(() => {
|
||||
unbindKeys()
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
showOpen: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showSubject: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showStar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
//#region --------------------------------------------------< 控制台更多选项 >--------------------------------------------------
|
||||
const moreMenu = ref<RightMenu>({ show: false, clientX: 0, clientY: 0 })
|
||||
|
||||
/**
|
||||
* 显示右键菜单
|
||||
* @param doc 文档
|
||||
* @param event 事件
|
||||
*/
|
||||
const showMoreMenu = (event: MouseEvent) => {
|
||||
moreMenu.value.show = true
|
||||
nextTick(() => {
|
||||
let y = event.clientY
|
||||
if (document.body.clientHeight - event.clientY < 50) {
|
||||
y = event.clientY - 50
|
||||
}
|
||||
moreMenu.value = { show: true, clientX: event.clientX, clientY: y }
|
||||
setTimeout(() => {
|
||||
document.body.addEventListener('click', closeMoreMenu)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
const closeMoreMenu = (event: MouseEvent) => {
|
||||
if (event.target) {
|
||||
let isPrevent = (event.target as HTMLElement).getAttribute('data-bl-prevet')
|
||||
@ -257,36 +83,6 @@ const closeMoreMenu = (event: MouseEvent) => {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region --------------------------------------------------< 查询 >--------------------------------------------------
|
||||
const curArticle = inject(provideKeyCurArticleInfo)
|
||||
|
||||
const onlyOpen = ref<boolean>(false) // 只显示公开
|
||||
const onlySubject = ref<boolean>(false) // 只显示专题
|
||||
const onlyStars = ref<boolean>(false) // 只显示 star
|
||||
|
||||
const changeOnlyOpen = () => {
|
||||
onlyOpen.value = !onlyOpen.value
|
||||
onlySubject.value = false
|
||||
onlyStars.value = false
|
||||
refreshDocTree()
|
||||
}
|
||||
|
||||
const changeOnlySubject = () => {
|
||||
onlyOpen.value = false
|
||||
onlySubject.value = !onlySubject.value
|
||||
onlyStars.value = false
|
||||
refreshDocTree()
|
||||
}
|
||||
|
||||
const changeOnlyStar = () => {
|
||||
onlyOpen.value = false
|
||||
onlySubject.value = false
|
||||
onlyStars.value = !onlyStars.value
|
||||
refreshDocTree()
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region --------------------------------------------------< 新增窗口 >--------------------------------------------------
|
||||
const ArticleInfoRef = ref()
|
||||
const isShowDocInfoDialog = ref<boolean>(false)
|
||||
@ -302,23 +98,6 @@ const openArticleReferenceWindow = () => {
|
||||
openNewArticleReferenceWindow()
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台刷新文档列表
|
||||
*/
|
||||
const refreshDocTree = () => {
|
||||
emits('refreshDocTree', onlyOpen.value, onlySubject.value, onlyStars.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存后的回调
|
||||
* 1. 刷新菜单列表
|
||||
* 2. 关闭 dialog 页面
|
||||
*/
|
||||
const savedCallback = () => {
|
||||
isShowDocInfoDialog.value = false
|
||||
emits('refreshDocTree', onlyOpen.value, onlySubject.value, onlyStars.value)
|
||||
}
|
||||
|
||||
const showSearch = () => {
|
||||
emits('show-search')
|
||||
}
|
||||
@ -363,7 +142,7 @@ const unbindKeys = () => {
|
||||
}
|
||||
|
||||
//#endregion
|
||||
const emits = defineEmits(['refreshDocTree', 'show-sort', 'show-search'])
|
||||
const emits = defineEmits(['show-sort', 'show-search'])
|
||||
defineExpose({ handleShowBackupDialog })
|
||||
</script>
|
||||
|
||||
@ -387,14 +166,6 @@ defineExpose({ handleShowBackupDialog })
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
// 排序
|
||||
.bl-a-leftdirection-line {
|
||||
font-size: 27px;
|
||||
padding-bottom: 3px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
// 搜索
|
||||
.bl-search-line {
|
||||
font-size: 23px;
|
||||
@ -408,14 +179,6 @@ defineExpose({ handleShowBackupDialog })
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
// 刷新图标
|
||||
.bl-a-cloudrefresh-line,
|
||||
.bl-a-fileadd-line {
|
||||
&:active {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.bl-correlation-line {
|
||||
font-size: 40px;
|
||||
padding-bottom: 0px;
|
||||
|
||||
@ -57,6 +57,7 @@ const toScroll = (id: string) => {
|
||||
const initPreview = (articleId: string) => {
|
||||
articleInfoApi({ id: articleId, showToc: false, showMarkdown: false, showHtml: true }).then((resp) => {
|
||||
article.value = resp.data
|
||||
document.title = `《${resp.data.name}》`
|
||||
nextTick(() => initToc())
|
||||
})
|
||||
}
|
||||
@ -87,7 +88,7 @@ onMounted(() => {
|
||||
@include box(100%, 100%);
|
||||
font-size: 15px;
|
||||
padding: 30px;
|
||||
overflow-y: overlay;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
line-height: 23px;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user