mirror of https://github.com/halo-dev/halo-admin
refactor: sheet setting modal (#381)
parent
48d145f053
commit
4ab77d59a0
|
@ -33,14 +33,11 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<SheetSettingDrawer
|
||||
:metas="selectedMetas"
|
||||
:sheet="sheetToStage"
|
||||
:visible="sheetSettingVisible"
|
||||
@close="sheetSettingVisible = false"
|
||||
@onRefreshSheet="onRefreshSheetFromSetting"
|
||||
@onRefreshSheetMetas="onRefreshSheetMetasFromSetting"
|
||||
@onSaved="handleRestoreSavedStatus"
|
||||
<SheetSettingModal
|
||||
:post="sheetToStage"
|
||||
:savedCallback="onSheetSavedCallback"
|
||||
:visible.sync="sheetSettingVisible"
|
||||
@onUpdate="onUpdateFromSetting"
|
||||
/>
|
||||
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
|
@ -51,7 +48,7 @@
|
|||
import { mixin, mixinDevice, mixinPostEdit } from '@/mixins/mixin.js'
|
||||
import { datetimeFormat } from '@/utils/datetime'
|
||||
import { PageView } from '@/layouts'
|
||||
import SheetSettingDrawer from './components/SheetSettingDrawer'
|
||||
import SheetSettingModal from './components/SheetSettingModal'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import MarkdownEditor from '@/components/Editor/MarkdownEditor'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
@ -60,7 +57,7 @@ export default {
|
|||
components: {
|
||||
PageView,
|
||||
AttachmentDrawer,
|
||||
SheetSettingDrawer,
|
||||
SheetSettingModal,
|
||||
MarkdownEditor
|
||||
},
|
||||
mixins: [mixin, mixinDevice, mixinPostEdit],
|
||||
|
@ -69,7 +66,6 @@ export default {
|
|||
attachmentDrawerVisible: false,
|
||||
sheetSettingVisible: false,
|
||||
sheetToStage: {},
|
||||
selectedMetas: [],
|
||||
contentChanges: 0,
|
||||
draftSaving: false,
|
||||
draftSaveErrored: false,
|
||||
|
@ -83,9 +79,7 @@ export default {
|
|||
next(vm => {
|
||||
if (sheetId) {
|
||||
apiClient.sheet.get(sheetId).then(response => {
|
||||
const sheet = response.data
|
||||
vm.sheetToStage = sheet
|
||||
vm.selectedMetas = sheet.metas
|
||||
vm.sheetToStage = response.data
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -234,11 +228,12 @@ export default {
|
|||
this.contentChanges++
|
||||
this.sheetToStage.originalContent = val
|
||||
},
|
||||
onRefreshSheetFromSetting(sheet) {
|
||||
this.sheetToStage = sheet
|
||||
onSheetSavedCallback() {
|
||||
this.contentChanges = 0
|
||||
this.$router.push({ name: 'SheetList', query: { activeKey: 'custom' } })
|
||||
},
|
||||
onRefreshSheetMetasFromSetting(metas) {
|
||||
this.selectedMetas = metas
|
||||
onUpdateFromSetting(sheet) {
|
||||
this.sheetToStage = sheet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -210,17 +210,22 @@
|
|||
@showSizeChange="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
<SheetSettingDrawer
|
||||
:metas="selectedMetas"
|
||||
:needTitle="true"
|
||||
:saveDraftButton="false"
|
||||
:sheet="selectedSheet"
|
||||
:visible="sheetSettingVisible"
|
||||
@close="onSheetSettingsClose"
|
||||
@onRefreshSheet="onRefreshSheetFromSetting"
|
||||
@onRefreshSheetMetas="onRefreshSheetMetasFromSetting"
|
||||
/>
|
||||
|
||||
<SheetSettingModal
|
||||
:loading="sheetSettingLoading"
|
||||
:post="selectedSheet"
|
||||
:savedCallback="onSheetSavedCallback"
|
||||
:visible.sync="sheetSettingVisible"
|
||||
@onClose="selectedSheet = {}"
|
||||
>
|
||||
<template #extraFooter>
|
||||
<a-button :disabled="selectPreviousButtonDisabled" @click="handleSelectPrevious">
|
||||
上一篇
|
||||
</a-button>
|
||||
<a-button :disabled="selectNextButtonDisabled" @click="handleSelectNext">
|
||||
下一篇
|
||||
</a-button>
|
||||
</template>
|
||||
</SheetSettingModal>
|
||||
<TargetCommentDrawer
|
||||
:id="selectedSheet.id"
|
||||
:description="selectedSheet.summary"
|
||||
|
@ -233,7 +238,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import SheetSettingDrawer from './SheetSettingDrawer'
|
||||
import SheetSettingModal from './SheetSettingModal'
|
||||
import TargetCommentDrawer from '../../comment/components/TargetCommentDrawer'
|
||||
import apiClient from '@/utils/api-client'
|
||||
|
||||
|
@ -294,7 +299,7 @@ export default {
|
|||
name: 'CustomSheetList',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
SheetSettingDrawer,
|
||||
SheetSettingModal,
|
||||
TargetCommentDrawer
|
||||
},
|
||||
data() {
|
||||
|
@ -314,8 +319,8 @@ export default {
|
|||
}
|
||||
},
|
||||
selectedSheet: {},
|
||||
selectedMetas: [],
|
||||
sheetSettingVisible: false,
|
||||
sheetSettingLoading: false,
|
||||
sheetCommentVisible: false
|
||||
}
|
||||
},
|
||||
|
@ -332,6 +337,14 @@ export default {
|
|||
size: this.list.params.size,
|
||||
total: this.list.total
|
||||
}
|
||||
},
|
||||
selectPreviousButtonDisabled() {
|
||||
const index = this.list.data.findIndex(sheet => sheet.id === this.selectedSheet.id)
|
||||
return index === 0 && !this.list.hasPrevious
|
||||
},
|
||||
selectNextButtonDisabled() {
|
||||
const index = this.list.data.findIndex(sheet => sheet.id === this.selectedSheet.id)
|
||||
return index === this.list.data.length - 1 && !this.list.hasNext
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -393,7 +406,6 @@ export default {
|
|||
handleShowSheetSettings(sheet) {
|
||||
apiClient.sheet.get(sheet.id).then(response => {
|
||||
this.selectedSheet = response.data
|
||||
this.selectedMetas = this.selectedSheet.metas
|
||||
this.sheetSettingVisible = true
|
||||
})
|
||||
},
|
||||
|
@ -426,14 +438,6 @@ export default {
|
|||
this.list.params.size = size
|
||||
this.handleListSheets()
|
||||
},
|
||||
|
||||
onSheetSettingsClose() {
|
||||
this.sheetSettingVisible = false
|
||||
this.selectedSheet = {}
|
||||
setTimeout(() => {
|
||||
this.handleListSheets(false)
|
||||
}, 500)
|
||||
},
|
||||
onSheetCommentsClose() {
|
||||
this.sheetCommentVisible = false
|
||||
this.selectedSheet = {}
|
||||
|
@ -441,11 +445,50 @@ export default {
|
|||
this.handleListSheets(false)
|
||||
}, 500)
|
||||
},
|
||||
onRefreshSheetFromSetting(sheet) {
|
||||
this.selectedSheet = sheet
|
||||
onSheetSavedCallback() {
|
||||
this.handleListSheets(false)
|
||||
},
|
||||
onRefreshSheetMetasFromSetting(metas) {
|
||||
this.selectedMetas = metas
|
||||
/**
|
||||
* Select previous sheet
|
||||
*/
|
||||
async handleSelectPrevious() {
|
||||
const index = this.list.data.findIndex(post => post.id === this.selectedSheet.id)
|
||||
if (index > 0) {
|
||||
this.sheetSettingLoading = true
|
||||
const response = await apiClient.sheet.get(this.list.data[index - 1].id)
|
||||
this.selectedSheet = response.data
|
||||
this.sheetSettingLoading = false
|
||||
return
|
||||
}
|
||||
if (index === 0 && this.list.hasPrevious) {
|
||||
this.list.params.page--
|
||||
await this.handleListPosts()
|
||||
this.sheetSettingLoading = true
|
||||
const response = await apiClient.sheet.get(this.list.data[this.list.data.length - 1].id)
|
||||
this.selectedSheet = response.data
|
||||
this.sheetSettingLoading = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Select next sheet
|
||||
*/
|
||||
async handleSelectNext() {
|
||||
const index = this.list.data.findIndex(post => post.id === this.selectedSheet.id)
|
||||
if (index < this.list.data.length - 1) {
|
||||
this.sheetSettingLoading = true
|
||||
const response = await apiClient.sheet.get(this.list.data[index + 1].id)
|
||||
this.selectedSheet = response.data
|
||||
this.sheetSettingLoading = false
|
||||
return
|
||||
}
|
||||
if (index === this.list.data.length - 1 && this.list.hasNext) {
|
||||
this.list.params.page++
|
||||
await this.handleListPosts()
|
||||
this.sheetSettingLoading = true
|
||||
const response = await apiClient.sheet.get(this.list.data[0].id)
|
||||
this.selectedSheet = response.data
|
||||
this.sheetSettingLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,344 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model="modalVisible"
|
||||
:afterClose="onClosed"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
:maskClosable="false"
|
||||
:width="680"
|
||||
destroyOnClose
|
||||
>
|
||||
<template #title>
|
||||
{{ modalTitle }}
|
||||
<a-icon v-if="loading" type="loading" />
|
||||
</template>
|
||||
|
||||
<div class="card-container">
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="normal" tab="常规">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" labelAlign="left">
|
||||
<a-form-item label="页面标题">
|
||||
<a-input v-model="form.model.title" />
|
||||
</a-form-item>
|
||||
<a-form-item :help="fullPath" label="页面别名">
|
||||
<a-input v-model="form.model.slug">
|
||||
<template #addonAfter>
|
||||
<a-popconfirm
|
||||
cancel-text="取消"
|
||||
ok-text="确定"
|
||||
placement="left"
|
||||
title="是否确定根据标题重新生成别名?"
|
||||
@confirm="handleGenerateSlug"
|
||||
>
|
||||
<a-icon class="cursor-pointer" type="sync" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="摘要">
|
||||
<a-input
|
||||
v-model="form.model.summary"
|
||||
:autoSize="{ minRows: 5 }"
|
||||
placeholder="如不填写,会从页面中自动截取"
|
||||
type="textarea"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="advanced" tab="高级">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" labelAlign="left">
|
||||
<a-form-item label="禁止评论">
|
||||
<a-switch v-model="form.model.disallowComment" />
|
||||
</a-form-item>
|
||||
<a-form-item label="发表时间:">
|
||||
<a-date-picker
|
||||
:defaultValue="createTimeDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择页面发表时间"
|
||||
showTime
|
||||
@change="onCreateTimeSelect"
|
||||
@ok="onCreateTimeSelect"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义模板:">
|
||||
<a-select v-model="form.model.template">
|
||||
<a-select-option key="" value="">无</a-select-option>
|
||||
<a-select-option v-for="template in templates" :key="template" :value="template">
|
||||
{{ template }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="封面图:">
|
||||
<div class="post-thumb">
|
||||
<a-space direction="vertical">
|
||||
<img
|
||||
:src="form.model.thumbnail || '/images/placeholder.jpg'"
|
||||
alt="Post cover thumbnail"
|
||||
class="img"
|
||||
@click="attachmentSelectVisible = true"
|
||||
/>
|
||||
<a-input v-model="form.model.thumbnail" placeholder="点击封面图选择图片,或者输入外部链接"></a-input>
|
||||
<a-button type="dashed" @click="form.model.thumbnail = null">移除</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="seo" tab="SEO">
|
||||
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }" labelAlign="left">
|
||||
<a-form-item label="自定义关键词">
|
||||
<a-input
|
||||
v-model="form.model.metaKeywords"
|
||||
:autoSize="{ minRows: 5 }"
|
||||
placeholder="多个关键词以英文逗号隔开"
|
||||
type="textarea"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义描述">
|
||||
<a-input
|
||||
v-model="form.model.metaDescription"
|
||||
:autoSize="{ minRows: 5 }"
|
||||
placeholder="如不填写,会从页面中自动截取"
|
||||
type="textarea"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="meta" tab="元数据">
|
||||
<MetaEditor :metas.sync="form.model.metas" :targetId="form.model.id" target="sheet" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<template slot="footer">
|
||||
<slot name="extraFooter" />
|
||||
<a-button :disabled="loading" @click="modalVisible = false">
|
||||
关闭
|
||||
</a-button>
|
||||
<ReactiveButton
|
||||
v-if="!form.model.id"
|
||||
:errored="form.draftSaveErrored"
|
||||
:loading="form.draftSaving"
|
||||
erroredText="保存失败"
|
||||
loadedText="保存成功"
|
||||
text="保存草稿"
|
||||
type="danger"
|
||||
@callback="handleSavedCallback"
|
||||
@click="handleCreateOrUpdate('DRAFT')"
|
||||
></ReactiveButton>
|
||||
<ReactiveButton
|
||||
:errored="form.saveErrored"
|
||||
:erroredText="`${form.model.id ? '保存' : '发布'}失败`"
|
||||
:loadedText="`${form.model.id ? '保存' : '发布'}成功`"
|
||||
:loading="form.saving"
|
||||
:text="`${form.model.id ? '保存' : '发布'}`"
|
||||
@callback="handleSavedCallback"
|
||||
@click="handleCreateOrUpdate()"
|
||||
></ReactiveButton>
|
||||
</template>
|
||||
<AttachmentSelectDrawer
|
||||
v-model="attachmentSelectVisible"
|
||||
:drawerWidth="480"
|
||||
@listenToSelect="handleSelectSheetThumb"
|
||||
/>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
// components
|
||||
import MetaEditor from '@/components/Post/MetaEditor'
|
||||
// libs
|
||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||
import { datetimeFormat } from '@/utils/datetime'
|
||||
import pinyin from 'tiny-pinyin'
|
||||
import { mapGetters } from 'vuex'
|
||||
// apis
|
||||
import apiClient from '@/utils/api-client'
|
||||
export default {
|
||||
name: 'SheetSettingModal',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
MetaEditor
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
post: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
savedCallback: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
model: {},
|
||||
saving: false,
|
||||
saveErrored: false,
|
||||
draftSaving: false,
|
||||
draftSaveErrored: false
|
||||
},
|
||||
templates: [],
|
||||
attachmentSelectVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options']),
|
||||
modalVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:visible', value)
|
||||
}
|
||||
},
|
||||
modalTitle() {
|
||||
return this.form.model.id ? '页面设置' : '页面发布'
|
||||
},
|
||||
createTimeDefaultValue() {
|
||||
if (this.form.model.createTime) {
|
||||
const date = new Date(this.form.model.createTime)
|
||||
return datetimeFormat(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
return datetimeFormat(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
fullPath() {
|
||||
const permalinkType = this.options.sheet_permalink_type
|
||||
const blogUrl = this.options.blog_url
|
||||
const sheetPrefix = this.options.sheet_prefix
|
||||
const pathSuffix = this.options.path_suffix ? this.options.path_suffix : ''
|
||||
const slug = this.form.model.slug || '{slug}'
|
||||
switch (permalinkType) {
|
||||
case 'SECONDARY':
|
||||
return `${blogUrl}/${sheetPrefix}/${slug}${pathSuffix}`
|
||||
case 'ROOT':
|
||||
return `${blogUrl}/${slug}${pathSuffix}`
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modalVisible(value) {
|
||||
if (value) {
|
||||
this.form.model = Object.assign({}, this.post)
|
||||
if (!this.form.model.slug && !this.form.model.id) {
|
||||
this.handleGenerateSlug()
|
||||
}
|
||||
}
|
||||
},
|
||||
post: {
|
||||
deep: true,
|
||||
handler(value) {
|
||||
this.form.model = Object.assign({}, value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.handleListCustomTemplates()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Creates or updates a post
|
||||
*/
|
||||
async handleCreateOrUpdate(preStatus = 'PUBLISHED') {
|
||||
if (!this.form.model.title) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '页面标题不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.form.model.status = preStatus
|
||||
const { id, status } = this.form.model
|
||||
try {
|
||||
this.form[status === 'PUBLISHED' ? 'saving' : 'draftSaving'] = true
|
||||
if (id) {
|
||||
await apiClient.sheet.update(id, this.form.model)
|
||||
} else {
|
||||
await apiClient.sheet.create(this.form.model)
|
||||
}
|
||||
} catch (error) {
|
||||
this.form[status === 'PUBLISHED' ? 'saveErrored' : 'draftSaveErrored'] = true
|
||||
this.$log.error(error)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.form.saving = false
|
||||
this.form.draftSaving = false
|
||||
}, 400)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handle saved callback event
|
||||
*/
|
||||
handleSavedCallback() {
|
||||
if (this.form.saveErrored || this.form.draftSaveErrored) {
|
||||
this.form.saveErrored = false
|
||||
this.form.draftSaveErrored = false
|
||||
} else {
|
||||
this.savedCallback && this.savedCallback()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handle list custom templates
|
||||
*/
|
||||
async handleListCustomTemplates() {
|
||||
try {
|
||||
const response = await apiClient.theme.listCustomSheetTemplates()
|
||||
this.templates = response.data
|
||||
} catch (error) {
|
||||
this.$log.error(error)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handle create time selected event
|
||||
*/
|
||||
onCreateTimeSelect(value) {
|
||||
this.form.model.createTime = value.valueOf()
|
||||
},
|
||||
/**
|
||||
* Generate slug
|
||||
*/
|
||||
handleGenerateSlug() {
|
||||
if (this.form.model.title) {
|
||||
if (pinyin.isSupported()) {
|
||||
let result = ''
|
||||
const tokens = pinyin.parse(this.form.model.title.replace(/\s+/g, '').toLowerCase())
|
||||
let lastToken
|
||||
tokens.forEach(token => {
|
||||
if (token.type === 2) {
|
||||
const target = token.target ? token.target.toLowerCase() : ''
|
||||
result += result && !/\n|\s/.test(lastToken.target) ? '-' + target : target
|
||||
} else {
|
||||
result += (lastToken && lastToken.type === 2 ? '-' : '') + token.target
|
||||
}
|
||||
lastToken = token
|
||||
})
|
||||
this.$set(this.form.model, 'slug', result)
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Select sheet thumbnail
|
||||
* @param data
|
||||
*/
|
||||
handleSelectSheetThumb(data) {
|
||||
this.form.model.thumbnail = encodeURI(data.path)
|
||||
this.attachmentSelectVisible = false
|
||||
},
|
||||
/**
|
||||
* Handle modal close event
|
||||
*/
|
||||
onClosed() {
|
||||
this.$emit('onClose')
|
||||
this.$emit('onUpdate', this.form.model)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue