feat(笔记预览): 实现大文件分块加载功能
添加分块加载API接口及前端实现,支持大文件(>500KB)的分页加载,提升大文件预览体验 后端实现分块逻辑并添加权限检查,前端添加加载提示和滚动加载功能
This commit is contained in:
@@ -3,6 +3,7 @@ package com.test.bijihoudaun.controller;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.test.bijihoudaun.common.response.R;
|
||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileChunk;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||
import com.test.bijihoudaun.service.MarkdownFileService;
|
||||
import com.test.bijihoudaun.util.SecurityUtil;
|
||||
@@ -117,4 +118,26 @@ public class MarkdownController {
|
||||
return R.success(files);
|
||||
}
|
||||
|
||||
@Operation(summary = "分块加载Markdown文件内容", description = "用于大文件(> 500KB)的分页加载")
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "文件ID", required = true),
|
||||
@Parameter(name = "chunkIndex", description = "块索引(从0开始)", required = false),
|
||||
@Parameter(name = "chunkSize", description = "块大小(字符数),默认10000", required = false)
|
||||
})
|
||||
@GetMapping("/{id}/chunk")
|
||||
public R<MarkdownFileChunk> getMarkdownChunk(
|
||||
@PathVariable Long id,
|
||||
@RequestParam(defaultValue = "0") int chunkIndex,
|
||||
@RequestParam(defaultValue = "10000") int chunkSize) {
|
||||
|
||||
// 获取当前认证状态
|
||||
boolean isAuthenticated = SecurityUtil.isUserAuthenticated();
|
||||
|
||||
MarkdownFileChunk chunk = markdownFileService.getMarkdownChunk(id, chunkIndex, chunkSize, isAuthenticated);
|
||||
if (ObjectUtil.isNotNull(chunk)) {
|
||||
return R.success(chunk);
|
||||
}
|
||||
return R.fail("文件未找到");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.test.bijihoudaun.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Markdown文件分块加载响应实体
|
||||
* 用于大文件分页加载
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "Markdown文件分块")
|
||||
public class MarkdownFileChunk implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "当前块的内容")
|
||||
private String chunk;
|
||||
|
||||
@Schema(description = "当前块索引(从0开始)")
|
||||
private int chunkIndex;
|
||||
|
||||
@Schema(description = "总块数")
|
||||
private int totalChunks;
|
||||
|
||||
@Schema(description = "是否还有更多块")
|
||||
private boolean hasMore;
|
||||
|
||||
@Schema(description = "笔记ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
private Long fileId;
|
||||
|
||||
@Schema(description = "笔记标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "是否私密 0-公开 1-私密")
|
||||
private Integer isPrivate;
|
||||
|
||||
@Schema(description = "总字符数")
|
||||
private long totalLength;
|
||||
|
||||
@Schema(description = "当前块字符数")
|
||||
private int chunkLength;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.test.bijihoudaun.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileChunk;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -66,4 +67,15 @@ public interface MarkdownFileService extends IService<MarkdownFile> {
|
||||
* @return 文件列表
|
||||
*/
|
||||
List<MarkdownFileVO> getRecentFiles(int limit);
|
||||
|
||||
/**
|
||||
* 分块加载Markdown文件内容
|
||||
* 用于大文件(> 500KB)的分页加载
|
||||
* @param id 文件ID
|
||||
* @param chunkIndex 块索引(从0开始)
|
||||
* @param chunkSize 块大小(字符数),默认10000
|
||||
* @param isAuthenticated 是否已认证
|
||||
* @return 文件块对象
|
||||
*/
|
||||
MarkdownFileChunk getMarkdownChunk(Long id, int chunkIndex, int chunkSize, boolean isAuthenticated);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.test.bijihoudaun.entity.ImageName;
|
||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileChunk;
|
||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||
import com.test.bijihoudaun.mapper.ImageNameMapper;
|
||||
import com.test.bijihoudaun.mapper.MarkdownFileMapper;
|
||||
@@ -156,4 +157,92 @@ public class MarkdownFileServiceImpl
|
||||
return markdownFileMapper.selectRecentWithGrouping(limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarkdownFileChunk getMarkdownChunk(Long id, int chunkIndex, int chunkSize, boolean isAuthenticated) {
|
||||
// 获取文件基本信息(不包含内容)
|
||||
MarkdownFileVO fileVO = markdownFileMapper.selectByIdWithGrouping(id);
|
||||
if (fileVO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查权限:私密笔记需要登录
|
||||
if (fileVO.getIsPrivate() != null && fileVO.getIsPrivate() == 1 && !isAuthenticated) {
|
||||
MarkdownFileChunk chunk = new MarkdownFileChunk();
|
||||
chunk.setFileId(id);
|
||||
chunk.setTitle(fileVO.getTitle());
|
||||
chunk.setIsPrivate(fileVO.getIsPrivate());
|
||||
chunk.setChunk("该笔记为私密笔记,请登录后查看");
|
||||
chunk.setChunkIndex(0);
|
||||
chunk.setTotalChunks(1);
|
||||
chunk.setHasMore(false);
|
||||
chunk.setTotalLength(0);
|
||||
chunk.setChunkLength(0);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// 获取完整内容
|
||||
MarkdownFile file = this.getById(id);
|
||||
if (file == null || file.getContent() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String fullContent = file.getContent();
|
||||
int totalLength = fullContent.length();
|
||||
|
||||
// 如果文件小于 500KB(约50万字符),直接返回全部内容
|
||||
if (totalLength <= 500000) {
|
||||
MarkdownFileChunk chunk = new MarkdownFileChunk();
|
||||
chunk.setFileId(id);
|
||||
chunk.setTitle(file.getTitle());
|
||||
chunk.setIsPrivate(file.getIsPrivate());
|
||||
chunk.setChunk(fullContent);
|
||||
chunk.setChunkIndex(0);
|
||||
chunk.setTotalChunks(1);
|
||||
chunk.setHasMore(false);
|
||||
chunk.setTotalLength(totalLength);
|
||||
chunk.setChunkLength(totalLength);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// 大文件分页加载
|
||||
// 计算总块数
|
||||
int totalChunks = (int) Math.ceil((double) totalLength / chunkSize);
|
||||
|
||||
// 确保 chunkIndex 在有效范围内
|
||||
if (chunkIndex < 0) {
|
||||
chunkIndex = 0;
|
||||
}
|
||||
if (chunkIndex >= totalChunks) {
|
||||
chunkIndex = totalChunks - 1;
|
||||
}
|
||||
|
||||
// 计算当前块的起止位置
|
||||
int start = chunkIndex * chunkSize;
|
||||
int end = Math.min(start + chunkSize, totalLength);
|
||||
|
||||
// 截取内容
|
||||
String chunkContent = fullContent.substring(start, end);
|
||||
|
||||
// 如果不是第一块,尝试从完整的换行处开始
|
||||
if (chunkIndex > 0 && start < totalLength) {
|
||||
// 找到第一个换行符,从下一行开始显示
|
||||
int firstNewLine = chunkContent.indexOf('\n');
|
||||
if (firstNewLine > 0) {
|
||||
chunkContent = chunkContent.substring(firstNewLine + 1);
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownFileChunk chunk = new MarkdownFileChunk();
|
||||
chunk.setFileId(id);
|
||||
chunk.setTitle(file.getTitle());
|
||||
chunk.setIsPrivate(file.getIsPrivate());
|
||||
chunk.setChunk(chunkContent);
|
||||
chunk.setChunkIndex(chunkIndex);
|
||||
chunk.setTotalChunks(totalChunks);
|
||||
chunk.setHasMore(chunkIndex < totalChunks - 1);
|
||||
chunk.setTotalLength(totalLength);
|
||||
chunk.setChunkLength(chunkContent.length());
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ export const markdownAll = () => axiosApi.get(`/api/markdown`);
|
||||
// 预览markdown文件
|
||||
export const Preview = (id) => axiosApi.get(`/api/markdown/${id}`);
|
||||
|
||||
// 分块加载markdown文件内容(用于大文件)
|
||||
export const PreviewChunk = (id, chunkIndex = 0, chunkSize = 10000) =>
|
||||
axiosApi.get(`/api/markdown/${id}/chunk?chunkIndex=${chunkIndex}&chunkSize=${chunkSize}`);
|
||||
|
||||
// 创建分类分组
|
||||
export const addGroupings = (group) => {
|
||||
return axiosApi.post('/api/groupings', group);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
:file="selectedFile"
|
||||
:is-mobile="isMobile"
|
||||
:is-user-logged-in="userStore.isLoggedIn"
|
||||
:has-more-chunks="hasMoreChunks"
|
||||
@back="selectedFile = null"
|
||||
@edit="editNote(selectedFile)"
|
||||
@delete="deleteNote(selectedFile)"
|
||||
@@ -66,8 +67,11 @@
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="hasMoreFiles && !showEditor && !selectedFile" class="load-more-trigger">
|
||||
<el-button @click="loadMoreFiles" type="primary" plain>加载更多</el-button>
|
||||
<div v-else-if="hasMoreFiles && !showEditor && !selectedFile" class="load-more-hint">
|
||||
<span>继续滚动加载更多...</span>
|
||||
</div>
|
||||
<div v-else-if="!hasMoreFiles && displayedFiles.length > 0" class="no-more-hint">
|
||||
<span>没有更多笔记了</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,6 +144,7 @@ import {
|
||||
groupingAll,
|
||||
markdownList,
|
||||
Preview,
|
||||
PreviewChunk,
|
||||
updateMarkdown,
|
||||
searchMarkdown,
|
||||
deleteMarkdown,
|
||||
@@ -180,6 +185,7 @@ const displayedFiles = ref([]);
|
||||
const currentPage = ref(0);
|
||||
const pageSize = ref(16);
|
||||
const isLoadingMore = ref(false);
|
||||
const loadCount = ref(0); // 记录加载次数,用于动态增加加载数量
|
||||
const noteListWrapper = ref(null);
|
||||
const showEditor = ref(false);
|
||||
const selectedFile = ref(null);
|
||||
@@ -203,6 +209,12 @@ const vditorReady = ref(false);
|
||||
// 提供给子组件使用
|
||||
provide('vditorReady', vditorReady);
|
||||
|
||||
// 大文件分块加载状态
|
||||
const currentChunkIndex = ref(0);
|
||||
const hasMoreChunks = ref(false);
|
||||
const isLoadingChunk = ref(false);
|
||||
const totalChunks = ref(0);
|
||||
|
||||
// Data for dialogs
|
||||
const itemToRename = ref(null);
|
||||
const fileToImport = ref(null);
|
||||
@@ -258,6 +270,7 @@ const resetToHomeView = async () => {
|
||||
searchKeyword.value = '';
|
||||
activeMenu.value = 'all';
|
||||
currentPage.value = 0;
|
||||
loadCount.value = 0; // 重置加载计数
|
||||
try {
|
||||
groupMarkdownFiles.value = await getRecentFiles(100) || [];
|
||||
updateDisplayedFiles();
|
||||
@@ -269,9 +282,21 @@ const resetToHomeView = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 计算动态加载数量:基础16个,每次增加8个,最多48个
|
||||
const getDynamicPageSize = () => {
|
||||
const baseSize = 16;
|
||||
const increment = 8;
|
||||
const maxSize = 48;
|
||||
return Math.min(baseSize + loadCount.value * increment, maxSize);
|
||||
};
|
||||
|
||||
const updateDisplayedFiles = () => {
|
||||
const start = 0;
|
||||
const end = (currentPage.value + 1) * pageSize.value;
|
||||
const dynamicPageSize = getDynamicPageSize();
|
||||
// 计算实际显示数量:首次16个,之后动态增加
|
||||
const end = displayedFiles.value.length === 0
|
||||
? dynamicPageSize
|
||||
: displayedFiles.value.length + dynamicPageSize;
|
||||
displayedFiles.value = groupMarkdownFiles.value.slice(start, end);
|
||||
};
|
||||
|
||||
@@ -280,6 +305,7 @@ const loadMoreFiles = () => {
|
||||
isLoadingMore.value = true;
|
||||
|
||||
setTimeout(() => {
|
||||
loadCount.value++;
|
||||
currentPage.value++;
|
||||
updateDisplayedFiles();
|
||||
isLoadingMore.value = false;
|
||||
@@ -288,11 +314,21 @@ const loadMoreFiles = () => {
|
||||
|
||||
const handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
if (scrollHeight - scrollTop - clientHeight < 100 && hasMoreFiles.value && !isLoadingMore.value) {
|
||||
// 距离底部 200px 时自动加载,给用户更流畅的体验
|
||||
if (scrollHeight - scrollTop - clientHeight < 200 && hasMoreFiles.value && !isLoadingMore.value) {
|
||||
loadMoreFiles();
|
||||
}
|
||||
};
|
||||
|
||||
// 预览区域滚动处理(用于大文件分块加载)
|
||||
const handlePreviewScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
// 距离底部 300px 时加载下一页内容
|
||||
if (scrollHeight - scrollTop - clientHeight < 300 && hasMoreChunks.value && !isLoadingChunk.value) {
|
||||
loadMoreContent();
|
||||
}
|
||||
};
|
||||
|
||||
// Event Handlers from Components
|
||||
const handleSelectFile = async (data) => {
|
||||
resetEdit();
|
||||
@@ -300,6 +336,7 @@ const handleSelectFile = async (data) => {
|
||||
const files = await markdownList(data.id);
|
||||
groupMarkdownFiles.value = files || [];
|
||||
currentPage.value = 0;
|
||||
loadCount.value = 0; // 重置加载计数
|
||||
updateDisplayedFiles();
|
||||
selectedFile.value = null;
|
||||
showEditor.value = false;
|
||||
@@ -377,26 +414,71 @@ const previewFile = async (file) => {
|
||||
}
|
||||
// 重置渲染缓存,确保每次打开笔记都重新渲染
|
||||
lastRenderedKey = null;
|
||||
// 重置分块加载状态
|
||||
currentChunkIndex.value = 0;
|
||||
hasMoreChunks.value = false;
|
||||
isLoadingChunk.value = false;
|
||||
totalChunks.value = 0;
|
||||
|
||||
// 先立即显示预览页(加载状态),让用户感知到响应
|
||||
selectedFile.value = { ...file, content: '', isLoading: true, isRendering: false };
|
||||
showEditor.value = false;
|
||||
|
||||
// 异步加载内容
|
||||
// 异步加载内容(使用分块加载)
|
||||
await loadNoteChunk(file.id, 0);
|
||||
};
|
||||
|
||||
// 分块加载笔记内容
|
||||
const loadNoteChunk = async (fileId, chunkIndex) => {
|
||||
if (isLoadingChunk.value) return;
|
||||
|
||||
isLoadingChunk.value = true;
|
||||
try {
|
||||
const content = await Preview(file.id) || '';
|
||||
// 内容加载完成后更新
|
||||
if (selectedFile.value && selectedFile.value.id === file.id) {
|
||||
const chunkData = await PreviewChunk(fileId, chunkIndex, 10000);
|
||||
|
||||
if (selectedFile.value && selectedFile.value.id === fileId) {
|
||||
// 更新分块加载状态
|
||||
currentChunkIndex.value = chunkData.chunkIndex;
|
||||
hasMoreChunks.value = chunkData.hasMore;
|
||||
totalChunks.value = chunkData.totalChunks;
|
||||
|
||||
// 如果是第一块,直接设置内容;否则追加内容
|
||||
let newContent;
|
||||
if (chunkIndex === 0) {
|
||||
newContent = chunkData.chunk;
|
||||
} else {
|
||||
newContent = selectedFile.value.content + chunkData.chunk;
|
||||
}
|
||||
|
||||
// 如果 Vditor 渲染引擎未就绪,显示渲染中状态
|
||||
const isRendering = !vditorReady.value;
|
||||
selectedFile.value = { ...file, content, isLoading: false, isRendering };
|
||||
|
||||
selectedFile.value = {
|
||||
...selectedFile.value,
|
||||
content: newContent,
|
||||
isLoading: false,
|
||||
isRendering,
|
||||
title: chunkData.title || selectedFile.value.title,
|
||||
isPrivate: chunkData.isPrivate !== undefined ? chunkData.isPrivate : selectedFile.value.isPrivate
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误已在 axios 拦截器中显示,这里不再重复显示
|
||||
console.error('获取笔记内容失败:', error);
|
||||
selectedFile.value = null;
|
||||
} finally {
|
||||
isLoadingChunk.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载更多内容(滚动到底部时调用)
|
||||
const loadMoreContent = async () => {
|
||||
if (!selectedFile.value || !hasMoreChunks.value || isLoadingChunk.value) return;
|
||||
|
||||
const nextChunkIndex = currentChunkIndex.value + 1;
|
||||
await loadNoteChunk(selectedFile.value.id, nextChunkIndex);
|
||||
};
|
||||
|
||||
const editNote = (file) => {
|
||||
editData.value = { ...file };
|
||||
showEditor.value = true;
|
||||
@@ -474,6 +556,7 @@ const handleSearch = async () => {
|
||||
try {
|
||||
groupMarkdownFiles.value = await searchMarkdown(searchKeyword.value) || [];
|
||||
currentPage.value = 0;
|
||||
loadCount.value = 0; // 重置加载计数
|
||||
updateDisplayedFiles();
|
||||
selectedFile.value = null;
|
||||
showEditor.value = false;
|
||||
@@ -669,6 +752,10 @@ const renderMarkdown = async (file) => {
|
||||
img.setAttribute('loading', 'lazy');
|
||||
}
|
||||
});
|
||||
// 添加滚动监听,用于大文件分块加载
|
||||
if (hasMoreChunks.value) {
|
||||
previewElement.addEventListener('scroll', handlePreviewScroll);
|
||||
}
|
||||
}
|
||||
});
|
||||
lastRenderedKey = renderKey;
|
||||
@@ -776,4 +863,68 @@ watch(showEditor, (isEditor) => {
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 没有更多笔记了 - 美化样式 */
|
||||
.no-more-hint {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30px 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.no-more-hint span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 24px;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
|
||||
border-radius: 24px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.no-more-hint span::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: #c0c4cc;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.no-more-hint span::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: #c0c4cc;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 暗黑模式适配 */
|
||||
.dark-theme .no-more-hint span {
|
||||
background: linear-gradient(135deg, #2c2c3d 0%, #1e1e2f 100%);
|
||||
color: #606266;
|
||||
border-color: #2c2c3d;
|
||||
}
|
||||
|
||||
.dark-theme .no-more-hint span::before,
|
||||
.dark-theme .no-more-hint span::after {
|
||||
background-color: #606266;
|
||||
}
|
||||
|
||||
/* 继续滚动加载更多提示 */
|
||||
.load-more-hint {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
color: var(--el-color-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -51,6 +51,11 @@
|
||||
<el-icon class="loading-icon is-loading"><Loading /></el-icon>
|
||||
<span class="loading-text">正在渲染...</span>
|
||||
</div>
|
||||
<!-- 大文件分块加载提示 -->
|
||||
<div v-if="file.hasMoreChunks" class="chunk-loading-hint">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载更多内容...</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -64,6 +69,10 @@ const props = defineProps({
|
||||
},
|
||||
isMobile: Boolean,
|
||||
isUserLoggedIn: Boolean,
|
||||
hasMoreChunks: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -192,4 +201,25 @@ const handleExport = (format) => {
|
||||
font-size: 14px;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
/* 分块加载提示 */
|
||||
.chunk-loading-hint {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
color: var(--el-color-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dark-theme .chunk-loading-hint {
|
||||
background-color: rgba(30, 30, 47, 0.9);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user