mirror of https://github.com/halo-dev/halo
515 lines
15 KiB
Vue
515 lines
15 KiB
Vue
<template>
|
|
<div class="page-header-index-wide">
|
|
<a-card :bordered="false">
|
|
<div class="table-page-search-wrapper">
|
|
<a-form layout="inline">
|
|
<a-row :gutter="48">
|
|
<a-col
|
|
:md="6"
|
|
:sm="24"
|
|
>
|
|
<a-form-item label="关键词">
|
|
<a-input v-model="queryParam.keyword" />
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col
|
|
:md="6"
|
|
:sm="24"
|
|
>
|
|
<a-form-item label="文章状态">
|
|
<a-select
|
|
v-model="queryParam.status"
|
|
placeholder="请选择文章状态"
|
|
@change="handleQuery"
|
|
>
|
|
<a-select-option
|
|
v-for="status in Object.keys(postStatus)"
|
|
:key="status"
|
|
:value="status"
|
|
>{{ postStatus[status].text }}</a-select-option>
|
|
</a-select>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col
|
|
:md="6"
|
|
:sm="24"
|
|
>
|
|
<a-form-item label="分类目录">
|
|
<a-select
|
|
v-model="queryParam.categoryId"
|
|
placeholder="请选择分类"
|
|
@change="handleQuery"
|
|
>
|
|
<a-select-option
|
|
v-for="category in categories"
|
|
:key="category.id"
|
|
>{{ category.name }}</a-select-option>
|
|
</a-select>
|
|
</a-form-item>
|
|
</a-col>
|
|
|
|
<a-col
|
|
:md="6"
|
|
:sm="24"
|
|
>
|
|
<span class="table-page-search-submitButtons">
|
|
<a-button
|
|
type="primary"
|
|
@click="handleQuery"
|
|
>查询</a-button>
|
|
<a-button
|
|
style="margin-left: 8px;"
|
|
@click="handleResetParam"
|
|
>重置</a-button>
|
|
</span>
|
|
</a-col>
|
|
</a-row>
|
|
</a-form>
|
|
</div>
|
|
|
|
<div class="table-operator">
|
|
<router-link :to="{name:'PostEdit'}">
|
|
<a-button
|
|
type="primary"
|
|
icon="plus"
|
|
>写文章</a-button>
|
|
</router-link>
|
|
<a-dropdown v-show="queryParam.status!=null && queryParam.status!=''">
|
|
<a-menu slot="overlay">
|
|
<a-menu-item
|
|
key="1"
|
|
v-if="queryParam.status === 'DRAFT'"
|
|
>
|
|
<a
|
|
href="javascript:void(0);"
|
|
@click="handlePublishMore"
|
|
>
|
|
<span>发布</span>
|
|
</a>
|
|
</a-menu-item>
|
|
<a-menu-item
|
|
key="2"
|
|
v-if="queryParam.status === 'PUBLISHED' || queryParam.status ==='DRAFT'"
|
|
>
|
|
<a
|
|
href="javascript:void(0);"
|
|
@click="handleRecycleMore"
|
|
>
|
|
<span>移到回收站</span>
|
|
</a>
|
|
</a-menu-item>
|
|
<a-menu-item
|
|
key="3"
|
|
v-if="queryParam.status === 'RECYCLE'"
|
|
>
|
|
<a
|
|
href="javascript:void(0);"
|
|
@click="handleDeleteMore"
|
|
>
|
|
<span>永久删除</span>
|
|
</a>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
<a-button style="margin-left: 8px;">
|
|
批量操作
|
|
<a-icon type="down" />
|
|
</a-button>
|
|
</a-dropdown>
|
|
</div>
|
|
<div style="margin-top:15px">
|
|
<a-table
|
|
:rowKey="post => post.id"
|
|
:rowSelection="{
|
|
onChange: onSelectionChange,
|
|
getCheckboxProps: getCheckboxProps
|
|
}"
|
|
:columns="columns"
|
|
:dataSource="formattedPosts"
|
|
:loading="postsLoading"
|
|
:pagination="false"
|
|
>
|
|
<span
|
|
slot="postTitle"
|
|
slot-scope="text,record"
|
|
style="max-width: 150px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
|
|
>
|
|
<a-icon type="pushpin" v-if="record.topPriority!=0" theme="twoTone" twoToneColor="red" />
|
|
<a
|
|
:href="options.blog_url+'/archives/'+record.url"
|
|
target="_blank"
|
|
style="text-decoration: none;"
|
|
>
|
|
<a-tooltip
|
|
placement="topLeft"
|
|
:title="'点击访问【'+text+'】'"
|
|
>{{ text }}</a-tooltip>
|
|
</a>
|
|
</span>
|
|
<span
|
|
slot="status"
|
|
slot-scope="statusProperty"
|
|
>
|
|
<a-badge :status="statusProperty.status" />
|
|
{{ statusProperty.text }}
|
|
</span>
|
|
|
|
<span
|
|
slot="categories"
|
|
slot-scope="categoriesOfPost"
|
|
>
|
|
<a-tag
|
|
v-for="(category,index) in categoriesOfPost"
|
|
:key="index"
|
|
color="blue"
|
|
style="margin-bottom: 8px"
|
|
>{{ category.name }}</a-tag>
|
|
</span>
|
|
|
|
<span
|
|
slot="tags"
|
|
slot-scope="tags"
|
|
>
|
|
<a-tag
|
|
v-for="(tag, index) in tags"
|
|
:key="index"
|
|
color="green"
|
|
style="margin-bottom: 8px"
|
|
>{{ tag.name }}</a-tag>
|
|
</span>
|
|
|
|
<span
|
|
slot="createTime"
|
|
slot-scope="createTime"
|
|
>
|
|
<a-tooltip placement="top">
|
|
<template slot="title">
|
|
{{ createTime | moment }}
|
|
</template>
|
|
{{ createTime | timeAgo }}
|
|
</a-tooltip>
|
|
</span>
|
|
|
|
<span
|
|
slot="action"
|
|
slot-scope="text, post"
|
|
>
|
|
<a
|
|
href="javascript:;"
|
|
@click="handleEditClick(post)"
|
|
v-if="post.status === 'PUBLISHED' || post.status === 'DRAFT'"
|
|
>编辑</a>
|
|
<a-popconfirm
|
|
:title="'你确定要发布【' + post.title + '】文章?'"
|
|
@confirm="handleEditStatusClick(post.id,'PUBLISHED')"
|
|
okText="确定"
|
|
cancelText="取消"
|
|
v-else-if="post.status === 'RECYCLE'"
|
|
>
|
|
<a href="javascript:;">还原</a>
|
|
</a-popconfirm>
|
|
|
|
<a-divider type="vertical" />
|
|
|
|
<a-popconfirm
|
|
:title="'你确定要将【' + post.title + '】文章移到回收站?'"
|
|
@confirm="handleEditStatusClick(post.id,'RECYCLE')"
|
|
okText="确定"
|
|
cancelText="取消"
|
|
v-if="post.status === 'PUBLISHED' || post.status === 'DRAFT'"
|
|
>
|
|
<a href="javascript:;">回收站</a>
|
|
</a-popconfirm>
|
|
|
|
<a-popconfirm
|
|
:title="'你确定要永久删除【' + post.title + '】文章?'"
|
|
@confirm="handleDeleteClick(post.id)"
|
|
okText="确定"
|
|
cancelText="取消"
|
|
v-else-if="post.status === 'RECYCLE'"
|
|
>
|
|
<a href="javascript:;">删除</a>
|
|
</a-popconfirm>
|
|
|
|
<a-divider type="vertical" />
|
|
|
|
<a
|
|
href="javascript:;"
|
|
@click="handleShowPostSettings(post)"
|
|
>设置</a>
|
|
</span>
|
|
</a-table>
|
|
<div class="page-wrapper">
|
|
<a-pagination
|
|
class="pagination"
|
|
:total="pagination.total"
|
|
:pageSizeOptions="['1', '2', '5', '10', '20', '50', '100']"
|
|
showSizeChanger
|
|
@showSizeChange="handlePaginationChange"
|
|
@change="handlePaginationChange"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</a-card>
|
|
|
|
<PostSetting
|
|
:post="selectedPost"
|
|
:tagIds="selectedTagIds"
|
|
:categoryIds="selectedCategoryIds"
|
|
:needTitle="true"
|
|
:saveDraftButton="false"
|
|
:savePublishButton="false"
|
|
:saveButton="true"
|
|
:visible="postSettingVisible"
|
|
@close="onPostSettingsClose"
|
|
@onRefreshPost="onRefreshPostFromSetting"
|
|
@onRefreshTagIds="onRefreshTagIdsFromSetting"
|
|
@onRefreshCategoryIds="onRefreshCategoryIdsFromSetting"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
|
import PostSetting from './components/PostSetting'
|
|
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
|
import TagSelect from './components/TagSelect'
|
|
import CategoryTree from './components/CategoryTree'
|
|
import categoryApi from '@/api/category'
|
|
import postApi from '@/api/post'
|
|
import optionApi from '@/api/option'
|
|
const columns = [
|
|
{
|
|
title: '标题',
|
|
dataIndex: 'title',
|
|
width: '150px',
|
|
scopedSlots: { customRender: 'postTitle' }
|
|
},
|
|
{
|
|
title: '状态',
|
|
className: 'status',
|
|
dataIndex: 'statusProperty',
|
|
width: '100px',
|
|
scopedSlots: { customRender: 'status' }
|
|
},
|
|
{
|
|
title: '分类',
|
|
dataIndex: 'categories',
|
|
scopedSlots: { customRender: 'categories' }
|
|
},
|
|
{
|
|
title: '标签',
|
|
dataIndex: 'tags',
|
|
scopedSlots: { customRender: 'tags' }
|
|
},
|
|
{
|
|
title: '评论',
|
|
width: '70px',
|
|
dataIndex: 'commentCount'
|
|
},
|
|
{
|
|
title: '访问',
|
|
width: '70px',
|
|
dataIndex: 'visits'
|
|
},
|
|
{
|
|
title: '发布时间',
|
|
dataIndex: 'createTime',
|
|
width: '170px',
|
|
scopedSlots: { customRender: 'createTime' }
|
|
},
|
|
{
|
|
title: '操作',
|
|
width: '180px',
|
|
scopedSlots: { customRender: 'action' }
|
|
}
|
|
]
|
|
export default {
|
|
name: 'PostList',
|
|
components: {
|
|
AttachmentSelectDrawer,
|
|
TagSelect,
|
|
CategoryTree,
|
|
PostSetting
|
|
},
|
|
mixins: [mixin, mixinDevice],
|
|
data() {
|
|
return {
|
|
postStatus: postApi.postStatus,
|
|
pagination: {
|
|
current: 1,
|
|
pageSize: 10,
|
|
sort: null
|
|
},
|
|
queryParam: {
|
|
page: 0,
|
|
size: 10,
|
|
sort: null,
|
|
keyword: null,
|
|
categoryId: null,
|
|
status: null
|
|
},
|
|
// 表头
|
|
columns,
|
|
selectedRowKeys: [],
|
|
selectedRows: [],
|
|
categories: [],
|
|
posts: [],
|
|
postsLoading: false,
|
|
postSettingVisible: false,
|
|
selectedPost: {},
|
|
selectedTagIds: [],
|
|
selectedCategoryIds: [],
|
|
options: [],
|
|
keys: ['blog_url']
|
|
}
|
|
},
|
|
computed: {
|
|
formattedPosts() {
|
|
return this.posts.map(post => {
|
|
post.statusProperty = this.postStatus[post.status]
|
|
return post
|
|
})
|
|
}
|
|
},
|
|
created() {
|
|
this.loadPosts()
|
|
this.loadOptions()
|
|
this.loadCategories()
|
|
},
|
|
methods: {
|
|
loadPosts() {
|
|
this.postsLoading = true
|
|
// Set from pagination
|
|
this.queryParam.page = this.pagination.current - 1
|
|
this.queryParam.size = this.pagination.pageSize
|
|
this.queryParam.sort = this.pagination.sort
|
|
postApi.query(this.queryParam).then(response => {
|
|
this.posts = response.data.data.content
|
|
this.pagination.total = response.data.data.total
|
|
this.postsLoading = false
|
|
})
|
|
},
|
|
loadCategories() {
|
|
categoryApi.listAll().then(response => {
|
|
this.categories = response.data.data
|
|
})
|
|
},
|
|
loadOptions() {
|
|
optionApi.listAll(this.keys).then(response => {
|
|
this.options = response.data.data
|
|
})
|
|
},
|
|
handleEditClick(post) {
|
|
this.$router.push({ name: 'PostEdit', query: { postId: post.id } })
|
|
},
|
|
onSelectionChange(selectedRowKeys) {
|
|
this.selectedRowKeys = selectedRowKeys
|
|
this.$log.debug(`SelectedRowKeys: ${selectedRowKeys}`)
|
|
},
|
|
getCheckboxProps(post) {
|
|
return {
|
|
props: {
|
|
disabled: post.status === 'RECYCLE',
|
|
name: post.title
|
|
}
|
|
}
|
|
},
|
|
handlePaginationChange(page, pageSize) {
|
|
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
|
this.pagination.current = page
|
|
this.pagination.pageSize = pageSize
|
|
this.loadPosts()
|
|
},
|
|
handleResetParam() {
|
|
this.queryParam.keyword = null
|
|
this.queryParam.categoryId = null
|
|
this.queryParam.status = null
|
|
this.loadPosts()
|
|
},
|
|
handleQuery() {
|
|
this.queryParam.page = 0
|
|
this.loadPosts()
|
|
},
|
|
handleEditStatusClick(postId, status) {
|
|
postApi.updateStatus(postId, status).then(response => {
|
|
this.$message.success('操作成功!')
|
|
this.loadPosts()
|
|
})
|
|
},
|
|
handleDeleteClick(postId) {
|
|
postApi.delete(postId).then(response => {
|
|
this.$message.success('删除成功!')
|
|
this.loadPosts()
|
|
})
|
|
},
|
|
handlePublishMore() {
|
|
if (this.selectedRowKeys.length <= 0) {
|
|
this.$message.success('请至少选择一项!')
|
|
return
|
|
}
|
|
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
|
const element = this.selectedRowKeys[index]
|
|
postApi.updateStatus(element, 'PUBLISHED').then(response => {
|
|
this.$log.debug(`postId: ${element}, status: PUBLISHED`)
|
|
this.selectedRowKeys = []
|
|
this.loadPosts()
|
|
})
|
|
}
|
|
},
|
|
handleRecycleMore() {
|
|
if (this.selectedRowKeys.length <= 0) {
|
|
this.$message.success('请至少选择一项!')
|
|
return
|
|
}
|
|
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
|
const element = this.selectedRowKeys[index]
|
|
postApi.updateStatus(element, 'RECYCLE').then(response => {
|
|
this.$log.debug(`postId: ${element}, status: RECYCLE`)
|
|
this.selectedRowKeys = []
|
|
this.loadPosts()
|
|
})
|
|
}
|
|
},
|
|
handleDeleteMore() {
|
|
if (this.selectedRowKeys.length <= 0) {
|
|
this.$message.success('请至少选择一项!')
|
|
return
|
|
}
|
|
for (let index = 0; index < this.selectedRowKeys.length; index++) {
|
|
const element = this.selectedRowKeys[index]
|
|
postApi.delete(element).then(response => {
|
|
this.$log.debug(`delete: ${element}`)
|
|
this.selectedRowKeys = []
|
|
this.loadPosts()
|
|
})
|
|
}
|
|
},
|
|
handleShowPostSettings(post) {
|
|
postApi.get(post.id).then(response => {
|
|
this.selectedPost = response.data.data
|
|
this.selectedTagIds = this.selectedPost.tagIds
|
|
this.selectedCategoryIds = this.selectedPost.categoryIds
|
|
this.postSettingVisible = true
|
|
})
|
|
},
|
|
// 关闭文章设置抽屉
|
|
onPostSettingsClose() {
|
|
this.postSettingVisible = false
|
|
this.selectedPost = {}
|
|
this.loadPosts()
|
|
},
|
|
onRefreshPostFromSetting(post) {
|
|
this.selectedPost = post
|
|
},
|
|
onRefreshTagIdsFromSetting(tagIds) {
|
|
this.selectedTagIds = tagIds
|
|
},
|
|
onRefreshCategoryIdsFromSetting(categoryIds) {
|
|
this.selectedCategoryIds = categoryIds
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style scoped>
|
|
</style>
|