refactor: sheet setting modal (#381)

pull/383/head
Ryan Wang 2021-11-27 19:58:07 +08:00 committed by GitHub
parent 48d145f053
commit 4ab77d59a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 427 additions and 45 deletions

View File

@ -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
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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>