文章和文件建立关联
This commit is contained in:
+128
-7
@@ -298,6 +298,8 @@ async function showPostForm(isEdit = false) {
|
||||
document.getElementById('post-form-title').textContent = isEdit ? '编辑文章' : '新建文章';
|
||||
document.getElementById('post-form').reset();
|
||||
document.getElementById('post-id').value = '';
|
||||
_coverFileID = null;
|
||||
_contentFileIDs.clear();
|
||||
setCover('');
|
||||
mdShowEdit();
|
||||
|
||||
@@ -327,6 +329,13 @@ async function editPost(id) {
|
||||
document.getElementById('post-title').value = post.title;
|
||||
document.getElementById('post-content').value = post.content;
|
||||
document.getElementById('post-summary').value = post.summary || '';
|
||||
// 重置文件追踪
|
||||
_coverFileID = null;
|
||||
_contentFileIDs.clear();
|
||||
// 从关联文件中恢复追踪
|
||||
if (post.files && Array.isArray(post.files)) {
|
||||
post.files.forEach(f => _contentFileIDs.add(f.id));
|
||||
}
|
||||
setCover(post.cover || '');
|
||||
document.getElementById('post-status').value = post.status;
|
||||
document.getElementById('post-istop').checked = post.is_top;
|
||||
@@ -364,7 +373,8 @@ async function handlePostSubmit(e) {
|
||||
category_id: parseInt(document.getElementById('post-category').value),
|
||||
status: document.getElementById('post-status').value,
|
||||
is_top: document.getElementById('post-istop').checked,
|
||||
tags: tags
|
||||
tags: tags,
|
||||
file_ids: buildFileIDs()
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -430,6 +440,87 @@ function mdInsertTable() {
|
||||
ta.selectionStart = ta.selectionEnd = start + table.length;
|
||||
}
|
||||
|
||||
// ==================== 图片插入对话框 ====================
|
||||
|
||||
let _imgFile = null; // 当前选择的图片文件
|
||||
|
||||
// 打开图片插入对话框
|
||||
function mdInsertImage() {
|
||||
_imgFile = null;
|
||||
document.getElementById('img-desc').value = '';
|
||||
document.getElementById('img-file-name').textContent = '未选择';
|
||||
document.getElementById('img-preview-box').style.display = 'none';
|
||||
document.getElementById('img-file-input').value = '';
|
||||
document.getElementById('img-upload-btn').disabled = true;
|
||||
document.getElementById('image-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 关闭图片插入对话框
|
||||
function hideImageModal() {
|
||||
document.getElementById('image-modal').classList.add('hidden');
|
||||
_imgFile = null;
|
||||
}
|
||||
|
||||
// 选择图片文件后:校验、预览、启用按钮
|
||||
function onImageFileSelected(input) {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const err = validateImageFile(file);
|
||||
if (err) {
|
||||
alert(err);
|
||||
input.value = '';
|
||||
_imgFile = null;
|
||||
document.getElementById('img-upload-btn').disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_imgFile = file;
|
||||
document.getElementById('img-file-name').textContent = file.name;
|
||||
|
||||
// 预览
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('img-preview-thumb').src = e.target.result;
|
||||
document.getElementById('img-preview-box').style.display = 'block';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
document.getElementById('img-upload-btn').disabled = false;
|
||||
}
|
||||
|
||||
// 确认上传并插入编辑器
|
||||
async function confirmInsertImage() {
|
||||
if (!_imgFile) return;
|
||||
|
||||
const desc = document.getElementById('img-desc').value.trim() || _imgFile.name.replace(/\.[^.]+$/, '');
|
||||
const btn = document.getElementById('img-upload-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '上传中...';
|
||||
|
||||
try {
|
||||
const data = await uploadImageFile(_imgFile);
|
||||
// 记录文件 ID
|
||||
if (data.id) {
|
||||
_contentFileIDs.add(data.id);
|
||||
}
|
||||
// 插入 Markdown 图片语法到编辑器
|
||||
mdShowEdit();
|
||||
const ta = document.getElementById('post-content');
|
||||
const imgMd = ``;
|
||||
const start = ta.selectionStart;
|
||||
ta.value = ta.value.substring(0, start) + imgMd + ta.value.substring(ta.selectionEnd);
|
||||
ta.focus();
|
||||
ta.selectionStart = ta.selectionEnd = start + imgMd.length;
|
||||
|
||||
hideImageModal();
|
||||
} catch (err) {
|
||||
alert('上传失败:' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '上传并插入';
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到编辑模式
|
||||
function mdShowEdit() {
|
||||
document.getElementById('post-content').classList.remove('hidden');
|
||||
@@ -1478,6 +1569,11 @@ async function saveCurrentFile() {
|
||||
|
||||
// ==================== 文件上传与管理 ====================
|
||||
|
||||
// 当前封面文件 ID(上传封面时记录)
|
||||
let _coverFileID = null;
|
||||
// 正文中插入的图片文件 ID 集合
|
||||
let _contentFileIDs = new Set();
|
||||
|
||||
// 允许的图片类型与大小限制(与后端保持一致)
|
||||
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
const MAX_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
@@ -1526,6 +1622,7 @@ async function handleCoverFileChange(e) {
|
||||
|
||||
try {
|
||||
const data = await uploadImageFile(file);
|
||||
_coverFileID = data.id || null;
|
||||
setCover(data.url);
|
||||
} catch (uErr) {
|
||||
alert(uErr.message);
|
||||
@@ -1553,9 +1650,19 @@ function setCover(url) {
|
||||
|
||||
// 移除封面图
|
||||
function removeCover() {
|
||||
_coverFileID = null;
|
||||
setCover('');
|
||||
}
|
||||
|
||||
// 构建提交时的 file_ids(封面 + 正文图片,去重)
|
||||
function buildFileIDs() {
|
||||
const ids = new Set(_contentFileIDs);
|
||||
if (_coverFileID) {
|
||||
ids.add(_coverFileID);
|
||||
}
|
||||
return Array.from(ids);
|
||||
}
|
||||
|
||||
// 加载已上传文件列表
|
||||
async function loadFiles() {
|
||||
try {
|
||||
@@ -1568,19 +1675,33 @@ async function loadFiles() {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = data.data.map(f => `
|
||||
grid.innerHTML = data.data.map(f => {
|
||||
// 根据是否关联文章显示不同操作
|
||||
let actionsHTML;
|
||||
if (f.post_title) {
|
||||
// 已关联文章,显示文章标题
|
||||
actionsHTML = `<div class="file-actions">
|
||||
<button class="btn-small btn-secondary" onclick="copyFileURL('${f.url}')">复制链接</button>
|
||||
<span class="file-linked">已关联: <a href="#/post/edit/${f.post_id}" title="查看文章">${f.post_title}</a></span>
|
||||
</div>`;
|
||||
} else {
|
||||
// 未关联,显示删除按钮
|
||||
actionsHTML = `<div class="file-actions">
|
||||
<button class="btn-small btn-secondary" onclick="copyFileURL('${f.url}')">复制链接</button>
|
||||
<button class="btn-small btn-danger" onclick="deleteFile(${f.id})">删除</button>
|
||||
</div>`;
|
||||
}
|
||||
return `
|
||||
<div class="file-card">
|
||||
<div class="file-thumb"><img src="${f.url}" alt="${f.orig_name || f.name}" loading="lazy"></div>
|
||||
<div class="file-info">
|
||||
<div class="file-name" title="${f.orig_name || f.name}">${f.orig_name || f.name}</div>
|
||||
<div class="file-meta">${formatFileSize(f.size)} · ${f.created_at}</div>
|
||||
</div>
|
||||
<div class="file-actions">
|
||||
<button class="btn-small btn-secondary" onclick="copyFileURL('${f.url}')">复制链接</button>
|
||||
<button class="btn-small btn-danger" onclick="deleteFile(${f.id})">删除</button>
|
||||
</div>
|
||||
${actionsHTML}
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
} catch (err) {
|
||||
console.error('加载文件失败:', err);
|
||||
}
|
||||
|
||||
+28
-2
@@ -50,7 +50,7 @@
|
||||
{{if index .PageSet "users"}}{{template "admin_modal_user" .}}{{end}}
|
||||
{{if index .PageSet "themes"}}{{template "admin_modal_theme" .}}{{end}}
|
||||
|
||||
<script>window.__currentUser = {{.CurrentUser}};</script>
|
||||
<script>window.__currentUser = {{.CurrentUser | json}};</script>
|
||||
<script src="/static/admin/app.js?v=21"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -164,7 +164,7 @@
|
||||
<button type="button" title="删除线" onclick="mdWrap('~~','~~','删除线文字')"><s>S</s></button>
|
||||
<button type="button" title="标题" onclick="mdInsertLine('## ','标题')">H</button>
|
||||
<button type="button" title="链接" onclick="mdWrap('[','](https://)','链接文字')">🔗</button>
|
||||
<button type="button" title="图片" onclick="mdWrap('','图片描述')">🖼</button>
|
||||
<button type="button" title="图片" onclick="mdInsertImage()">🖼</button>
|
||||
<button type="button" title="行内代码" onclick="mdWrap('`','`','代码')"></></button>
|
||||
<button type="button" title="代码块" onclick="mdWrap('\n```\n','\n```\n','代码块')">{ }</button>
|
||||
<button type="button" title="引用" onclick="mdInsertLine('> ','引用内容')">”</button>
|
||||
@@ -191,6 +191,32 @@
|
||||
<button type="submit" class="btn-primary" onclick="mdShowEdit()">保存</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 图片插入对话框 -->
|
||||
<div id="image-modal" class="modal hidden">
|
||||
<div class="modal-content" style="max-width:420px">
|
||||
<h3>插入图片</h3>
|
||||
<div class="form-group">
|
||||
<label>图片描述</label>
|
||||
<input type="text" id="img-desc" placeholder="输入图片描述(alt 文本)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>选择图片</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button type="button" class="btn-secondary" onclick="document.getElementById('img-file-input').click()">选择文件</button>
|
||||
<input type="file" id="img-file-input" accept="image/jpeg,image/png,image/gif,image/webp" style="display:none" onchange="onImageFileSelected(this)">
|
||||
<span id="img-file-name" style="color:#888;font-size:13px">未选择</span>
|
||||
</div>
|
||||
<div id="img-preview-box" style="margin-top:8px;display:none">
|
||||
<img id="img-preview-thumb" style="max-width:100%;max-height:200px;border-radius:4px;border:1px solid #ddd">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn-secondary" onclick="hideImageModal()">取消</button>
|
||||
<button type="button" class="btn-primary" id="img-upload-btn" onclick="confirmInsertImage()" disabled>上传并插入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "admin_page_categories"}}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Reference in New Issue
Block a user