feat(note): 添加私密笔记功能

- 新增私密笔记相关样式和图标
- 在笔记列表和详情页面添加私密状态显示
- 实现私密笔记的创建和状态切换功能
- 添加未登录用户查看私密笔记时的提示
This commit is contained in:
2025-08-06 15:03:03 +08:00
parent 5f608b7573
commit e3bc24bd9a
3 changed files with 143 additions and 6 deletions

View File

@@ -40,6 +40,7 @@
<el-header class="preview-header">
<h2 class="preview-title">
{{ selectedFile.title }}
<el-icon v-if="selectedFile.isPrivate === 1" class="lock-icon"><Lock /></el-icon>
<el-icon class="edit-icon" @click="openRenameDialog(selectedFile, 'file')"><Edit /></el-icon>
</h2>
<div class="actions">
@@ -50,6 +51,9 @@
<el-button v-if="showEditor" type="primary" @click="showEditor = !showEditor; previewFile(editData)">返回</el-button>
<el-button v-if="showEditor && userStore.isLoggedIn" type="success" @click="handleSave(vditor.getValue())">保存</el-button>
<span v-if="showEditor" class="save-status">{{ saveStatus }}</span>
<el-button v-if="!showEditor && userStore.isLoggedIn" type="info" @click="showPrivacyDialog = true">
{{ selectedFile.isPrivate === 1 ? '设为公开' : '设为私密' }}
</el-button>
<el-dropdown v-if="!showEditor && userStore.isLoggedIn" @command="handleExport">
<el-button type="success">
导出<el-icon class="el-icon--right"><arrow-down /></el-icon>
@@ -64,7 +68,12 @@
</el-dropdown>
</div>
</el-header>
<div v-if="!showEditor" :key="selectedFile.id" class="markdown-preview"></div>
<div v-if="!showEditor" :key="selectedFile.id" class="markdown-preview">
<div v-if="selectedFile.isPrivate === 1 && !userStore.isLoggedIn && !selectedFile.content" class="private-note-notice">
<el-icon><Lock /></el-icon>
<p>这是私密笔记请登录后查看完整内容</p>
</div>
</div>
<!-- Vditor 编辑器 -->
<div v-show="showEditor" id="vditor" class="vditor" />
</div>
@@ -109,10 +118,11 @@
</el-header>
<div v-if="groupMarkdownFiles.length > 0" class="file-list">
<el-card v-for="file in groupMarkdownFiles" :key="file.id" shadow="hover" class="file-item">
<el-card v-for="file in groupMarkdownFiles" :key="file.id" shadow="hover" class="file-item" :class="{ 'private-note': file.isPrivate === 1 }">
<div @click="previewFile(file)" class="file-title">
<span>{{ file.title }}</span>
<span class="file-group-name">{{ file.groupingName }}</span>
<el-icon v-if="file.isPrivate === 1 && !userStore.isLoggedIn" class="lock-icon"><Lock /></el-icon>
</div>
</el-card>
</div>
@@ -158,6 +168,16 @@
style="width: 100%;"
></el-cascader>
</el-form-item>
<el-form-item label="私密笔记">
<el-switch
v-model="newNoteForm.isPrivate"
:active-value="1"
:inactive-value="0"
active-text="私密"
inactive-text="公开"
/>
<div class="form-item-help">私密笔记只有登录用户才能查看内容</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showCreateNoteDialog = false">取消</el-button>
@@ -246,6 +266,21 @@
<el-button type="primary" @click="handleUpdatePassword">确定</el-button>
</template>
</el-dialog>
<!-- 修改笔记私密状态对话框 -->
<el-dialog v-model="showPrivacyDialog" :title="selectedFile && selectedFile.isPrivate === 1 ? '设为公开笔记' : '设为私密笔记'" width="400px">
<div v-if="selectedFile">
<p>您确定要将笔记 <strong>"{{ selectedFile.title }}"</strong> {{ selectedFile.isPrivate === 1 ? '设为公开' : '设为私密' }}</p>
<div class="privacy-explanation">
<el-icon><InfoFilled /></el-icon>
<span>{{ selectedFile.isPrivate === 1 ? '公开笔记:所有用户都可以查看内容' : '私密笔记:只有登录用户才能查看内容' }}</span>
</div>
</div>
<template #footer>
<el-button @click="showPrivacyDialog = false">取消</el-button>
<el-button type="primary" @click="handlePrivacyChange">确定</el-button>
</template>
</el-dialog>
</el-main>
</el-container>
</el-container>
@@ -274,7 +309,7 @@ import {
generateRegistrationCode,
updatePassword
} from '@/api/CommonApi.js'
import { Plus, Fold, Expand, Folder, Document, Search, Edit, Delete, ArrowDown, Clock } from "@element-plus/icons-vue";
import { Plus, Fold, Expand, Folder, Document, Search, Edit, Delete, ArrowDown, Clock, Lock, InfoFilled } from "@element-plus/icons-vue";
import { useUserStore } from '../stores/user';
import { useRouter } from 'vue-router';
@@ -304,6 +339,7 @@ const showSystemSettingsDialog = ref(false);
const isRegistrationEnabled = ref(true);
const generatedCode = ref('');
const showUpdatePasswordDialog = ref(false);
const showPrivacyDialog = ref(false);
const groupFormRef = ref(null);
const newGroupForm = ref({ name: '', parentId: null });
@@ -317,7 +353,8 @@ const newNoteForm = ref({
title: '',
groupingId: null,
fileName: '',
content: ''
content: '',
isPrivate: 0
});
const noteFormRules = ref({
title: [{ required: true, message: '请输入笔记标题', trigger: 'blur' }],
@@ -494,7 +531,8 @@ const createNote = async () => {
title: newNoteForm.value.title,
groupingId: groupingId,
fileName: newNoteForm.value.title + '.md',
content: ''
content: '',
isPrivate: newNoteForm.value.isPrivate
};
editData.value = payload;
showCreateNoteDialog.value = false;
@@ -511,7 +549,7 @@ const createNote = async () => {
};
const resetNoteForm = () => {
newNoteForm.value = { id: null, title: '', groupingId: null, fileName: '', content: '' };
newNoteForm.value = { id: null, title: '', groupingId: null, fileName: '', content: '', isPrivate: 0 };
if (noteFormRef.value) {
noteFormRef.value.resetFields();
}
@@ -571,6 +609,11 @@ const previewFile = async (file) => {
content: content
};
showEditor.value = false; // 确保进入预览模式
// 如果是私密笔记且用户未登录,显示提示
if (file.isPrivate === 1 && !userStore.isLoggedIn) {
ElMessage.info('这是私密笔记,请登录后查看完整内容');
}
} catch (error) {
ElMessage.error('获取笔记内容失败: ' + error.message);
selectedFile.value = null;
@@ -587,6 +630,33 @@ const editNote = async (file) => {
});
};
// 修改笔记私密状态
const handlePrivacyChange = async () => {
if (!selectedFile.value) return;
try {
const newPrivacyStatus = selectedFile.value.isPrivate === 1 ? 0 : 1;
const payload = {
...selectedFile.value,
isPrivate: newPrivacyStatus
};
const updatedFile = await updateMarkdown(payload);
selectedFile.value = updatedFile;
// 更新列表中的文件状态
const index = groupMarkdownFiles.value.findIndex(file => file.id === selectedFile.value.id);
if (index !== -1) {
groupMarkdownFiles.value[index] = updatedFile;
}
showPrivacyDialog.value = false;
ElMessage.success(`笔记已${newPrivacyStatus === 1 ? '设为私密' : '设为公开'}`);
} catch (error) {
ElMessage.error('修改笔记状态失败: ' + error.message);
}
};
// 图片上传
const handleImageUpload=async (files) => {
const promise = await uploadImage(files[0]);