Compare commits

...

6 Commits

Author SHA1 Message Date
xiaozzzi
7b1c898b90 fix: 修复首页图表样式 2024-04-10 23:19:28 +08:00
xiaozzzi
20361dceb6 fix: 修改文章元信息也会计为每日编辑文章数的问题 2024-04-10 23:19:09 +08:00
xiaozzzi
a9b864efcb pref: 优化首页专题样式 2024-04-10 21:02:45 +08:00
xiaozzzi
a718002501 fix: 修复专题的查询逻辑 2024-04-10 20:53:09 +08:00
xiaozzzi
7d14414605 feat: 更多的博客配置项 2024-04-10 20:44:00 +08:00
xiaozzzi
b1d7721edc pref: 优化文章与文件夹收藏图标 2024-04-09 16:55:25 +08:00
24 changed files with 417 additions and 56 deletions

View File

@ -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"),
;
/**

View File

@ -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;
/**

View File

@ -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);
/**
* 修改某段日期内修改的文章数据
*

View File

@ -19,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;
@ -188,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());

View File

@ -118,6 +118,10 @@ public class ArticleEntity extends AbstractPOJO implements Serializable {
* 用户ID
*/
private Long userId;
/**
* 文章内容的修改时间
*/
private Date updMarkdownTime;
//region ============================== 非数据库字段 ==============================
/**

View File

@ -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());
}
}
}

View File

@ -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);

View File

@ -9,7 +9,6 @@ import com.blossom.backend.server.article.draft.pojo.ArticleUpdTagReq;
import com.blossom.backend.server.doc.DocService;
import com.blossom.backend.server.folder.pojo.*;
import com.blossom.backend.server.utils.DocUtil;
import com.blossom.common.base.enums.YesNo;
import com.blossom.common.base.exception.XzException404;
import com.blossom.common.base.pojo.DelReq;
import com.blossom.common.base.pojo.R;
@ -46,7 +45,7 @@ public class FolderController {
if (userId == null) {
return R.ok(new ArrayList<>());
}
return R.ok(baseService.subjects(userId, YesNo.YES));
return R.ok(baseService.subjects(userId, true, false));
}
/**
@ -55,8 +54,8 @@ public class FolderController {
* @param starStatus 公开状态
*/
@GetMapping("/subjects")
public R<List<FolderSubjectRes>> listSubject(@RequestParam("starStatus") Integer starStatus) {
return R.ok(baseService.subjects(AuthContext.getUserId(), YesNo.getValue(starStatus)));
public R<List<FolderSubjectRes>> listSubject() {
return R.ok(baseService.subjects(AuthContext.getUserId(), false, true));
}
/**

View File

@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@ -56,33 +55,38 @@ public class FolderService extends ServiceImpl<FolderMapper, FolderEntity> {
* @param userId 用户ID
* @param starStatus 公开状态
*/
public List<FolderSubjectRes> subjects(Long userId, @NotNull YesNo starStatus) {
public List<FolderSubjectRes> subjects(Long userId, boolean openStatus, boolean starStatus) {
// 1. 查询所有专题
FolderEntity where = new FolderEntity();
where.setTags(TagEnum.subject.name());
where.setUserId(userId);
where.setStarStatus(starStatus.getValue());
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. 查询这些文件夹下的所有文章
ArticleEntity articleWhere = new ArticleEntity();
articleWhere.setPids(allOpenSubjectIds);
articleWhere.setPids(allSubjectIds);
articleWhere.setUserId(userId);
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);
// 默认专题字数
@ -90,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())) {

View File

@ -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

View File

@ -641,4 +641,7 @@ CREATE TABLE IF NOT EXISTS `base_user_param`
COLLATE = utf8mb4_bin;
-- Code that might be wrong goes last
alter table blossom_folder add column star_status tinyint(1) NOT NULL DEFAULT 0 COMMENT '收藏 0:否,1:是';
-- 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 '文章内容的修改时间';

View File

@ -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
}
}

View File

@ -125,7 +125,7 @@
<div class="tree-menu-level2" :style="rMenuLevel2">
<div v-if="curDoc.o === 0" @click="open(1)"><span class="iconbl bl-a-cloudupload-line"></span>公开{{ curDocType }}</div>
<div v-if="curDoc.o === 1" @click="open(0)"><span class="iconbl bl-a-clouddownload-line"></span>取消{{ curDocType }}公开</div>
<div v-if="curDoc.star === 0" @click="star(1)"><span class="iconbl bl-star-fill"></span>收藏{{ curDocType }}</div>
<div v-if="curDoc.star === 0" @click="star(1)"><span class="iconbl bl-star-line"></span>收藏{{ curDocType }}</div>
<div v-if="curDoc.star === 1" @click="star(0)"><span class="iconbl bl-star-line"></span>取消收藏{{ curDocType }}</div>
<div v-if="curDoc.ty === 3 && !curDoc.t.includes('toc')" @click="addArticleTag('toc')">
<span class="iconbl bl-list-ordered"></span>设为专题目录

View File

@ -56,7 +56,7 @@ let maxWords = 0
const subjects = ref<any>([])
const getSubjects = () => {
subjectsApi({ starStatus: 1 }).then((resp) => {
subjectsApi().then((resp) => {
subjects.value = resp.data
if (!isEmpty(resp.data)) {
maxWords = resp.data

View File

@ -76,7 +76,7 @@
<ArticleSubjects></ArticleSubjects>
</bl-col>
<bl-col width="100%" height="calc(100% - 45px - 270px - 330px)">
<bl-col width="100%" height="calc(100% - 33px - 270px - 330px)">
<bl-row class="container-name">待办事项</bl-row>
<bl-row class="container-sub-name">Todo List</bl-row>
<bl-row style="padding-bottom: 10px; height: calc(100% - 80px)">

View File

@ -4,24 +4,24 @@
<el-tab-pane label="客户端配置" name="client">
<div class="tab-content"><ConfigClient></ConfigClient></div>
</el-tab-pane>
<el-tab-pane label="服务器配置" name="server" :lazy="true" v-if="userStore.userinfo.type === 1">
<el-tab-pane label="服务器配置" name="server" :lazy="true" v-if="userStore.userinfo.type === 1 && userStore.isLogin">
<div class="tab-content"><ConfigServer ref="ConfigServerRef"></ConfigServer></div>
</el-tab-pane>
<el-tab-pane label="博客配置" name="blog" :lazy="true">
<el-tab-pane label="博客配置" name="blog" :lazy="true" v-if="userStore.isLogin">
<div class="tab-content"><ConfigBlog ref="ConfigBlogRef"></ConfigBlog></div>
</el-tab-pane>
<el-tab-pane label="修改个人信息" name="userinfo" :lazy="true">
<el-tab-pane label="修改个人信息" name="userinfo" :lazy="true" v-if="userStore.isLogin">
<div class="tab-content"><ConfigUserinfo ref="ConfigUserinfoRef"></ConfigUserinfo></div>
</el-tab-pane>
<el-tab-pane label="修改登录密码" name="password" :lazy="true">
<el-tab-pane label="修改登录密码" name="password" :lazy="true" v-if="userStore.isLogin">
<div class="tab-content"><ConfigUpdPwd></ConfigUpdPwd></div>
</el-tab-pane>
<el-tab-pane label="添加使用账号" name="adduser" :lazy="true" v-if="userStore.userinfo.type === 1">
<el-tab-pane label="添加使用账号" name="adduser" :lazy="true" v-if="userStore.userinfo.type === 1 && userStore.isLogin">
<div class="tab-content">
<ConfigAddUser></ConfigAddUser>
</div>
</el-tab-pane>
<el-tab-pane label="实时访问流量" name="flow" v-if="userStore.isLogin && userStore.userinfo.type === 1" :lazy="true">
<el-tab-pane label="实时访问流量" name="flow" :lazy="true" v-if="userStore.isLogin && userStore.userinfo.type === 1 && userStore.isLogin">
<SentinelResources></SentinelResources>
</el-tab-pane>
</el-tabs>

View File

@ -25,6 +25,30 @@
</div>
</el-form-item>
<el-divider></el-divider>
<el-form-item label="博客主题色">
<bl-row class="colors">
<el-input
size="default"
v-model="userParamForm.WEB_BLOG_COLOR"
:style="{
width: '180px',
marginRight: '15px',
'--el-input-text-color': userParamForm.WEB_BLOG_COLOR,
'--el-input-border-color': userParamForm.WEB_BLOG_COLOR
}"></el-input>
<el-color-picker
size="default"
popper-class="theme-color-picker"
v-model="userParamForm.WEB_BLOG_COLOR"
color-format="rgb"
@change="changeBlogColor" />
<div class="color-item" v-for="color in colors" :key="color" :style="{ backgroundColor: color }" @click="changeBlogColor(color)"></div>
</bl-row>
<div class="conf-tip">博客主题色主要影响专题样式颜色以及文章中的链接颜色</div>
</el-form-item>
<el-form-item label="博客名称">
<el-input size="default" v-model="userParamForm.WEB_LOGO_NAME" @change="(cur: any) => updParam('WEB_LOGO_NAME', cur)"></el-input>
<div class="conf-tip">博客左上角名称以及在浏览器标签中的名称</div>
@ -35,6 +59,17 @@
<div class="conf-tip">博客左上角 Logo 的访问地址以及在浏览器标签中的 Logo</div>
</el-form-item>
<el-form-item label="是否显示文章标题">
<bl-row>
<el-switch
v-model="userParamForm.WEB_BLOG_SHOW_ARTICLE_NAME"
size="default"
style="margin-right: 10px"
@change="(cur: boolean) => updParam('WEB_BLOG_SHOW_ARTICLE_NAME', cur ? '1' : '0')" />
</bl-row>
<div class="conf-tip">是否在文章内容的顶部显示文章的标题</div>
</el-form-item>
<el-form-item label="开启专题特殊样式">
<bl-row>
<el-switch
@ -46,19 +81,83 @@
<div class="conf-tip">是否在博客文档列表中以特殊的样式显示专题</div>
</el-form-item>
<el-form-item label="自定义信息">
<el-form-item label="开启文章水印">
<bl-row>
<el-switch
size="default"
v-model="userParamForm.WEB_BLOG_WATERMARK_ENABLED"
@change="(cur: boolean) => updParam('WEB_BLOG_WATERMARK_ENABLED', cur ? '1' : '0')" />
</bl-row>
<div class="conf-tip">是否开启博客文章背景文字水印</div>
<div class="conf-watermark" v-if="userParamForm.WEB_BLOG_WATERMARK_ENABLED">
<bl-row>
<bl-row width="60%">
<div class="label">水印内容</div>
<el-input
size="default"
v-model="userParamForm.WEB_BLOG_WATERMARK_CONTENT"
@change="(cur: any) => updParam('WEB_BLOG_WATERMARK_CONTENT', cur)"></el-input>
</bl-row>
<bl-row width="40%">
<div class="label">水印大小</div>
<el-input-number
size="default"
v-model="userParamForm.WEB_BLOG_WATERMARK_FONTSIZE"
@change="(cur: any) => updParam('WEB_BLOG_WATERMARK_FONTSIZE', cur.toString())"></el-input-number>
</bl-row>
</bl-row>
<bl-row style="margin-top: 10px">
<bl-row width="60%">
<div class="label">水印颜色</div>
<el-input
size="default"
style="width: calc(100% - 120px); margin-right: 8px"
v-model="userParamForm.WEB_BLOG_WATERMARK_COLOR"
@change="(cur: any) => updParam('WEB_BLOG_WATERMARK_COLOR', cur)">
<template #suffix>
<el-tooltip effect="light" placement="top" transition="none">
<template #content> 使用透明颜色会有更好的显示效果<br />: rgba(157, 157, 157, 0.2)</template>
<span class="iconbl bl-admonish-line"></span>
</el-tooltip>
</template>
</el-input>
<el-tooltip effect="light" placement="top" transition="none">
<template #content> 使用透明颜色会有更好的效果<br />: rgba(157, 157, 157, 0.2)</template>
<el-color-picker
size="default"
popper-class="theme-color-picker"
v-model="userParamForm.WEB_BLOG_WATERMARK_COLOR"
color-format="rgba"
show-alpha
@change="changeWatermarkColor" />
</el-tooltip>
</bl-row>
<bl-row width="40%">
<div class="label">密集程度</div>
<el-input-number
size="default"
v-model="userParamForm.WEB_BLOG_WATERMARK_GAP"
:step="10"
@change="(cur: any) => updParam('WEB_BLOG_WATERMARK_GAP', cur.toString())"></el-input-number>
</bl-row>
</bl-row>
</div>
</el-form-item>
<el-form-item label="ICP备案号">
<el-input size="default" v-model="userParamForm.WEB_IPC_BEI_AN_HAO" @change="(cur: any) => updParam('WEB_IPC_BEI_AN_HAO', cur)"></el-input>
<div class="conf-tip">如果博客作为你的域名首页你可能需要配置 ICP 备案号该内容会跳转至中国大陆ICP/IP地址/域名信息备案管理系统</div>
</el-form-item>
<el-form-item label="首页自定义信息">
<el-input
size="default"
type="textarea"
:rows="5"
v-model="userParamForm.WEB_GONG_WANG_AN_BEI"
@change="(cur: any) => updParam('WEB_GONG_WANG_AN_BEI', cur)"></el-input>
<div class="conf-tip">自定义信息可填写 HTML 内容</div>
</el-form-item>
<el-form-item label="ICP备案号">
<el-input size="default" v-model="userParamForm.WEB_IPC_BEI_AN_HAO" @change="(cur: any) => updParam('WEB_IPC_BEI_AN_HAO', cur)"></el-input>
<div class="conf-tip">如果博客作为你的域名首页你可能需要配置 ICP 备案号该内容会跳转至中国大陆ICP/IP地址/域名信息备案管理系统</div>
<div class="conf-tip">首页底部的自定义信息可填写 HTML 内容也可在此自定义备案信息即跳转地址等信息</div>
</el-form-item>
<el-form-item label="外部链接">
@ -98,18 +197,57 @@ const userParamForm = ref({
WEB_GONG_WANG_AN_BEI: '',
WEB_BLOG_URL_ERROR_TIP_SHOW: '',
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
})
const colors = ['rgb(136, 118, 87)', 'rgb(119, 150, 73)', 'rgb(128, 164, 146)', 'rgb(110, 155, 197)', 'rgb(97, 94, 168)', 'rgb(104, 104, 104)']
const changeBlogColor = (color: string) => {
if (!color) {
userParamForm.value.WEB_BLOG_COLOR = 'rgb(104, 104, 104)'
} else {
userParamForm.value.WEB_BLOG_COLOR = color
}
updParam('WEB_BLOG_COLOR', userParamForm.value.WEB_BLOG_COLOR)
}
const changeWatermarkColor = (color: string) => {
if (!color) {
userParamForm.value.WEB_BLOG_WATERMARK_COLOR = 'rgba(157, 157, 157, 0.2)'
} else {
userParamForm.value.WEB_BLOG_WATERMARK_COLOR = color
}
updParam('WEB_BLOG_WATERMARK_COLOR', userParamForm.value.WEB_BLOG_WATERMARK_COLOR)
}
/**
* 获取参数列表
*/
const getParamList = () => {
userParamListApi().then((resp) => {
userParamForm.value = { ...resp.data, ...{ WEB_BLOG_SUBJECT_TITLE: resp.data.WEB_BLOG_SUBJECT_TITLE === '1' } }
userParamForm.value = {
...resp.data,
...{
WEB_BLOG_SUBJECT_TITLE: resp.data.WEB_BLOG_SUBJECT_TITLE === '1',
WEB_BLOG_SHOW_ARTICLE_NAME: resp.data.WEB_BLOG_SHOW_ARTICLE_NAME === '1',
WEB_BLOG_WATERMARK_ENABLED: resp.data.WEB_BLOG_WATERMARK_ENABLED === '1',
WEB_BLOG_WATERMARK_FONTSIZE: Number(resp.data.WEB_BLOG_WATERMARK_FONTSIZE),
WEB_BLOG_WATERMARK_GAP: Number(resp.data.WEB_BLOG_WATERMARK_GAP)
}
}
})
}
/**
* 刷新参数
*/
const refreshParam = () => {
userParamRefreshApi().then((_) => {
Notify.success('', '刷新成功')
@ -118,6 +256,12 @@ const refreshParam = () => {
})
}
/**
* 修改参数
*
* @param paramName 参数名
* @param paramValue 参数值
*/
const updParam = (paramName: string, paramValue: string) => {
userParamUpdApi({ paramName: paramName, paramValue: paramValue }).then((_resp) => {
userStore.getUserinfo()
@ -130,6 +274,9 @@ const toBlog = () => {
openExtenal(url)
}
/**
* 获取连接配置模板
*/
const genWebLinksTemplate = () => {
if (isNotBlank(userParamForm.value.WEB_BLOG_LINKS)) {
Notify.info('你需要将内容清空后再生成模板', '提示')
@ -165,4 +312,32 @@ defineExpose({ reload })
white-space: pre;
color: var(--bl-text-color-light);
}
.colors {
align-content: flex-start;
flex-wrap: wrap;
.el-color-picker--small {
margin: 0 10px 10px 0;
}
.color-item {
@include box(28px, 28px);
margin: 0 0 0 15px;
border-radius: 4px;
position: relative;
transition: transform 0.3s;
cursor: pointer;
text-align: center;
&:hover {
transform: scale(1.1);
}
}
}
.theme-color-picker {
z-index: 3001 !important;
margin: 0 10px 10px 0;
}
</style>

View File

@ -69,4 +69,31 @@
font-style: italic;
}
}
.conf-watermark {
@include box(100%, auto);
color: var(--bl-text-color);
border: 1px solid var(--el-border-color);
background-color: var(--bl-bg-color);
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
.label {
min-width: 50px;
margin: 0 10px;
&:first-child {
margin-left: 0;
}
}
.el-input {
width: calc(100% - 80px);
}
.el-input-number {
width: 130px;
}
}
}

View File

@ -5,7 +5,7 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { onMounted, type StyleHTMLAttributes } from 'vue'
import { useUserStore } from '@/stores/user'
import { ElConfigProvider } from 'element-plus'
import { isNotBlank } from './assets/utils/obj'
@ -15,6 +15,9 @@ const userStore = useUserStore()
onMounted(async () => {
await userStore.getUserinfoOpen()
initCustomTheme()
console.log(userStore.userParams.WEB_BLOG_SHOW_ARTICLE_NAME);
// 使, 使
if (isNotBlank(userStore.userParams.WEB_LOGO_NAME)) {
@ -32,4 +35,42 @@ onMounted(async () => {
document.getElementsByTagName('head')[0].appendChild(link)
}
})
const THEME_STYLE_TAG_ID = 'blossom-blog-theme-css'
/**
* 初始化样式
*/
const initCustomTheme = () => {
const rgb = userStore.userParams.WEB_BLOG_COLOR
console.log(rgb)
if (!rgb.toLowerCase().startsWith('rgb(')) {
return
}
const prefix = rgb.substring(4, rgb.length - 1)
let text = `:root {
--el-color-primary: ${rgb};
--el-color-primary-dark: ${rgb};
--el-color-primary-dark-2: rgba(${prefix}, 0.8);
--el-color-primary-light-1: rgba(${prefix}, 0.9);
--el-color-primary-light-2: rgba(${prefix}, 0.8);
--el-color-primary-light-3: rgba(${prefix}, 0.7);
--el-color-primary-light-4: rgba(${prefix}, 0.6);
--el-color-primary-light-5: rgba(${prefix}, 0.5);
--el-color-primary-light-6: rgba(${prefix}, 0.4);
--el-color-primary-light-7: rgba(${prefix}, 0.3);
--el-color-primary-light-8: rgba(${prefix}, 0.2);
--el-color-primary-light-9: rgba(${prefix}, 0.1);
}`
let themeStyleTag = document.createElement('style') as unknown as StyleHTMLAttributes
themeStyleTag.type = 'text/css'
themeStyleTag.id = THEME_STYLE_TAG_ID
themeStyleTag.innerHTML = text
document
.getElementsByTagName('head')
.item(0)!
.appendChild(themeStyleTag as Node)
// }
}
</script>

View File

@ -46,7 +46,14 @@ const DEFAULT_USER_INFO = {
WEB_GONG_WANG_AN_BEI: '',
WEB_BLOG_URL_ERROR_TIP_SHOW: 1,
WEB_BLOG_LINKS: '',
WEB_BLOG_SUBJECT_TITLE: '1'
WEB_BLOG_SUBJECT_TITLE: '1',
WEB_BLOG_SHOW_ARTICLE_NAME: '1',
WEB_BLOG_COLOR: 'rgb(104, 104, 104)',
WEB_BLOG_WATERMARK_ENABLED: '0',
WEB_BLOG_WATERMARK_CONTENT: '',
WEB_BLOG_WATERMARK_FONTSIZE: 15,
WEB_BLOG_WATERMARK_COLOR: '',
WEB_BLOG_WATERMARK_GAP: 100
}
}

View File

@ -50,14 +50,24 @@
</div>
<div class="doc-content-container" ref="PreviewRef" :style="{ fontSize: getFontSize() }">
<div class="article-name">{{ article.name }}</div>
<div class="bl-preview" :style="{ fontSize: getFontSize() }" v-html="article.html"></div>
<div class="article-name" v-if="userStore.userParams.WEB_BLOG_SHOW_ARTICLE_NAME === '1'">{{ article.name }}</div>
<el-watermark
:font="{
color: userStore.userParams.WEB_BLOG_WATERMARK_COLOR,
fontSize: userStore.userParams.WEB_BLOG_WATERMARK_FONTSIZE,
textBaseline: 'hanging'
}"
:content="article.id > 0 && userStore.userParams.WEB_BLOG_WATERMARK_ENABLED === '1' ? userStore.userParams.WEB_BLOG_WATERMARK_CONTENT : ''"
:gap="[userStore.userParams.WEB_BLOG_WATERMARK_GAP, userStore.userParams.WEB_BLOG_WATERMARK_GAP]">
<div class="bl-preview" :style="{ fontSize: getFontSize() }" v-html="article.html"></div>
</el-watermark>
</div>
<div class="toc-container" :style="tocStyle">
<div class="viewer-toc">
<div v-if="article.id != 0">
<div class="toc-subtitle" style="font-size: 15px">{{ article.name }}</div>
<div class="toc-subtitle" style="font-size: 15px">{{ article.name }}</div>
<div class="toc-subtitle">
<span class="iconbl bl-pen-line"></span> {{ article.words }} | <span class="iconbl bl-read-line"></span> {{ article.uv }} |
<span class="iconbl bl-like-line"></span> {{ article.likes }}

View File

@ -17,6 +17,7 @@
border-bottom: 1px solid #e5e5e5;
text-align: left;
position: relative;
word-break: break-all;
font-size: 2em;
}

View File

@ -25,9 +25,9 @@
}
.toc-subtitle {
width: 100%;
@include flex(row, flex-start, center);
@include font(12px);
width: 100%;
color: #ababab;
overflow: hidden;
white-space: nowrap;
@ -53,6 +53,7 @@
&:hover {
font-weight: bold;
color: var(--el-color-primary);
}
}

View File

@ -64,7 +64,7 @@ const toToc = (articleId: number) => {
border-radius: 5px;
padding: 5px 10px;
margin: 15px 16px;
transition: 0.3s;
transition: border 0.3s, box-shadow 0.3s;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
@ -72,7 +72,7 @@ const toToc = (articleId: number) => {
font-size: 12px;
margin-right: 5px;
opacity: 0;
transition: 0.3s;
transition: opacity 0.3s;
}
&:hover {
@ -87,7 +87,6 @@ const toToc = (articleId: number) => {
text-shadow: 5px 5px 15px #000, -3px -3px 10px rgba(255, 255, 255, 0.5);
}
.cover,
.cover-name {
opacity: 1;
}
@ -107,7 +106,7 @@ const toToc = (articleId: number) => {
height: 35px;
font-weight: 700;
color: #cdcdcd;
transition: 0.3s;
transition: text-shadow 0.3s;
}
.words {
@ -134,7 +133,7 @@ const toToc = (articleId: number) => {
box-shadow: 0 0 10px 1px #181818;
text-align: center;
transform: scale(0.8) rotate(45deg);
transition: 0.3s;
transition: opacity 0.3s;
}
.describes {