mirror of https://github.com/halo-dev/halo
611 lines
18 KiB
Vue
611 lines
18 KiB
Vue
<template>
|
|
<a-drawer
|
|
title="文章设置"
|
|
:width="isMobile()?'100%':'480'"
|
|
placement="right"
|
|
closable
|
|
destroyOnClose
|
|
@close="onClose"
|
|
:visible="visible"
|
|
:afterVisibleChange="handleAfterVisibleChanged"
|
|
>
|
|
<div class="post-setting-drawer-content">
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">基本设置</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<a-form layout="vertical">
|
|
<a-form-item
|
|
label="文章标题:"
|
|
v-if="needTitle"
|
|
>
|
|
<a-input v-model="selectedPost.title" />
|
|
</a-form-item>
|
|
<a-form-item
|
|
label="文章别名:"
|
|
:help="fullPath"
|
|
>
|
|
<a-input v-model="selectedPost.slug" />
|
|
</a-form-item>
|
|
|
|
<a-form-item label="发表时间:">
|
|
<a-date-picker
|
|
showTime
|
|
:defaultValue="pickerDefaultValue"
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
placeholder="选择文章发表时间"
|
|
@change="onPostDateChange"
|
|
@ok="onPostDateOk"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="开启评论:">
|
|
<a-radio-group
|
|
v-model="selectedPost.disallowComment"
|
|
:defaultValue="false"
|
|
>
|
|
<a-radio :value="false">开启</a-radio>
|
|
<a-radio :value="true">关闭</a-radio>
|
|
</a-radio-group>
|
|
</a-form-item>
|
|
<a-form-item label="是否置顶:">
|
|
<a-radio-group
|
|
v-model="selectedPost.topPriority"
|
|
:defaultValue="0"
|
|
>
|
|
<a-radio :value="1">是</a-radio>
|
|
<a-radio :value="0">否</a-radio>
|
|
</a-radio-group>
|
|
</a-form-item>
|
|
<a-form-item
|
|
label="自定义模板:"
|
|
v-if="customTpls.length > 0"
|
|
>
|
|
<a-select v-model="selectedPost.template">
|
|
<a-select-option
|
|
key=""
|
|
value=""
|
|
>无</a-select-option>
|
|
<a-select-option
|
|
v-for="tpl in customTpls"
|
|
:key="tpl"
|
|
:value="tpl"
|
|
>{{ tpl }}</a-select-option>
|
|
</a-select>
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">分类目录</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<a-form layout="vertical">
|
|
<a-form-item>
|
|
<category-tree
|
|
v-model="selectedCategoryIds"
|
|
:categories="categories"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item v-if="categoryFormVisible">
|
|
<category-select-tree
|
|
:categories="categories"
|
|
v-model="categoryToCreate.parentId"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item v-if="categoryFormVisible">
|
|
<a-input
|
|
placeholder="分类名称"
|
|
v-model="categoryToCreate.name"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item v-if="categoryFormVisible">
|
|
<a-input
|
|
placeholder="分类路径"
|
|
v-model="categoryToCreate.slug"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item>
|
|
<a-space>
|
|
<a-button
|
|
type="primary"
|
|
v-if="categoryFormVisible"
|
|
@click="handlerCreateCategory"
|
|
>保存</a-button>
|
|
<a-button
|
|
type="dashed"
|
|
v-if="!categoryFormVisible"
|
|
@click="categoryFormVisible = true"
|
|
>新增</a-button>
|
|
<a-button
|
|
v-if="categoryFormVisible"
|
|
@click="categoryFormVisible = false"
|
|
>取消</a-button>
|
|
</a-space>
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">标签</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<a-form layout="vertical">
|
|
<a-form-item>
|
|
<TagSelect v-model="selectedTagIds" />
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">摘要</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<a-form layout="vertical">
|
|
<a-form-item>
|
|
<a-input
|
|
type="textarea"
|
|
:autoSize="{ minRows: 5 }"
|
|
v-model="selectedPost.summary"
|
|
placeholder="如不填写,会从文章中自动截取"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">封面图</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<div class="post-thumb">
|
|
<img
|
|
class="img"
|
|
:src="selectedPost.thumbnail || '/images/placeholder.jpg'"
|
|
@click="thumbDrawerVisible=true"
|
|
>
|
|
|
|
<a-form layout="vertial">
|
|
<a-form-item>
|
|
<a-input
|
|
v-model="selectedPost.thumbnail"
|
|
placeholder="点击封面图选择图片,或者输入外部链接"
|
|
></a-input>
|
|
</a-form-item>
|
|
</a-form>
|
|
|
|
<a-button
|
|
class="post-thumb-remove"
|
|
type="dashed"
|
|
@click="selectedPost.thumbnail = null"
|
|
>移除</a-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a-divider class="divider-transparent" />
|
|
</div>
|
|
<AttachmentSelectDrawer
|
|
v-model="thumbDrawerVisible"
|
|
@listenToSelect="handleSelectPostThumb"
|
|
:drawerWidth="480"
|
|
/>
|
|
|
|
<a-drawer
|
|
title="高级设置"
|
|
:width="isMobile()?'100%':'480'"
|
|
placement="right"
|
|
closable
|
|
destroyOnClose
|
|
@close="advancedVisible = false"
|
|
:visible="advancedVisible"
|
|
>
|
|
<div class="post-setting-drawer-content">
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">加密设置</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<a-form layout="vertical">
|
|
<a-form-item label="访问密码:">
|
|
<a-input-password
|
|
v-model="selectedPost.password"
|
|
autocomplete="new-password"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">SEO 设置</h3>
|
|
<div class="post-setting-drawer-item">
|
|
<a-form layout="vertical">
|
|
<a-form-item label="自定义关键词:">
|
|
<a-input
|
|
v-model="selectedPost.metaKeywords"
|
|
placeholder="多个关键词以英文逗号隔开,如不填写,将自动使用标签作为关键词"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="自定义描述:">
|
|
<a-input
|
|
type="textarea"
|
|
:autoSize="{ minRows: 5 }"
|
|
v-model="selectedPost.metaDescription"
|
|
placeholder="如不填写,会从文章中自动截取"
|
|
/>
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
</div>
|
|
<a-divider />
|
|
<div class="mb-4">
|
|
<h3 class="post-setting-drawer-title">元数据</h3>
|
|
<a-form layout="vertical">
|
|
<a-form-item
|
|
v-for="(meta, index) in selectedMetas"
|
|
:key="index"
|
|
:prop="'metas.' + index + '.value'"
|
|
>
|
|
<a-row :gutter="5">
|
|
<a-col :span="12">
|
|
<a-input v-model="meta.key"><i slot="addonBefore">K</i></a-input>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-input v-model="meta.value">
|
|
<i slot="addonBefore">V</i>
|
|
<a
|
|
href="javascript:void(0);"
|
|
slot="addonAfter"
|
|
@click.prevent="handleRemovePostMeta(meta)"
|
|
>
|
|
<a-icon type="close" />
|
|
</a>
|
|
</a-input>
|
|
</a-col>
|
|
</a-row>
|
|
</a-form-item>
|
|
<a-form-item>
|
|
<a-button
|
|
type="dashed"
|
|
@click="handleInsertPostMeta"
|
|
>新增</a-button>
|
|
</a-form-item>
|
|
</a-form>
|
|
</div>
|
|
<a-divider class="divider-transparent" />
|
|
</div>
|
|
</a-drawer>
|
|
|
|
<div class="bottom-control">
|
|
<a-space>
|
|
<a-button
|
|
type="dashed"
|
|
@click="advancedVisible = true"
|
|
>高级</a-button>
|
|
<ReactiveButton
|
|
type="danger"
|
|
v-if="saveDraftButton"
|
|
@click="handleDraftClick"
|
|
@callback="handleSavedCallback"
|
|
:loading="draftSaving"
|
|
:errored="draftSavedErrored"
|
|
text="保存草稿"
|
|
loadedText="保存成功"
|
|
erroredText="保存失败"
|
|
></ReactiveButton>
|
|
<ReactiveButton
|
|
@click="handlePublishClick"
|
|
@callback="handleSavedCallback"
|
|
:loading="saving"
|
|
:errored="savedErrored"
|
|
:text="`${selectedPost.id?'保存':'发布'}`"
|
|
:loadedText="`${selectedPost.id?'保存':'发布'}成功`"
|
|
:erroredText="`${selectedPost.id?'保存':'发布'}失败`"
|
|
></ReactiveButton>
|
|
</a-space>
|
|
</div>
|
|
</a-drawer>
|
|
</template>
|
|
<script>
|
|
// components
|
|
import CategoryTree from './CategoryTree'
|
|
import CategorySelectTree from './CategorySelectTree'
|
|
import TagSelect from './TagSelect'
|
|
|
|
// libs
|
|
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
|
import moment from 'moment'
|
|
import pinyin from 'tiny-pinyin'
|
|
import { mapGetters } from 'vuex'
|
|
import { datetimeFormat } from '@/utils/util'
|
|
|
|
// apis
|
|
import categoryApi from '@/api/category'
|
|
import postApi from '@/api/post'
|
|
import themeApi from '@/api/theme'
|
|
|
|
export default {
|
|
name: 'PostSettingDrawer',
|
|
mixins: [mixin, mixinDevice],
|
|
components: {
|
|
CategoryTree,
|
|
CategorySelectTree,
|
|
TagSelect
|
|
},
|
|
data() {
|
|
return {
|
|
thumbDrawerVisible: false,
|
|
categoryFormVisible: false,
|
|
advancedVisible: false,
|
|
selectedPost: this.post,
|
|
selectedTagIds: this.tagIds,
|
|
selectedCategoryIds: this.categoryIds,
|
|
categories: [],
|
|
categoryToCreate: {},
|
|
customTpls: [],
|
|
saving: false,
|
|
savedErrored: false,
|
|
draftSaving: false,
|
|
draftSavedErrored: false
|
|
}
|
|
},
|
|
props: {
|
|
post: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
tagIds: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
categoryIds: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
metas: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
needTitle: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false
|
|
},
|
|
saveDraftButton: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true
|
|
},
|
|
visible: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true
|
|
}
|
|
},
|
|
watch: {
|
|
post(val) {
|
|
this.selectedPost = val
|
|
},
|
|
selectedPost(val) {
|
|
this.$emit('onRefreshPost', val)
|
|
},
|
|
tagIds(val) {
|
|
this.selectedTagIds = val
|
|
},
|
|
selectedTagIds(val) {
|
|
this.$emit('onRefreshTagIds', val)
|
|
},
|
|
categoryIds(val) {
|
|
this.selectedCategoryIds = val
|
|
},
|
|
selectedCategoryIds(val) {
|
|
this.$emit('onRefreshCategoryIds', val)
|
|
},
|
|
selectedMetas(val) {
|
|
this.$emit('onRefreshPostMetas', val)
|
|
}
|
|
},
|
|
computed: {
|
|
...mapGetters(['options']),
|
|
selectedMetas() {
|
|
return this.metas
|
|
},
|
|
pickerDefaultValue() {
|
|
if (this.selectedPost.createTime) {
|
|
var date = new Date(this.selectedPost.createTime)
|
|
return moment(date, 'YYYY-MM-DD HH:mm:ss')
|
|
}
|
|
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
|
},
|
|
fullPath() {
|
|
const permalinkType = this.options.post_permalink_type
|
|
const blogUrl = this.options.blog_url
|
|
const archivesPrefix = this.options.archives_prefix
|
|
const pathSuffix = this.options.path_suffix ? this.options.path_suffix : ''
|
|
switch (permalinkType) {
|
|
case 'DEFAULT':
|
|
return `${blogUrl}/${archivesPrefix}/${
|
|
this.selectedPost.slug ? this.selectedPost.slug : '{slug}'
|
|
}${pathSuffix}`
|
|
case 'DATE':
|
|
return `${blogUrl}${datetimeFormat(
|
|
this.selectedPost.createTime ? this.selectedPost.createTime : new Date(),
|
|
'/YYYY/MM/'
|
|
)}${this.selectedPost.slug ? this.selectedPost.slug : '{slug}'}${pathSuffix}`
|
|
case 'DAY':
|
|
return `${blogUrl}${datetimeFormat(
|
|
this.selectedPost.createTime ? this.selectedPost.createTime : new Date(),
|
|
'/YYYY/MM/DD/'
|
|
)}${this.selectedPost.slug ? this.selectedPost.slug : '{slug}'}${pathSuffix}`
|
|
case 'ID':
|
|
return `${blogUrl}/?p=${this.selectedPost.id ? this.selectedPost.id : '{id}'}`
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
handleAfterVisibleChanged(visible) {
|
|
if (visible) {
|
|
this.handleListCategories()
|
|
this.handleListPresetMetasField()
|
|
this.handleListCustomTpls()
|
|
this.handleSetPinyinSlug()
|
|
}
|
|
},
|
|
handleListCategories() {
|
|
categoryApi.listAll().then((response) => {
|
|
this.categories = response.data.data
|
|
})
|
|
},
|
|
handleListPresetMetasField() {
|
|
if (this.metas.length <= 0) {
|
|
themeApi.getActivatedTheme().then((response) => {
|
|
const fields = response.data.data.postMetaField
|
|
if (fields && fields.length > 0) {
|
|
for (let i = 0, len = fields.length; i < len; i++) {
|
|
this.selectedMetas.push({
|
|
value: '',
|
|
key: fields[i]
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
handleListCustomTpls() {
|
|
themeApi.customPostTpls().then((response) => {
|
|
this.customTpls = response.data.data
|
|
})
|
|
},
|
|
handleSelectPostThumb(data) {
|
|
this.selectedPost.thumbnail = encodeURI(data.path)
|
|
this.thumbDrawerVisible = false
|
|
},
|
|
handlerCreateCategory() {
|
|
if (!this.categoryToCreate.name) {
|
|
this.$notification['error']({
|
|
message: '提示',
|
|
description: '分类名称不能为空!'
|
|
})
|
|
return
|
|
}
|
|
categoryApi
|
|
.create(this.categoryToCreate)
|
|
.then((response) => {
|
|
this.categoryToCreate = {}
|
|
this.categoryFormVisible = false
|
|
})
|
|
.finally(() => {
|
|
this.handleListCategories()
|
|
})
|
|
},
|
|
handleDraftClick() {
|
|
this.selectedPost.status = 'DRAFT'
|
|
this.createOrUpdatePost()
|
|
},
|
|
handlePublishClick() {
|
|
if (this.selectedPost.status === 'DRAFT') {
|
|
this.selectedPost.createTime = new Date()
|
|
}
|
|
this.selectedPost.status = 'PUBLISHED'
|
|
this.createOrUpdatePost()
|
|
},
|
|
createOrUpdatePost() {
|
|
if (!this.selectedPost.title) {
|
|
this.$notification['error']({
|
|
message: '提示',
|
|
description: '文章标题不能为空!'
|
|
})
|
|
return
|
|
}
|
|
// Set category ids
|
|
this.selectedPost.categoryIds = this.selectedCategoryIds
|
|
// Set tag ids
|
|
this.selectedPost.tagIds = this.selectedTagIds
|
|
// Set post metas
|
|
this.selectedPost.metas = this.selectedMetas
|
|
if (this.selectedPost.status === 'DRAFT') {
|
|
this.draftSaving = true
|
|
} else {
|
|
this.saving = true
|
|
}
|
|
if (this.selectedPost.id) {
|
|
// Update the post
|
|
postApi
|
|
.update(this.selectedPost.id, this.selectedPost, false)
|
|
.catch(() => {
|
|
if (this.selectedPost.status === 'DRAFT') {
|
|
this.draftSavedErrored = true
|
|
} else {
|
|
this.savedErrored = true
|
|
}
|
|
})
|
|
.finally(() => {
|
|
setTimeout(() => {
|
|
this.saving = false
|
|
this.draftSaving = false
|
|
}, 400)
|
|
})
|
|
} else {
|
|
// Create the post
|
|
postApi
|
|
.create(this.selectedPost, false)
|
|
.catch(() => {
|
|
if (this.selectedPost.status === 'DRAFT') {
|
|
this.draftSavedErrored = true
|
|
} else {
|
|
this.savedErrored = true
|
|
}
|
|
})
|
|
.then((response) => {
|
|
this.selectedPost = response.data.data
|
|
})
|
|
.finally(() => {
|
|
setTimeout(() => {
|
|
this.saving = false
|
|
this.draftSaving = false
|
|
}, 400)
|
|
})
|
|
}
|
|
},
|
|
handleSavedCallback() {
|
|
if (this.draftSavedErrored || this.savedErrored) {
|
|
this.draftSavedErrored = false
|
|
this.savedErrored = false
|
|
} else {
|
|
this.$emit('onSaved', true)
|
|
this.$router.push({ name: 'PostList' })
|
|
}
|
|
},
|
|
onClose() {
|
|
this.$emit('close', false)
|
|
},
|
|
onPostDateChange(value, dateString) {
|
|
this.selectedPost.createTime = value.valueOf()
|
|
},
|
|
onPostDateOk(value) {
|
|
this.selectedPost.createTime = value.valueOf()
|
|
},
|
|
handleRemovePostMeta(item) {
|
|
var index = this.selectedMetas.indexOf(item)
|
|
if (index !== -1) {
|
|
this.selectedMetas.splice(index, 1)
|
|
}
|
|
},
|
|
handleInsertPostMeta() {
|
|
this.selectedMetas.push({
|
|
value: '',
|
|
key: ''
|
|
})
|
|
},
|
|
handleSetPinyinSlug() {
|
|
if (this.selectedPost.title && !this.selectedPost.id) {
|
|
if (pinyin.isSupported()) {
|
|
this.$set(this.selectedPost, 'slug', pinyin.convertToPinyin(this.selectedPost.title, '-', true))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|