feat: 文章属性前端完成 — Phase 3
- 写文章页:可见性/类型/转载来源/创作声明/禁止转载 属性表单 - 类型切换联动(转载显示来源,原创显示禁止转载复选框) - 文章详情页:私密角标、置顶标记、创作声明横幅、转载来源 - 管理后台:置顶/取消置顶操作按钮 - PostService Create/Update 签名扩展接受文章属性 - DTO/接口同步更新
This commit is contained in:
@ -42,9 +42,9 @@ type postUseCase interface {
|
|||||||
|
|
||||||
// studioUseCase StudioController 对 PostService 的最小依赖(ISP:9 个方法)
|
// studioUseCase StudioController 对 PostService 的最小依赖(ISP:9 个方法)
|
||||||
type studioUseCase interface {
|
type studioUseCase interface {
|
||||||
Create(userID uint, title, body string) (*model.Post, error)
|
Create(userID uint, title, body, visibility, postType, reprintSource, declaration string, reprintProhibited bool) (*model.Post, error)
|
||||||
GetByID(id uint) (*model.Post, error)
|
GetByID(id uint) (*model.Post, error)
|
||||||
Update(postID uint, title, body string) error
|
Update(postID uint, title, body, visibility, postType, reprintSource, declaration string, reprintProhibited bool) error
|
||||||
Delete(postID uint) error
|
Delete(postID uint) error
|
||||||
SubmitForAudit(postID uint) error
|
SubmitForAudit(postID uint) error
|
||||||
ListByUser(userID uint, status string, page, pageSize int) ([]model.Post, int64, error)
|
ListByUser(userID uint, status string, page, pageSize int) ([]model.Post, int64, error)
|
||||||
|
|||||||
@ -30,7 +30,7 @@ func (ctrl *StudioController) Create(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
post, err := ctrl.postService.Create(uid, req.Title, req.Body)
|
post, err := ctrl.postService.Create(uid, req.Title, req.Body, req.Visibility, req.PostType, req.ReprintSource, req.Declaration, req.ReprintProhibited)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Error(c, http.StatusInternalServerError, "发布失败")
|
common.Error(c, http.StatusInternalServerError, "发布失败")
|
||||||
return
|
return
|
||||||
@ -69,7 +69,7 @@ func (ctrl *StudioController) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctrl.postService.Update(uint(id), req.Title, req.Body); err != nil {
|
if err := ctrl.postService.Update(uint(id), req.Title, req.Body, req.Visibility, req.PostType, req.ReprintSource, req.Declaration, req.ReprintProhibited); err != nil {
|
||||||
common.Error(c, http.StatusInternalServerError, "编辑失败")
|
common.Error(c, http.StatusInternalServerError, "编辑失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,12 +34,22 @@ type PostListResult struct {
|
|||||||
type PostCreateRequest struct {
|
type PostCreateRequest struct {
|
||||||
Title string `json:"title" binding:"required,min=1,max=200"`
|
Title string `json:"title" binding:"required,min=1,max=200"`
|
||||||
Body string `json:"body" binding:"required,min=1"`
|
Body string `json:"body" binding:"required,min=1"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
PostType string `json:"post_type"`
|
||||||
|
ReprintSource string `json:"reprint_source"`
|
||||||
|
Declaration string `json:"declaration"`
|
||||||
|
ReprintProhibited bool `json:"reprint_prohibited"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostUpdateRequest 编辑请求
|
// PostUpdateRequest 编辑请求
|
||||||
type PostUpdateRequest struct {
|
type PostUpdateRequest struct {
|
||||||
Title string `json:"title" binding:"required,min=1,max=200"`
|
Title string `json:"title" binding:"required,min=1,max=200"`
|
||||||
Body string `json:"body" binding:"required,min=1"`
|
Body string `json:"body" binding:"required,min=1"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
PostType string `json:"post_type"`
|
||||||
|
ReprintSource string `json:"reprint_source"`
|
||||||
|
Declaration string `json:"declaration"`
|
||||||
|
ReprintProhibited bool `json:"reprint_prohibited"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostRejectRequest 退回请求
|
// PostRejectRequest 退回请求
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Update 编辑帖子(权限在 controller 层检查)
|
// Update 编辑帖子(权限在 controller 层检查)
|
||||||
func (s *PostService) Update(postID uint, title, body string) error {
|
func (s *PostService) Update(postID uint, title, body string, visibility, postType, reprintSource, declaration string, reprintProhibited bool) error {
|
||||||
post, err := s.findPost(postID)
|
post, err := s.findPost(postID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -16,6 +16,13 @@ func (s *PostService) Update(postID uint, title, body string) error {
|
|||||||
return common.ErrPostCannotEdit
|
return common.ErrPostCannotEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文章属性随时可更新(不影响已发布内容展示)
|
||||||
|
post.Visibility = visibility
|
||||||
|
post.PostType = postType
|
||||||
|
post.ReprintSource = reprintSource
|
||||||
|
post.Declaration = declaration
|
||||||
|
post.ReprintProhibited = reprintProhibited
|
||||||
|
|
||||||
// 已通过帖子:编辑内容保存为待审修订,不影响当前展示
|
// 已通过帖子:编辑内容保存为待审修订,不影响当前展示
|
||||||
if post.Status == model.PostStatusApproved {
|
if post.Status == model.PostStatusApproved {
|
||||||
post.PendingTitle = title
|
post.PendingTitle = title
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func (s *PostService) findPostWithAuthor(id uint) (*model.Post, error) {
|
|||||||
// Create 创建帖子
|
// Create 创建帖子
|
||||||
// body 为 Vditor 输出的 Markdown,后端直接存储不做 HTML 消毒
|
// body 为 Vditor 输出的 Markdown,后端直接存储不做 HTML 消毒
|
||||||
// MD 是纯文本,无 XSS 注入风险;前端渲染时由 Vditor 转 HTML
|
// MD 是纯文本,无 XSS 注入风险;前端渲染时由 Vditor 转 HTML
|
||||||
func (s *PostService) Create(userID uint, title, body string) (*model.Post, error) {
|
func (s *PostService) Create(userID uint, title, body string, visibility, postType, reprintSource, declaration string, reprintProhibited bool) (*model.Post, error) {
|
||||||
status := model.PostStatusApproved
|
status := model.PostStatusApproved
|
||||||
if s.auditEnabled() {
|
if s.auditEnabled() {
|
||||||
status = model.PostStatusDraft
|
status = model.PostStatusDraft
|
||||||
@ -81,6 +81,11 @@ func (s *PostService) Create(userID uint, title, body string) (*model.Post, erro
|
|||||||
UserID: userID,
|
UserID: userID,
|
||||||
Status: status,
|
Status: status,
|
||||||
AllowComment: true,
|
AllowComment: true,
|
||||||
|
Visibility: visibility,
|
||||||
|
PostType: postType,
|
||||||
|
ReprintSource: reprintSource,
|
||||||
|
Declaration: declaration,
|
||||||
|
ReprintProhibited: reprintProhibited,
|
||||||
}
|
}
|
||||||
if err := s.repo.Create(post); err != nil {
|
if err := s.repo.Create(post); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -18,6 +18,12 @@
|
|||||||
<span>收藏:{{.Post.FavoritesCount}}</span>
|
<span>收藏:{{.Post.FavoritesCount}}</span>
|
||||||
<span>评论:{{.Post.CommentsCount}}</span>
|
<span>评论:{{.Post.CommentsCount}}</span>
|
||||||
<span>发布时间:{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</span>
|
<span>发布时间:{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</span>
|
||||||
|
{{if eq .Post.Visibility "private"}}
|
||||||
|
<span class="post-badge post-badge-private">私密</span>
|
||||||
|
{{end}}
|
||||||
|
{{if .Post.PinType}}
|
||||||
|
<span class="post-badge post-badge-pinned">📌 {{if eq .Post.PinType "global"}}全局置顶{{else}}分类置顶{{end}}</span>
|
||||||
|
{{end}}
|
||||||
{{if and .Post.IsLocked (eq $.Post.UserID $.UID)}}
|
{{if and .Post.IsLocked (eq $.Post.UserID $.UID)}}
|
||||||
<span class="post-status post-status-locked">已锁定{{if .Post.LockReason}}:{{.Post.LockReason}}{{end}}</span>
|
<span class="post-status post-status-locked">已锁定{{if .Post.LockReason}}:{{.Post.LockReason}}{{end}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -25,6 +31,19 @@
|
|||||||
<span class="post-status post-status-revision">修订审核中</span>
|
<span class="post-status post-status-revision">修订审核中</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 文章属性声明 -->
|
||||||
|
{{if or .Post.Declaration .Post.ReprintSource .Post.ReprintProhibited (eq .Post.PostType "reprint")}}
|
||||||
|
<div class="post-declarations">
|
||||||
|
{{if .Post.Declaration}}
|
||||||
|
<div class="post-declaration-item">⚡ {{declarationLabel .Post.Declaration}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq .Post.PostType "reprint"}}
|
||||||
|
<div class="post-declaration-item">📎 转载自:{{.Post.ReprintSource}}</div>
|
||||||
|
{{else if .Post.ReprintProhibited}}
|
||||||
|
<div class="post-declaration-item">⚠ 未经作者许可,禁止转载</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{{if .Post.RejectReason}}
|
{{if .Post.RejectReason}}
|
||||||
|
|||||||
@ -45,6 +45,40 @@
|
|||||||
{{if and .Post .Post.IsLocked}}disabled{{end}}>
|
{{if and .Post .Post.IsLocked}}disabled{{end}}>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章属性面板 -->
|
||||||
|
<div class="editor-meta"{{if and .Post .Post.IsLocked}} style="opacity:0.5;pointer-events:none"{{end}}>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">可见性</span>
|
||||||
|
<label class="meta-radio"><input type="radio" name="visibility" value="public" checked> 公开</label>
|
||||||
|
<label class="meta-radio"><input type="radio" name="visibility" value="private"> 私密</label>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">类型</span>
|
||||||
|
<label class="meta-radio"><input type="radio" name="post_type" value="original" checked> 原创</label>
|
||||||
|
<label class="meta-radio"><input type="radio" name="post_type" value="reprint"> 转载</label>
|
||||||
|
<span id="reprintSourceArea" style="display:none;margin-left:8px">
|
||||||
|
<input type="text" id="reprintSource" placeholder="转载来源" style="width:260px">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row" id="reprintProhibitArea">
|
||||||
|
<span class="meta-label"></span>
|
||||||
|
<label class="meta-checkbox"><input type="checkbox" id="reprintProhibited"> 未经作者许可,禁止转载</label>
|
||||||
|
</div>
|
||||||
|
<div class="meta-row">
|
||||||
|
<span class="meta-label">声明</span>
|
||||||
|
<select id="declaration" style="width:auto">
|
||||||
|
<option value="">(无特别声明)</option>
|
||||||
|
<option value="ai_generated">AI 辅助创作</option>
|
||||||
|
<option value="fictional">含虚构情节</option>
|
||||||
|
<option value="speculative">含猜测性内容</option>
|
||||||
|
<option value="disturbing">可能令人不适</option>
|
||||||
|
<option value="spoiler">含剧透内容</option>
|
||||||
|
<option value="controversial">涉及争议性话题</option>
|
||||||
|
<option value="opinion">个人观点</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="editor-main">
|
<div class="editor-main">
|
||||||
<div class="editor-pane">
|
<div class="editor-pane">
|
||||||
<div id="vditor"></div>
|
<div id="vditor"></div>
|
||||||
|
|||||||
@ -1374,3 +1374,39 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- 文章属性声明 --- */
|
||||||
|
.post-declarations {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #fffbeb;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-declaration-item {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #92400e;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-badge-private {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-badge-pinned {
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|||||||
@ -759,3 +759,48 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Editor Meta (文章属性) --- */
|
||||||
|
.editor-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 24px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-radio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-radio input[type="radio"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|||||||
@ -98,6 +98,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
document.getElementById('postForm')?.addEventListener('submit', handleSubmit)
|
document.getElementById('postForm')?.addEventListener('submit', handleSubmit)
|
||||||
|
|
||||||
|
// 类型切换联动:转载→显示来源,原创→显示禁止转载
|
||||||
|
const typeRadios = document.querySelectorAll('input[name="post_type"]')
|
||||||
|
const reprintSourceArea = document.getElementById('reprintSourceArea')
|
||||||
|
const reprintProhibitArea = document.getElementById('reprintProhibitArea')
|
||||||
|
typeRadios.forEach(r => r.addEventListener('change', () => {
|
||||||
|
const isReprint = document.querySelector('input[name="post_type"]:checked')?.value === 'reprint'
|
||||||
|
if (reprintSourceArea) reprintSourceArea.style.display = isReprint ? '' : 'none'
|
||||||
|
if (reprintProhibitArea) reprintProhibitArea.style.display = isReprint ? 'none' : ''
|
||||||
|
}))
|
||||||
|
|
||||||
if (!isLocked) {
|
if (!isLocked) {
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||||
@ -174,6 +184,13 @@ async function handleSubmit(e) {
|
|||||||
const body = vditor ? vditor.getValue() : ''
|
const body = vditor ? vditor.getValue() : ''
|
||||||
if (!body.trim()) { showToast('请输入正文', 'warning'); return }
|
if (!body.trim()) { showToast('请输入正文', 'warning'); return }
|
||||||
|
|
||||||
|
// 读取文章属性
|
||||||
|
const visibility = document.querySelector('input[name="visibility"]:checked')?.value || 'public'
|
||||||
|
const postType = document.querySelector('input[name="post_type"]:checked')?.value || 'original'
|
||||||
|
const reprintSource = document.getElementById('reprintSource')?.value.trim() || ''
|
||||||
|
const declaration = document.getElementById('declaration')?.value || ''
|
||||||
|
const reprintProhibited = document.getElementById('reprintProhibited')?.checked || false
|
||||||
|
|
||||||
const isEdit = !!postId
|
const isEdit = !!postId
|
||||||
const url = isEdit ? '/api/studio/posts/' + postId : '/api/studio/posts'
|
const url = isEdit ? '/api/studio/posts/' + postId : '/api/studio/posts'
|
||||||
const method = isEdit ? 'PUT' : 'POST'
|
const method = isEdit ? 'PUT' : 'POST'
|
||||||
@ -184,7 +201,13 @@ async function handleSubmit(e) {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': getCSRFToken(),
|
'X-CSRF-Token': getCSRFToken(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ title, body }),
|
body: JSON.stringify({
|
||||||
|
title, body,
|
||||||
|
visibility, post_type: postType,
|
||||||
|
reprint_source: reprintSource,
|
||||||
|
declaration,
|
||||||
|
reprint_prohibited: reprintProhibited,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
<td>{{.ID}}</td>
|
<td>{{.ID}}</td>
|
||||||
<td><a href="/posts/{{.ID}}" target="_blank" class="post-link">{{.Title}}</a>{{if .PendingBody}} <span class="revision-badge">修订中</span>{{end}}</td>
|
<td><a href="/posts/{{.ID}}" target="_blank" class="post-link">{{.Title}}</a>{{if .PendingBody}} <span class="revision-badge">修订中</span>{{end}}</td>
|
||||||
<td>{{if .AuthorName}}{{.AuthorName}}({{.UserID}}){{else}}UID{{.UserID}}{{end}}</td>
|
<td>{{if .AuthorName}}{{.AuthorName}}({{.UserID}}){{else}}UID{{.UserID}}{{end}}</td>
|
||||||
<td><span class="status-badge status-{{.Status}}">{{index $.StatusNames .Status}}</span>{{if .IsLocked}} <span class="status-badge status-locked">已锁定</span>{{end}}</td>
|
<td><span class="status-badge status-{{.Status}}">{{index $.StatusNames .Status}}</span>{{if .IsLocked}} <span class="status-badge status-locked">已锁定</span>{{end}}{{if .PinType}} <span class="status-badge status-pinned">📌</span>{{end}}</td>
|
||||||
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
|
<td>{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
|
||||||
<td class="actions-cell">
|
<td class="actions-cell">
|
||||||
<div class="action-btns">
|
<div class="action-btns">
|
||||||
@ -63,6 +63,14 @@
|
|||||||
{{if .IsLocked}}
|
{{if .IsLocked}}
|
||||||
<button class="btn btn-sm btn-success post-unlock" data-id="{{.ID}}">解锁</button>
|
<button class="btn btn-sm btn-success post-unlock" data-id="{{.ID}}">解锁</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if eq .Status "approved"}}
|
||||||
|
{{if not .PinType}}
|
||||||
|
<button class="btn btn-sm btn-info post-pin" data-id="{{.ID}}" data-pin-type="global">全局置顶</button>
|
||||||
|
<button class="btn btn-sm btn-info post-pin" data-id="{{.ID}}" data-pin-type="category">分类置顶</button>
|
||||||
|
{{else}}
|
||||||
|
<button class="btn btn-sm btn-secondary post-unpin" data-id="{{.ID}}">取消置顶</button>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -80,6 +80,7 @@
|
|||||||
.status-approved { background: #d1fae5; color: #059669; }
|
.status-approved { background: #d1fae5; color: #059669; }
|
||||||
.status-rejected { background: #fee2e2; color: #dc2626; }
|
.status-rejected { background: #fee2e2; color: #dc2626; }
|
||||||
.status-locked { background: #fee2e2; color: #b91c1c; }
|
.status-locked { background: #fee2e2; color: #b91c1c; }
|
||||||
|
.status-pinned { background: #dbeafe; color: #1d4ed8; }
|
||||||
|
|
||||||
.revision-section { margin-bottom: 24px; }
|
.revision-section { margin-bottom: 24px; }
|
||||||
.revision-section h2 { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #4338ca; }
|
.revision-section h2 { font-size: 16px; font-weight: 600; margin-bottom: 12px; color: #4338ca; }
|
||||||
@ -111,6 +112,7 @@
|
|||||||
.btn-success { background: #10b981; color: #fff; }
|
.btn-success { background: #10b981; color: #fff; }
|
||||||
.btn-warning { background: #f59e0b; color: #fff; }
|
.btn-warning { background: #f59e0b; color: #fff; }
|
||||||
.btn-danger { background: #ef4444; color: #fff; }
|
.btn-danger { background: #ef4444; color: #fff; }
|
||||||
|
.btn-info { background: #3b82f6; color: #fff; }
|
||||||
|
|
||||||
/* 分页 */
|
/* 分页 */
|
||||||
.pagination {
|
.pagination {
|
||||||
|
|||||||
@ -52,4 +52,22 @@
|
|||||||
.then(function(d) { if (d.success) refreshPage(); else showToast(d.message || '操作失败', 'error'); })
|
.then(function(d) { if (d.success) refreshPage(); else showToast(d.message || '操作失败', 'error'); })
|
||||||
.catch(function() { showToast('操作失败', 'error'); });
|
.catch(function() { showToast('操作失败', 'error'); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 置顶
|
||||||
|
document.querySelectorAll('.post-pin').forEach(function(btn) {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
var id = btn.getAttribute('data-id');
|
||||||
|
var pinType = btn.getAttribute('data-pin-type');
|
||||||
|
api.post('/api/admin/posts/' + id + '/pin', { pin_type: pinType })
|
||||||
|
.then(function(d) { if (d.success) refreshPage(); else showToast(d.message || '操作失败', 'error'); })
|
||||||
|
.catch(function() { showToast('操作失败', 'error'); });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取消置顶
|
||||||
|
handleClick('.post-unpin', '确定取消置顶吗?', function(id) {
|
||||||
|
api.post('/api/admin/posts/' + id + '/unpin')
|
||||||
|
.then(function(d) { if (d.success) refreshPage(); else showToast(d.message || '操作失败', 'error'); })
|
||||||
|
.catch(function() { showToast('操作失败', 'error'); });
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user