pref: created ReactiveButton component. (halo-dev/console#216)

* pref: created ReactiveButton component.

* refactor: ReactiveButton.

* refactor: Profile page.

* feat: add form validate for comment reply.

* refactor: PostSettingsDrawer.
pull/3445/head
Ryan Wang 2020-07-17 09:40:14 +08:00 committed by GitHub
parent 23768f1d18
commit 88867e6957
40 changed files with 1202 additions and 576 deletions

View File

@ -17,7 +17,7 @@
<body> <body>
<noscript> <noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to <strong>We're sorry but halo admin client doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong> continue.</strong>
</noscript> </noscript>
<div id="app"> <div id="app">

View File

@ -14,7 +14,7 @@
align="middle" align="middle"
> >
<a-input-search <a-input-search
placeholder="搜索附件" placeholder="搜索"
v-model="queryParam.keyword" v-model="queryParam.keyword"
@search="handleQuery()" @search="handleQuery()"
enterButton enterButton

View File

@ -0,0 +1,89 @@
<template>
<a-button
:type="computedType"
@click="handleClick"
:icon="computedIcon"
:loading="loading"
>{{ computedText }}</a-button>
</template>
<script>
export default {
name: 'ReactiveButton',
props: {
type: {
type: String,
default: 'primary'
},
icon: {
type: String,
default: null
},
loading: {
type: Boolean,
default: false
},
errored: {
type: Boolean,
default: false
},
text: {
type: String,
default: ''
},
loadedText: {
type: String,
default: ''
},
erroredText: {
type: String,
default: ''
}
},
data() {
return {
loaded: false,
hasError: false
}
},
watch: {
loading(value) {
if (!value) {
this.loaded = true
if (this.errored) {
this.hasError = true
}
setTimeout(() => {
this.loaded = false
this.hasError = false
this.$emit('callback')
}, 800)
}
}
},
computed: {
computedType() {
if (this.loaded) {
return this.hasError ? 'danger' : this.type
}
return this.type
},
computedIcon() {
if (this.loaded) {
return this.hasError ? 'close-circle' : 'check-circle'
}
return this.icon
},
computedText() {
if (this.loaded) {
return this.hasError ? this.erroredText : this.loadedText
}
return this.text
}
},
methods: {
handleClick() {
this.$emit('click')
}
}
}
</script>

View File

@ -51,7 +51,6 @@ export default {
var responseObject = response.data var responseObject = response.data
var HaloEditor = this.$refs.md var HaloEditor = this.$refs.md
HaloEditor.$img2Url(pos, encodeURI(responseObject.data.path)) HaloEditor.$img2Url(pos, encodeURI(responseObject.data.path))
this.$message.success('图片上传成功!')
}) })
}, },
handleSaveDraft() { handleSaveDraft() {

View File

@ -80,7 +80,7 @@ const updateTheme = primaryColor => {
javascriptEnabled: true javascriptEnabled: true
}; };
` `
lessScriptNode.src = 'https://cdnjs.loli.net/ajax/libs/less.js/3.8.1/less.min.js' lessScriptNode.src = 'https://cdn.jsdelivr.net/npm/less@3.8.1/dist/less.min.js'
lessScriptNode.async = true lessScriptNode.async = true
lessScriptNode.onload = () => { lessScriptNode.onload = () => {
buildIt() buildIt()

View File

@ -7,9 +7,9 @@
:allow-multiple="multiple" :allow-multiple="multiple"
:allowRevert="false" :allowRevert="false"
:accepted-file-types="accept" :accepted-file-types="accept"
:maxParallelUploads="loadOptions?options.attachment_upload_max_parallel_uploads:1" :maxParallelUploads="maxParallelUploads"
:allowImagePreview="loadOptions?options.attachment_upload_image_preview_enable:false" :allowImagePreview="allowImagePreview"
:maxFiles="loadOptions?options.attachment_upload_max_files:1" :maxFiles="maxFiles"
labelFileProcessing="上传中" labelFileProcessing="上传中"
labelFileProcessingComplete="上传完成" labelFileProcessingComplete="上传完成"
labelFileProcessingAborted="取消上传" labelFileProcessingAborted="取消上传"
@ -77,6 +77,27 @@ export default {
default: true default: true
} }
}, },
computed: {
...mapGetters(['options']),
maxParallelUploads() {
if (this.options && this.options.length > 0) {
return this.options.attachment_upload_max_parallel_uploads
}
return 1
},
allowImagePreview() {
if (this.options && this.options.length > 0) {
return this.options.attachment_upload_image_preview_enable
}
return false
},
maxFiles() {
if (this.options && this.options.length > 0) {
return this.options.attachment_upload_max_files
}
return 1
}
},
data: function() { data: function() {
return { return {
server: { server: {
@ -120,9 +141,6 @@ export default {
fileList: [] fileList: []
} }
}, },
computed: {
...mapGetters(['options'])
},
methods: { methods: {
handleFilePondInit() { handleFilePondInit() {
this.$log.debug('FilePond has initialized') this.$log.debug('FilePond has initialized')

View File

@ -4,12 +4,14 @@ import Ellipsis from '@/components/Ellipsis'
import FooterToolbar from '@/components/FooterToolbar' import FooterToolbar from '@/components/FooterToolbar'
import FilePondUpload from '@/components/Upload/FilePondUpload' import FilePondUpload from '@/components/Upload/FilePondUpload'
import AttachmentSelectDrawer from './Attachment/AttachmentSelectDrawer' import AttachmentSelectDrawer from './Attachment/AttachmentSelectDrawer'
import ReactiveButton from './Button/ReactiveButton'
const _components = { const _components = {
Ellipsis, Ellipsis,
FooterToolbar, FooterToolbar,
FilePondUpload, FilePondUpload,
AttachmentSelectDrawer AttachmentSelectDrawer,
ReactiveButton
} }
const components = {} const components = {}

View File

@ -40,3 +40,7 @@ export function timeAgo(time) {
export function isObject(value) { export function isObject(value) {
return value && typeof value === 'object' && value.constructor === Object return value && typeof value === 'object' && value.constructor === Object
} }
export function datetimeFormat(value, pattern = 'YYYY-MM-DD HH:mm') {
return moment(value).format(pattern)
}

View File

@ -327,7 +327,7 @@ export default {
this.$contextmenu({ this.$contextmenu({
items: [ items: [
{ {
label: '复制图片链接', label: `${this.handleJudgeMediaType(item) ? '复制图片链接' : '复制文件链接'}`,
onClick: () => { onClick: () => {
const text = `${encodeURI(item.path)}` const text = `${encodeURI(item.path)}`
this.$copyText(text) this.$copyText(text)
@ -343,6 +343,7 @@ export default {
divided: true divided: true
}, },
{ {
disabled: !this.handleJudgeMediaType(item),
label: '复制 Markdown 格式链接', label: '复制 Markdown 格式链接',
onClick: () => { onClick: () => {
const text = `![${item.name}](${encodeURI(item.path)})` const text = `![${item.name}](${encodeURI(item.path)})`

View File

@ -144,7 +144,15 @@
okText="确定" okText="确定"
cancelText="取消" cancelText="取消"
> >
<a-button type="danger">删除</a-button> <ReactiveButton
type="danger"
@callback="handleDeletedCallback"
:loading="deleting"
:errored="deleteErrored"
text="删除"
loadedText="删除成功"
erroredText="删除失败"
></ReactiveButton>
</a-popconfirm> </a-popconfirm>
</div> </div>
</a-drawer> </a-drawer>
@ -173,6 +181,8 @@ export default {
videoPreviewVisible: false, videoPreviewVisible: false,
nonsupportPreviewVisible: false, nonsupportPreviewVisible: false,
player: {}, player: {},
deleting: false,
deleteErrored: false,
videoOptions: { videoOptions: {
lang: 'zh-cn', lang: 'zh-cn',
video: { video: {
@ -214,11 +224,22 @@ export default {
}, },
methods: { methods: {
handleDeleteAttachment() { handleDeleteAttachment() {
attachmentApi.delete(this.attachment.id).then(response => { this.deleting = true
this.$message.success('删除成功!') attachmentApi
this.$emit('delete', this.attachment) .delete(this.attachment.id)
this.onClose() .catch(() => {
}) this.deleteErrored = true
})
.finally(() => {
setTimeout(() => {
this.deleting = false
}, 400)
})
},
handleDeletedCallback() {
this.$emit('delete', this.attachment)
this.deleteErrored = false
this.onClose()
}, },
doUpdateAttachment() { doUpdateAttachment() {
if (!this.attachment.name) { if (!this.attachment.name) {

View File

@ -402,23 +402,31 @@
destroyOnClose destroyOnClose
> >
<template slot="footer"> <template slot="footer">
<a-button <ReactiveButton
key="submit"
type="primary" type="primary"
@click="handleCreateClick" @click="handleCreateClick"
> @callback="handleRepliedCallback"
回复 :loading="replying"
</a-button> :errored="replyErrored"
text="回复"
loadedText="回复成功"
erroredText="回复失败"
></ReactiveButton>
</template> </template>
<a-form layout="vertical"> <a-form-model
<a-form-item> ref="replyCommentForm"
:model="replyComment"
:rules="replyCommentRules"
layout="vertical"
>
<a-form-model-item prop="content">
<a-input <a-input
type="textarea" type="textarea"
:autoSize="{ minRows: 8 }" :autoSize="{ minRows: 8 }"
v-model.trim="replyComment.content" v-model.trim="replyComment.content"
/> />
</a-form-item> </a-form-model-item>
</a-form> </a-form-model>
</a-modal> </a-modal>
<!-- <CommentDetail <!-- <CommentDetail
v-model="commentDetailVisible" v-model="commentDetailVisible"
@ -551,9 +559,14 @@ export default {
comments: [], comments: [],
selectedComment: {}, selectedComment: {},
replyComment: {}, replyComment: {},
replyCommentRules: {
content: [{ required: true, message: '* 内容不能为空', trigger: ['change', 'blur'] }]
},
loading: false, loading: false,
commentStatus: commentApi.commentStatus, commentStatus: commentApi.commentStatus,
commentDetailVisible: false commentDetailVisible: false,
replying: false,
replyErrored: false
} }
}, },
created() { created() {
@ -625,24 +638,32 @@ export default {
} }
}, },
handleCreateClick() { handleCreateClick() {
if (!this.replyComment.content) { const _this = this
this.$notification['error']({ _this.$refs.replyCommentForm.validate(valid => {
message: '提示', if (valid) {
description: '评论内容不能为空!' _this.replying = true
}) commentApi
return .create(_this.type, _this.replyComment)
.catch(() => {
_this.replyErrored = true
})
.finally(() => {
setTimeout(() => {
_this.replying = false
}, 400)
})
}
})
},
handleRepliedCallback() {
if (this.replyErrored) {
this.replyErrored = false
} else {
this.replyComment = {}
this.selectedComment = {}
this.replyCommentVisible = false
this.handleListComments()
} }
commentApi
.create(this.type, this.replyComment)
.then(response => {
this.$message.success('回复成功!')
this.replyComment = {}
this.selectedComment = {}
this.replyCommentVisible = false
})
.finally(() => {
this.handleListComments()
})
}, },
handlePaginationChange(page, pageSize) { handlePaginationChange(page, pageSize) {
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`) this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)

View File

@ -206,36 +206,7 @@
:xs="24" :xs="24"
class="mb-3" class="mb-3"
> >
<a-card <JournalPublishCard />
:bordered="false"
:bodyStyle="{ padding: '16px' }"
>
<template slot="title">
速记
<a-tooltip
slot="action"
title="内容将保存到页面/所有页面/日志页面"
>
<a-icon type="info-circle-o" />
</a-tooltip>
</template>
<a-form layout="vertical">
<a-form-item>
<a-input
type="textarea"
:autoSize="{ minRows: 8 }"
v-model="journal.sourceContent"
placeholder="写点什么吧..."
/>
</a-form-item>
<a-form-item>
<a-button
type="primary"
@click="handleCreateJournalClick"
>保存</a-button>
</a-form-item>
</a-form>
</a-card>
</a-col> </a-col>
<a-col <a-col
:xl="8" :xl="8"
@ -295,6 +266,7 @@
<script> <script>
import { PageView } from '@/layouts' import { PageView } from '@/layouts'
import AnalysisCard from './components/AnalysisCard' import AnalysisCard from './components/AnalysisCard'
import JournalPublishCard from './components/JournalPublishCard'
import RecentCommentTab from './components/RecentCommentTab' import RecentCommentTab from './components/RecentCommentTab'
import LogListDrawer from './components/LogListDrawer' import LogListDrawer from './components/LogListDrawer'
import countTo from 'vue-count-to' import countTo from 'vue-count-to'
@ -302,12 +274,12 @@ import countTo from 'vue-count-to'
import postApi from '@/api/post' import postApi from '@/api/post'
import logApi from '@/api/log' import logApi from '@/api/log'
import statisticsApi from '@/api/statistics' import statisticsApi from '@/api/statistics'
import journalApi from '@/api/journal'
export default { export default {
name: 'Dashboard', name: 'Dashboard',
components: { components: {
PageView, PageView,
AnalysisCard, AnalysisCard,
JournalPublishCard,
RecentCommentTab, RecentCommentTab,
countTo, countTo,
LogListDrawer LogListDrawer
@ -403,22 +375,6 @@ export default {
}, 200) }, 200)
}) })
}, },
handleEditPostClick(post) {
this.$router.push({ name: 'PostEdit', query: { postId: post.id } })
},
handleCreateJournalClick() {
if (!this.journal.sourceContent) {
this.$notification['error']({
message: '提示',
description: '内容不能为空!'
})
return
}
journalApi.create(this.journal).then(response => {
this.$message.success('发表成功!')
this.journal = {}
})
},
handlePostPreview(postId) { handlePostPreview(postId) {
postApi.preview(postId).then(response => { postApi.preview(postId).then(response => {
window.open(response.data, '_blank') window.open(response.data, '_blank')

View File

@ -0,0 +1,83 @@
<template>
<a-card
:bordered="false"
:bodyStyle="{ padding: '16px' }"
>
<template slot="title">
速记
<a-tooltip
slot="action"
title="内容将保存到页面/所有页面/日志页面"
>
<a-icon type="info-circle-o" />
</a-tooltip>
</template>
<a-form-model
ref="journalForm"
:model="form.model"
:rules="form.rules"
layout="vertical"
>
<a-form-model-item prop="sourceContent">
<a-input
type="textarea"
:autoSize="{ minRows: 8 }"
v-model="form.model.sourceContent"
placeholder="写点什么吧..."
/>
</a-form-model-item>
<a-form-model-item>
<ReactiveButton
@click="handleCreateJournalClick"
@callback="() => {
if(!form.errored) form.model = {}
form.errored = false
}"
:loading="form.saving"
:errored="form.errored"
text="发布"
loadedText="发布成功"
erroredText="发布失败"
></ReactiveButton>
</a-form-model-item>
</a-form-model>
</a-card>
</template>
<script>
import journalApi from '@/api/journal'
export default {
name: 'JournalPublishCard',
data() {
return {
form: {
model: {},
rules: {
sourceContent: [{ required: true, message: '* 内容不能为空', trigger: ['change', 'blur'] }]
},
saving: false,
errored: false
}
}
},
methods: {
handleCreateJournalClick() {
const _this = this
_this.$refs.journalForm.validate(valid => {
if (valid) {
_this.form.saving = true
journalApi
.create(_this.form.model)
.catch(() => {
this.form.errored = true
})
.finally(() => {
setTimeout(() => {
_this.form.saving = false
}, 400)
})
}
})
}
}
}
</script>

View File

@ -85,18 +85,28 @@
</a-select> </a-select>
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
v-if="!isUpdateMode"
type="primary" type="primary"
@click="handleCreateOrUpdateMenu" @click="handleCreateOrUpdateMenu"
v-if="!isUpdateMode" @callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>保存</a-button> :errored="form.errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<a-button-group v-else> <a-button-group v-else>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleCreateOrUpdateMenu" @click="handleCreateOrUpdateMenu"
@callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>更新</a-button> :errored="form.errored"
text="更新"
loadedText="更新成功"
erroredText="更新失败"
></ReactiveButton>
<a-button <a-button
type="dashed" type="dashed"
@click="form.model = {}" @click="form.model = {}"
@ -266,6 +276,7 @@ export default {
target: '_self' target: '_self'
}, },
saving: false, saving: false,
errored: false,
rules: { rules: {
name: [ name: [
{ required: true, message: '* 菜单名称不能为空', trigger: ['change', 'blur'] }, { required: true, message: '* 菜单名称不能为空', trigger: ['change', 'blur'] },
@ -338,34 +349,38 @@ export default {
if (_this.isUpdateMode) { if (_this.isUpdateMode) {
menuApi menuApi
.update(_this.form.model.id, _this.form.model) .update(_this.form.model.id, _this.form.model)
.then(response => { .catch(() => {
_this.$message.success('更新成功!') _this.form.errored = true
_this.form.model = { target: '_self' }
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListMenus()
_this.handleListTeams()
}) })
} else { } else {
menuApi menuApi
.create(_this.form.model) .create(_this.form.model)
.then(response => { .catch(() => {
_this.$message.success('保存成功!') _this.form.errored = true
_this.form.model = { target: '_self' }
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListMenus()
_this.handleListTeams()
}) })
} }
} }
}) })
},
handleSavedCallback() {
const _this = this
if (_this.form.errored) {
_this.form.errored = false
} else {
_this.form.model = { target: '_self' }
_this.handleListMenus()
_this.handleListTeams()
}
} }
} }
} }

View File

@ -55,12 +55,16 @@
></codemirror> ></codemirror>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <ReactiveButton
type="primary"
@click="handlerSaveContent" @click="handlerSaveContent"
:disabled="buttonDisabled" @callback="saveErrored=false"
:loading="saving" :loading="saving"
>保存</a-button> :errored="saveErrored"
:disabled="buttonDisabled"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-card> </a-card>
@ -95,7 +99,8 @@ export default {
themes: [], themes: [],
themesLoading: false, themesLoading: false,
selectedTheme: {}, selectedTheme: {},
saving: false saving: false,
saveErrored: false
} }
}, },
created() { created() {
@ -186,8 +191,8 @@ export default {
this.saving = true this.saving = true
themeApi themeApi
.saveContent(this.selectedTheme.id, this.file.path, this.content) .saveContent(this.selectedTheme.id, this.file.path, this.content)
.then(response => { .catch(() => {
this.$message.success('保存成功!') this.saveErrored = true
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {

View File

@ -41,7 +41,7 @@
</div> </div>
<div <div
v-else v-else
@click="handleActivateClick(item)" @click="handleActiveTheme(item)"
> >
<a-icon <a-icon
type="lock" type="lock"
@ -350,15 +350,10 @@ export default {
}, 200) }, 200)
}) })
}, },
activeTheme(themeId) { handleActiveTheme(theme) {
themeApi themeApi.active(theme.id).finally(() => {
.active(themeId) this.handleListThemes()
.then(response => { })
this.$message.success('设置成功!')
})
.finally(() => {
this.handleListThemes()
})
}, },
handleUpdateTheme(themeId) { handleUpdateTheme(themeId) {
const hide = this.$message.loading('更新中...', 0) const hide = this.$message.loading('更新中...', 0)
@ -397,9 +392,6 @@ export default {
handleEditClick(theme) { handleEditClick(theme) {
this.settingDrawer(theme) this.settingDrawer(theme)
}, },
handleActivateClick(theme) {
this.activeTheme(theme.id)
},
handleFetching() { handleFetching() {
if (!this.fetchingUrl) { if (!this.fetchingUrl) {
this.$notification['error']({ this.$notification['error']({
@ -447,14 +439,9 @@ export default {
}) })
}, },
handleReload() { handleReload() {
themeApi themeApi.reload().finally(() => {
.reload() this.handleListThemes()
.then(response => { })
this.$message.success('刷新成功!')
})
.finally(() => {
this.handleListThemes()
})
}, },
handleShowUpdateNewThemeModal(item) { handleShowUpdateNewThemeModal(item) {
this.prepareUpdateTheme = item this.prepareUpdateTheme = item

View File

@ -220,11 +220,16 @@
@click="toggleViewMode" @click="toggleViewMode"
class="mr-2" class="mr-2"
>预览模式</a-button> >预览模式</a-button>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveSettings" @click="handleSaveSettings"
@callback="saveErrored=false"
:loading="saving" :loading="saving"
>保存</a-button> :errored="saveErrored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</footer-tool-bar> </footer-tool-bar>
</a-drawer> </a-drawer>
</template> </template>
@ -258,7 +263,8 @@ export default {
viewMode: false, viewMode: false,
formColValue: 12, formColValue: 12,
clientHeight: document.documentElement.clientHeight, clientHeight: document.documentElement.clientHeight,
saving: false saving: false,
saveErrored: false
} }
}, },
model: { model: {
@ -311,11 +317,13 @@ export default {
themeApi themeApi
.saveSettings(this.theme.id, this.themeSettings) .saveSettings(this.theme.id, this.themeSettings)
.then(response => { .then(response => {
this.$message.success('保存成功!')
if (this.viewMode) { if (this.viewMode) {
document.getElementById('themeViewIframe').contentWindow.location.reload(true) document.getElementById('themeViewIframe').contentWindow.location.reload(true)
} }
}) })
.catch(() => {
this.saveErrored = true
})
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.saving = false this.saving = false

View File

@ -69,18 +69,28 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
v-if="!isUpdateMode"
type="primary" type="primary"
@click="handleCreateOrUpdateCategory" @click="handleCreateOrUpdateCategory"
v-if="!isUpdateMode" @callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>保存</a-button> :errored="form.errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<a-button-group v-else> <a-button-group v-else>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleCreateOrUpdateCategory" @click="handleCreateOrUpdateCategory"
@callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>更新</a-button> :errored="form.errored"
text="更新"
loadedText="更新成功"
erroredText="更新失败"
></ReactiveButton>
<a-button <a-button
type="dashed" type="dashed"
@click="form.model = {}" @click="form.model = {}"
@ -295,6 +305,7 @@ export default {
form: { form: {
model: {}, model: {},
saving: false, saving: false,
errored: false,
rules: { rules: {
name: [ name: [
{ required: true, message: '* 分类名称不能为空', trigger: ['change', 'blur'] }, { required: true, message: '* 分类名称不能为空', trigger: ['change', 'blur'] },
@ -361,33 +372,38 @@ export default {
if (_this.isUpdateMode) { if (_this.isUpdateMode) {
categoryApi categoryApi
.update(_this.form.model.id, _this.form.model) .update(_this.form.model.id, _this.form.model)
.then(response => { .catch(() => {
_this.$message.success('更新成功!') this.form.errored = true
_this.form.model = {}
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListCategories()
}) })
} else { } else {
categoryApi categoryApi
.create(this.form.model) .create(this.form.model)
.then(response => { .catch(() => {
_this.$message.success('保存成功!') this.form.errored = true
_this.form.model = {}
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListCategories()
}) })
} }
} }
}) })
}, },
handleSavedCallback() {
if (this.form.errored) {
this.form.errored = false
} else {
const _this = this
_this.form.model = {}
_this.handleListCategories()
}
},
handleCreateMenuByCategory(category) { handleCreateMenuByCategory(category) {
const menu = { const menu = {
name: category.name, name: category.name,

View File

@ -43,12 +43,17 @@
<AttachmentDrawer v-model="attachmentDrawerVisible" /> <AttachmentDrawer v-model="attachmentDrawerVisible" />
<footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}"> <footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}">
<a-button <ReactiveButton
type="danger" type="danger"
class="mr-2" class="mr-2"
@click="handleSaveDraft(false)" @click="handleSaveDraft(false)"
@callback="draftSavederrored = false"
:loading="draftSaving" :loading="draftSaving"
>保存草稿</a-button> :errored="draftSavederrored"
text="保存草稿"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<a-button <a-button
@click="handlePreview" @click="handlePreview"
class="mr-2" class="mr-2"
@ -98,7 +103,8 @@ export default {
isSaved: false, isSaved: false,
contentChanges: 0, contentChanges: 0,
draftSaving: false, draftSaving: false,
previewSaving: false previewSaving: false,
draftSavederrored: false
} }
}, },
beforeRouteEnter(to, from, next) { beforeRouteEnter(to, from, next) {
@ -190,8 +196,8 @@ export default {
if (draftOnly) { if (draftOnly) {
postApi postApi
.updateDraft(this.postToStage.id, this.postToStage.originalContent) .updateDraft(this.postToStage.id, this.postToStage.originalContent)
.then(response => { .catch(() => {
this.$message.success('保存草稿成功!') this.draftSavederrored = true
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
@ -201,9 +207,10 @@ export default {
} else { } else {
postApi postApi
.update(this.postToStage.id, this.postToStage, false) .update(this.postToStage.id, this.postToStage, false)
.catch(() => {
this.draftSavederrored = true
})
.then(response => { .then(response => {
this.$log.debug('Updated post', response.data.data)
this.$message.success('保存草稿成功!')
this.postToStage = response.data.data this.postToStage = response.data.data
}) })
.finally(() => { .finally(() => {
@ -216,15 +223,16 @@ export default {
// Create the post // Create the post
postApi postApi
.create(this.postToStage, false) .create(this.postToStage, false)
.catch(() => {
this.draftSavederrored = true
})
.then(response => { .then(response => {
this.$log.debug('Created post', response.data.data)
this.$message.success('保存草稿成功!')
this.postToStage = response.data.data this.postToStage = response.data.data
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.draftSaving = false this.draftSaving = false
}, 200) }, 400)
}) })
} }
}, },

View File

@ -49,18 +49,28 @@
</a-input> </a-input>
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
v-if="!isUpdateMode"
type="primary" type="primary"
@click="handleCreateOrUpdateTag" @click="handleCreateOrUpdateTag"
v-if="!isUpdateMode" @callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>保存</a-button> :errored="form.errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<a-button-group v-else> <a-button-group v-else>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleCreateOrUpdateTag" @click="handleCreateOrUpdateTag"
@callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>更新</a-button> :errored="form.errored"
text="更新"
loadedText="更新成功"
erroredText="更新失败"
></ReactiveButton>
<a-button <a-button
type="dashed" type="dashed"
@click="form.model = {}" @click="form.model = {}"
@ -137,6 +147,7 @@ export default {
form: { form: {
model: {}, model: {},
saving: false, saving: false,
errored: false,
rules: { rules: {
name: [ name: [
{ required: true, message: '* 标签名称不能为空', trigger: ['change', 'blur'] }, { required: true, message: '* 标签名称不能为空', trigger: ['change', 'blur'] },
@ -180,15 +191,10 @@ export default {
}) })
}, },
handleDeleteTag(tagId) { handleDeleteTag(tagId) {
tagApi tagApi.delete(tagId).finally(() => {
.delete(tagId) this.form.model = {}
.then(response => { this.handleListTags()
this.$message.success('删除成功!') })
this.form.model = {}
})
.finally(() => {
this.handleListTags()
})
}, },
handleCreateOrUpdateTag() { handleCreateOrUpdateTag() {
const _this = this const _this = this
@ -198,33 +204,38 @@ export default {
if (_this.isUpdateMode) { if (_this.isUpdateMode) {
tagApi tagApi
.update(_this.form.model.id, _this.form.model) .update(_this.form.model.id, _this.form.model)
.then(response => { .catch(() => {
_this.$message.success('更新成功!') this.form.errored = true
_this.form.model = {}
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListTags()
}) })
} else { } else {
tagApi tagApi
.create(_this.form.model) .create(_this.form.model)
.then(response => { .catch(() => {
_this.$message.success('保存成功!') this.form.errored = true
_this.form.model = {}
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListTags()
}) })
} }
} }
}) })
}, },
handleSavedCallback() {
const _this = this
if (_this.form.errored) {
_this.form.errored = false
} else {
_this.form.model = {}
_this.handleListTags()
}
},
handleSelectThumbnail(data) { handleSelectThumbnail(data) {
this.$set(this.form.model, 'thumbnail', encodeURI(data.path)) this.$set(this.form.model, 'thumbnail', encodeURI(data.path))
this.thumbnailDrawer.visible = false this.thumbnailDrawer.visible = false

View File

@ -20,13 +20,10 @@
> >
<a-input v-model="selectedPost.title" /> <a-input v-model="selectedPost.title" />
</a-form-item> </a-form-item>
<a-form-item label="文章别名:"> <a-form-item
<template slot="help"> label="文章别名:"
<span v-if="options.post_permalink_type === 'DEFAULT'">{{ options.blog_url }}/{{ options.archives_prefix }}/{{ selectedPost.slug?selectedPost.slug:'${slug}' }}{{ (options.path_suffix?options.path_suffix:'') }}</span> :help="fullPath"
<span v-else-if="options.post_permalink_type === 'DATE'">{{ options.blog_url }}{{ selectedPost.createTime?selectedPost.createTime:new Date() | moment_post_date }}{{ selectedPost.slug?selectedPost.slug:'${slug}' }}{{ (options.path_suffix?options.path_suffix:'') }}</span> >
<span v-else-if="options.post_permalink_type === 'DAY'">{{ options.blog_url }}{{ selectedPost.createTime?selectedPost.createTime:new Date() | moment_post_day }}{{ selectedPost.slug?selectedPost.slug:'${slug}' }}{{ (options.path_suffix?options.path_suffix:'') }}</span>
<span v-else-if="options.post_permalink_type === 'ID'">{{ options.blog_url }}/?p={{ selectedPost.id?selectedPost.id:'${id}' }}</span>
</template>
<a-input v-model="selectedPost.slug" /> <a-input v-model="selectedPost.slug" />
</a-form-item> </a-form-item>
@ -284,17 +281,27 @@
type="dashed" type="dashed"
@click="advancedVisible = true" @click="advancedVisible = true"
>高级</a-button> >高级</a-button>
<a-button <ReactiveButton
type="danger"
v-if="saveDraftButton"
class="mr-2" class="mr-2"
@click="handleDraftClick" @click="handleDraftClick"
v-if="saveDraftButton" @callback="handleSavedCallback"
:loading="draftSaving" :loading="draftSaving"
>保存草稿</a-button> :errored="draftSavedErrored"
<a-button text="保存草稿"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<ReactiveButton
@click="handlePublishClick" @click="handlePublishClick"
type="primary" @callback="handleSavedCallback"
:loading="saving" :loading="saving"
> {{ selectedPost.id?'保存':'发布' }} </a-button> :errored="savedErrored"
:text="`${selectedPost.id?'保存':'发布'}`"
:loadedText="`${selectedPost.id?'保存':'发布'}成功`"
:erroredText="`${selectedPost.id?'保存':'发布'}失败`"
></ReactiveButton>
</div> </div>
</a-drawer> </a-drawer>
</template> </template>
@ -308,6 +315,7 @@ import { mapGetters } from 'vuex'
import categoryApi from '@/api/category' import categoryApi from '@/api/category'
import postApi from '@/api/post' import postApi from '@/api/post'
import themeApi from '@/api/theme' import themeApi from '@/api/theme'
import { datetimeFormat } from '@/utils/util'
export default { export default {
name: 'PostSettingDrawer', name: 'PostSettingDrawer',
mixins: [mixin, mixinDevice], mixins: [mixin, mixinDevice],
@ -328,7 +336,9 @@ export default {
categoryToCreate: {}, categoryToCreate: {},
customTpls: [], customTpls: [],
saving: false, saving: false,
draftSaving: false savedErrored: false,
draftSaving: false,
draftSavedErrored: false
} }
}, },
props: { props: {
@ -388,9 +398,8 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters(['options']),
selectedMetas() { selectedMetas() {
// selectedMetasdata
// ,使metas
return this.metas return this.metas
}, },
pickerDefaultValue() { pickerDefaultValue() {
@ -400,7 +409,32 @@ export default {
} }
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss') return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
}, },
...mapGetters(['options']) 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: { methods: {
handleAfterVisibleChanged(visible) { handleAfterVisibleChanged(visible) {
@ -491,17 +525,12 @@ export default {
// Update the post // Update the post
postApi postApi
.update(this.selectedPost.id, this.selectedPost, false) .update(this.selectedPost.id, this.selectedPost, false)
.then(response => { .catch(() => {
this.$log.debug('Updated post', response.data.data)
if (this.selectedPost.status === 'DRAFT') { if (this.selectedPost.status === 'DRAFT') {
this.$message.success('草稿保存成功!') this.draftSavedErrored = true
} else { } else {
this.$message.success('文章更新成功!') this.savedErrored = true
} }
this.$emit('onSaved', true)
this.$router.push({ name: 'PostList' })
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
@ -513,17 +542,14 @@ export default {
// Create the post // Create the post
postApi postApi
.create(this.selectedPost, false) .create(this.selectedPost, false)
.then(response => { .catch(() => {
this.$log.debug('Created post', response.data.data)
if (this.selectedPost.status === 'DRAFT') { if (this.selectedPost.status === 'DRAFT') {
this.$message.success('草稿保存成功!') this.draftSavedErrored = true
} else { } else {
this.$message.success('文章发布成功!') this.savedErrored = true
} }
})
this.$emit('onSaved', true) .then(response => {
this.$router.push({ name: 'PostList' })
this.selectedPost = response.data.data this.selectedPost = response.data.data
}) })
.finally(() => { .finally(() => {
@ -534,6 +560,15 @@ export default {
}) })
} }
}, },
handleSavedCallback() {
if (this.draftSavedErrored || this.savedErrored) {
this.draftSavedErrored = false
this.savedErrored = false
} else {
this.$emit('onSaved', true)
this.$router.push({ name: 'PostList' })
}
},
onClose() { onClose() {
this.$emit('close', false) this.$emit('close', false)
}, },

View File

@ -38,12 +38,17 @@
<AttachmentDrawer v-model="attachmentDrawerVisible" /> <AttachmentDrawer v-model="attachmentDrawerVisible" />
<footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}"> <footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}">
<a-button <ReactiveButton
type="danger" type="danger"
class="mr-2" class="mr-2"
@click="handleSaveDraft(false)" @click="handleSaveDraft(false)"
@callback="draftSavederrored = false"
:loading="draftSaving" :loading="draftSaving"
>保存草稿</a-button> :errored="draftSavederrored"
text="保存草稿"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<a-button <a-button
@click="handlePreview" @click="handlePreview"
class="mr-2" class="mr-2"
@ -91,6 +96,7 @@ export default {
isSaved: false, isSaved: false,
contentChanges: 0, contentChanges: 0,
draftSaving: false, draftSaving: false,
draftSavederrored: false,
previewSaving: false previewSaving: false
} }
}, },
@ -180,8 +186,8 @@ export default {
if (draftOnly) { if (draftOnly) {
sheetApi sheetApi
.updateDraft(this.sheetToStage.id, this.sheetToStage.originalContent) .updateDraft(this.sheetToStage.id, this.sheetToStage.originalContent)
.then(response => { .catch(() => {
this.$message.success('保存草稿成功!') this.draftSavederrored = true
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
@ -191,9 +197,10 @@ export default {
} else { } else {
sheetApi sheetApi
.update(this.sheetToStage.id, this.sheetToStage, false) .update(this.sheetToStage.id, this.sheetToStage, false)
.catch(() => {
this.draftSavederrored = true
})
.then(response => { .then(response => {
this.$log.debug('Updated sheet', response.data.data)
this.$message.success('保存草稿成功!')
this.sheetToStage = response.data.data this.sheetToStage = response.data.data
}) })
.finally(() => { .finally(() => {
@ -205,15 +212,16 @@ export default {
} else { } else {
sheetApi sheetApi
.create(this.sheetToStage, false) .create(this.sheetToStage, false)
.catch(() => {
this.draftSavederrored = true
})
.then(response => { .then(response => {
this.$log.debug('Created sheet', response.data.data)
this.$message.success('保存草稿成功!')
this.sheetToStage = response.data.data this.sheetToStage = response.data.data
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.draftSaving = false this.draftSaving = false
}, 200) }, 400)
}) })
} }
}, },

View File

@ -22,7 +22,7 @@
</a-form-item> </a-form-item>
<a-form-item <a-form-item
label="页面别名:" label="页面别名:"
:help="options.blog_url+'/'+options.sheet_prefix+'/'+ (selectedSheet.slug ? selectedSheet.slug : '{slug}')+(options.path_suffix?options.path_suffix:'')" :help="fullPath"
> >
<a-input v-model="selectedSheet.slug" /> <a-input v-model="selectedSheet.slug" />
</a-form-item> </a-form-item>
@ -197,17 +197,27 @@
type="dashed" type="dashed"
@click="advancedVisible = true" @click="advancedVisible = true"
>高级</a-button> >高级</a-button>
<a-button <ReactiveButton
type="danger"
v-if="saveDraftButton" v-if="saveDraftButton"
class="mr-2" class="mr-2"
@click="handleDraftClick" @click="handleDraftClick"
@callback="handleSavedCallback"
:loading="draftSaving" :loading="draftSaving"
>保存草稿</a-button> :errored="draftSavedErrored"
<a-button text="保存草稿"
type="primary" loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<ReactiveButton
@click="handlePublishClick" @click="handlePublishClick"
@callback="handleSavedCallback"
:loading="saving" :loading="saving"
>{{ selectedSheet.id?'保存':'发布' }}</a-button> :errored="savedErrored"
:text="`${selectedSheet.id?'保存':'发布'}`"
:loadedText="`${selectedSheet.id?'保存':'发布'}成功`"
:erroredText="`${selectedSheet.id?'保存':'发布'}失败`"
></ReactiveButton>
</div> </div>
</a-drawer> </a-drawer>
</template> </template>
@ -228,7 +238,9 @@ export default {
selectedSheet: this.sheet, selectedSheet: this.sheet,
customTpls: [], customTpls: [],
saving: false, saving: false,
draftSaving: false savedErrored: false,
draftSaving: false,
draftSavedErrored: false
} }
}, },
props: { props: {
@ -268,6 +280,7 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters(['options']),
selectedMetas() { selectedMetas() {
return this.metas return this.metas
}, },
@ -278,7 +291,12 @@ export default {
} }
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss') return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
}, },
...mapGetters(['options']) fullPath() {
const blogUrl = this.options.blog_url
const sheetPrefix = this.options.sheet_prefix
const pathSuffix = this.options.path_suffix ? this.options.path_suffix : ''
return `${blogUrl}/${sheetPrefix}/${this.selectedSheet.slug ? this.selectedSheet.slug : '{slug}'}${pathSuffix}`
}
}, },
methods: { methods: {
handleAfterVisibleChanged(visible) { handleAfterVisibleChanged(visible) {
@ -344,17 +362,12 @@ export default {
if (this.selectedSheet.id) { if (this.selectedSheet.id) {
sheetApi sheetApi
.update(this.selectedSheet.id, this.selectedSheet, false) .update(this.selectedSheet.id, this.selectedSheet, false)
.then(response => { .catch(() => {
this.$log.debug('Updated sheet', response.data.data)
if (this.selectedSheet.status === 'DRAFT') { if (this.selectedSheet.status === 'DRAFT') {
this.$message.success('草稿保存成功!') this.draftSavedErrored = true
} else { } else {
this.$message.success('页面更新成功!') this.savedErrored = true
} }
this.$emit('onSaved', true)
this.$router.push({ name: 'SheetList', query: { activeKey: 'custom' } })
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
@ -365,17 +378,14 @@ export default {
} else { } else {
sheetApi sheetApi
.create(this.selectedSheet, false) .create(this.selectedSheet, false)
.then(response => { .catch(() => {
this.$log.debug('Created sheet', response.data.data)
if (this.selectedSheet.status === 'DRAFT') { if (this.selectedSheet.status === 'DRAFT') {
this.$message.success('草稿保存成功!') this.draftSavedErrored = true
} else { } else {
this.$message.success('页面发布成功!') this.savedErrored = true
} }
})
this.$emit('onSaved', true) .then(response => {
this.$router.push({ name: 'SheetList', query: { activeKey: 'custom' } })
this.selectedSheet = response.data.data this.selectedSheet = response.data.data
}) })
.finally(() => { .finally(() => {
@ -386,6 +396,15 @@ export default {
}) })
} }
}, },
handleSavedCallback() {
if (this.draftSavedErrored || this.savedErrored) {
this.draftSavedErrored = false
this.savedErrored = false
} else {
this.$emit('onSaved', true)
this.$router.push({ name: 'SheetList', query: { activeKey: 'custom' } })
}
},
onClose() { onClose() {
this.$emit('close', false) this.$emit('close', false)
}, },

View File

@ -209,11 +209,16 @@
type="dashed" type="dashed"
@click="attachmentDrawerVisible = true" @click="attachmentDrawerVisible = true"
>附件库</a-button> >附件库</a-button>
<a-button <ReactiveButton
key="submit"
type="primary" type="primary"
@click="createOrUpdateJournal" @click="createOrUpdateJournal"
>发布</a-button> @callback="handleSavedCallback"
:loading="saving"
:errored="errored"
text="发布"
loadedText="发布成功"
erroredText="发布失败"
></ReactiveButton>
</template> </template>
<a-form layout="vertical"> <a-form layout="vertical">
<a-form-item> <a-form-item>
@ -283,7 +288,9 @@ export default {
journal: {}, journal: {},
isPublic: true, isPublic: true,
replyComment: {}, replyComment: {},
options: [] options: [],
saving: false,
errored: false
} }
}, },
created() { created() {
@ -355,29 +362,39 @@ export default {
}) })
return return
} }
this.saving = true
if (this.journal.id) { if (this.journal.id) {
journalApi journalApi
.update(this.journal.id, this.journal) .update(this.journal.id, this.journal)
.then(response => { .catch(() => {
this.$message.success('更新成功!') this.errored = true
this.isPublic = true
}) })
.finally(() => { .finally(() => {
this.hanldeListJournals() setTimeout(() => {
this.saving = false
}, 400)
}) })
} else { } else {
journalApi journalApi
.create(this.journal) .create(this.journal)
.then(response => { .catch(() => {
this.$message.success('发表成功!') this.errored = true
this.isPublic = true
}) })
.finally(() => { .finally(() => {
this.hanldeListJournals() setTimeout(() => {
this.saving = false
}, 400)
}) })
} }
this.visible = false },
handleSavedCallback() {
if (this.errored) {
this.errored = false
} else {
this.isPublic = true
this.visible = false
this.hanldeListJournals()
}
}, },
handlePaginationChange(page, pageSize) { handlePaginationChange(page, pageSize) {
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`) this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)

View File

@ -77,18 +77,28 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
v-if="!isUpdateMode"
type="primary" type="primary"
@click="handleCreateOrUpdateLink" @click="handleCreateOrUpdateLink"
v-if="!isUpdateMode" @callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>保存</a-button> :errored="form.errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
<a-button-group v-else> <a-button-group v-else>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleCreateOrUpdateLink" @click="handleCreateOrUpdateLink"
@callback="handleSavedCallback"
:loading="form.saving" :loading="form.saving"
>更新</a-button> :errored="form.errored"
text="更新"
loadedText="更新成功"
erroredText="更新失败"
></ReactiveButton>
<a-button <a-button
type="dashed" type="dashed"
@click="form.model = {}" @click="form.model = {}"
@ -298,6 +308,7 @@ export default {
form: { form: {
model: {}, model: {},
saving: false, saving: false,
errored: false,
rules: { rules: {
name: [ name: [
{ required: true, message: '* 友情链接名称不能为空', trigger: ['change', 'blur'] }, { required: true, message: '* 友情链接名称不能为空', trigger: ['change', 'blur'] },
@ -385,35 +396,39 @@ export default {
if (_this.isUpdateMode) { if (_this.isUpdateMode) {
linkApi linkApi
.update(_this.form.model.id, _this.form.model) .update(_this.form.model.id, _this.form.model)
.then(response => { .catch(() => {
_this.$message.success('更新成功!') this.form.errored = true
_this.form.model = {}
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListLinks()
_this.handleListLinkTeams()
}) })
} else { } else {
linkApi linkApi
.create(_this.form.model) .create(_this.form.model)
.then(response => { .catch(() => {
_this.$message.success('保存成功!') this.form.errored = true
_this.form.model = {}
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
_this.form.saving = false _this.form.saving = false
}, 400) }, 400)
_this.handleListLinks()
_this.handleListLinkTeams()
}) })
} }
} }
}) })
}, },
handleSavedCallback() {
const _this = this
if (_this.form.errored) {
_this.form.errored = false
} else {
_this.form.model = {}
_this.handleListLinks()
_this.handleListLinkTeams()
}
},
handleSaveOptions() { handleSaveOptions() {
optionApi optionApi
.save(this.optionsModal.data) .save(this.optionsModal.data)

View File

@ -17,6 +17,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="seo"> <a-tab-pane key="seo">
@ -28,6 +30,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="post"> <a-tab-pane key="post">
@ -39,6 +43,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="comment"> <a-tab-pane key="comment">
@ -50,6 +56,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="attachment"> <a-tab-pane key="attachment">
@ -61,6 +69,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="smtp"> <a-tab-pane key="smtp">
@ -72,6 +82,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="other"> <a-tab-pane key="other">
@ -83,6 +95,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
@ -101,6 +115,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="api"> <a-tab-pane key="api">
@ -112,6 +128,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="advanced-other"> <a-tab-pane key="advanced-other">
@ -123,6 +141,8 @@
@onChange="onOptionsChange" @onChange="onOptionsChange"
@onSave="onSaveOptions" @onSave="onSaveOptions"
:saving="saving" :saving="saving"
:errored="errored"
@callback="errored=false"
/> />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
@ -177,7 +197,8 @@ export default {
return { return {
options: {}, options: {},
advancedOptions: false, advancedOptions: false,
saving: false saving: false,
errored: false
} }
}, },
created() { created() {
@ -197,8 +218,8 @@ export default {
this.saving = true this.saving = true
optionApi optionApi
.save(this.options) .save(this.options)
.then(response => { .catch(() => {
this.$message.success('保存成功!') this.errored = true
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {

View File

@ -56,13 +56,18 @@
</a-row> </a-row>
<a-divider class="divider-transparent" /> <a-divider class="divider-transparent" />
<div class="bottom-control"> <div class="bottom-control">
<a-button <ReactiveButton
type="primary" type="primary"
icon="download"
class="mr-2" class="mr-2"
:loading="backuping" icon="download"
@click="handleBackupClick" @click="handleBackupClick"
>备份</a-button> @callback="handleBackupedCallback"
:loading="backuping"
:errored="backupErrored"
text="备份"
loadedText="备份成功"
erroredText="备份失败"
></ReactiveButton>
<a-button <a-button
type="dashed" type="dashed"
icon="reload" icon="reload"
@ -82,6 +87,7 @@ export default {
return { return {
backuping: false, backuping: false,
loading: false, loading: false,
backupErrored: false,
backups: [] backups: []
} }
}, },
@ -119,29 +125,30 @@ export default {
this.backuping = true this.backuping = true
backupApi backupApi
.backupWorkDir() .backupWorkDir()
.then(response => { .catch(() => {
this.$message.success('备份成功!') this.backupErrored = true
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.backuping = false this.backuping = false
}, 400) }, 400)
this.handleListBackups()
}) })
}, },
handleBackupedCallback() {
if (this.backupErrored) {
this.backupErrored = false
} else {
this.handleListBackups()
}
},
handleBackupDeleteClick(backup) { handleBackupDeleteClick(backup) {
backup.deleting = true backup.deleting = true
backupApi backupApi.deleteWorkDirBackup(backup.filename).finally(() => {
.deleteWorkDirBackup(backup.filename) setTimeout(() => {
.then(response => { backup.deleting = false
this.$message.success('删除成功!') }, 400)
}) this.handleListBackups()
.finally(() => { })
setTimeout(() => {
backup.deleting = false
}, 400)
this.handleListBackups()
})
}, },
onClose() { onClose() {
this.$emit('close', false) this.$emit('close', false)

View File

@ -56,13 +56,18 @@
</a-row> </a-row>
<a-divider class="divider-transparent" /> <a-divider class="divider-transparent" />
<div class="bottom-control"> <div class="bottom-control">
<a-button <ReactiveButton
type="primary" type="primary"
icon="download"
class="mr-2" class="mr-2"
:loading="backuping" icon="download"
@click="handleExportClick" @click="handleExportClick"
>备份</a-button> @callback="handleBackupedCallback"
:loading="backuping"
:errored="backupErrored"
text="备份"
loadedText="备份成功"
erroredText="备份失败"
></ReactiveButton>
<a-button <a-button
type="dashed" type="dashed"
icon="reload" icon="reload"
@ -82,6 +87,7 @@ export default {
return { return {
backuping: false, backuping: false,
loading: false, loading: false,
backupErrored: false,
files: [] files: []
} }
}, },
@ -119,29 +125,30 @@ export default {
this.backuping = true this.backuping = true
backupApi backupApi
.exportData() .exportData()
.then(response => { .catch(() => {
this.$message.success('导出成功!') this.backupErrored = true
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.backuping = false this.backuping = false
}, 400) }, 400)
this.handleListBackups()
}) })
}, },
handleBackupedCallback() {
if (this.backupErrored) {
this.backupErrored = false
} else {
this.handleListBackups()
}
},
handleFileDeleteClick(file) { handleFileDeleteClick(file) {
file.deleting = true file.deleting = true
backupApi backupApi.deleteExportedData(file.filename).finally(() => {
.deleteExportedData(file.filename) setTimeout(() => {
.then(response => { file.deleting = false
this.$message.success('删除成功!') }, 400)
}) this.handleListBackups()
.finally(() => { })
setTimeout(() => {
file.deleting = false
}, 400)
this.handleListBackups()
})
}, },
onClose() { onClose() {
this.$emit('close', false) this.$emit('close', false)

View File

@ -7,10 +7,16 @@
<a-switch v-model="options.developer_mode" /> <a-switch v-model="options.developer_mode" />
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
>保存</a-button> @callback="errored=false"
:loading="saving"
:errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
@ -27,28 +33,38 @@ export default {
lg: { span: 8 }, lg: { span: 8 },
sm: { span: 12 }, sm: { span: 12 },
xs: { span: 24 } xs: { span: 24 }
} },
saving: false,
errored: false
} }
}, },
created() { created() {
this.loadFormOptions() this.handleListOptions()
}, },
methods: { methods: {
...mapActions(['refreshOptionsCache']), ...mapActions(['refreshOptionsCache']),
loadFormOptions() { handleListOptions() {
optionApi.listAll().then(response => { optionApi.listAll().then(response => {
this.options = response.data.data this.options = response.data.data
}) })
}, },
handleSaveOptions() { handleSaveOptions() {
optionApi.save(this.options).then(response => { this.saving = true
this.loadFormOptions() optionApi
this.refreshOptionsCache() .save(this.options)
this.$message.success('保存成功!') .catch(() => {
if (!this.options.developer_mode) { this.errored = false
this.$router.push({ name: 'ToolList' }) })
} .finally(() => {
}) setTimeout(() => {
this.saving = false
}, 400)
this.handleListOptions()
this.refreshOptionsCache()
if (!this.options.developer_mode) {
this.$router.push({ name: 'ToolList' })
}
})
} }
} }
} }

View File

@ -14,11 +14,16 @@
<a-switch v-model="options.global_absolute_path_enabled" /> <a-switch v-model="options.global_absolute_path_enabled" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -34,6 +39,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -17,11 +17,16 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -37,6 +42,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -373,11 +373,16 @@
</a-form-model-item> </a-form-model-item>
</div> </div>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -446,6 +451,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -61,11 +61,16 @@
/> />
</a-form-model-item> --> </a-form-model-item> -->
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -81,6 +86,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -62,11 +62,16 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
@ -89,6 +94,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -31,23 +31,17 @@
placeholder="第三方网站统计的代码Google Analytics、百度统计、CNZZ 等" placeholder="第三方网站统计的代码Google Analytics、百度统计、CNZZ 等"
/> />
</a-form-model-item> </a-form-model-item>
<!-- <a-form-model-item
label="黑名单 IP"
>
<a-input
type="textarea"
:autoSize="{ minRows: 5 }"
v-model="options.blog_ip_blacklist"
placeholder="多个 IP 地址换行隔开"
/>
</a-form-model-item> -->
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -63,6 +57,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -9,10 +9,10 @@
> >
<a-form-model-item label="文章固定链接类型:"> <a-form-model-item label="文章固定链接类型:">
<template slot="help"> <template slot="help">
<span v-if="options.post_permalink_type === 'DEFAULT'">{{ options.blog_url }}/{{ options.archives_prefix }}/${slug}{{ options.path_suffix }}</span> <span v-if="options.post_permalink_type === 'DEFAULT'">{{ options.blog_url }}/{{ options.archives_prefix }}/{slug}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'DATE'">{{ options.blog_url }}{{ new Date() | moment_post_date }}${slug}{{ options.path_suffix }}</span> <span v-else-if="options.post_permalink_type === 'DATE'">{{ options.blog_url }}{{ new Date() | moment_post_date }}{slug}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'DAY'">{{ options.blog_url }}{{ new Date() | moment_post_day }}${slug}{{ options.path_suffix }}</span> <span v-else-if="options.post_permalink_type === 'DAY'">{{ options.blog_url }}{{ new Date() | moment_post_day }}{slug}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'ID'">{{ options.blog_url }}/?p=${id}</span> <span v-else-if="options.post_permalink_type === 'ID'">{{ options.blog_url }}/?p={id}</span>
</template> </template>
<a-select v-model="options.post_permalink_type"> <a-select v-model="options.post_permalink_type">
<a-select-option <a-select-option
@ -24,7 +24,7 @@
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="自定义页面前缀:"> <a-form-model-item label="自定义页面前缀:">
<template slot="help"> <template slot="help">
<span>{{ options.blog_url }}/{{ options.sheet_prefix }}/${slug}{{ options.path_suffix }}</span> <span>{{ options.blog_url }}/{{ options.sheet_prefix }}/{slug}{{ options.path_suffix }}</span>
</template> </template>
<a-input v-model="options.sheet_prefix" /> <a-input v-model="options.sheet_prefix" />
</a-form-model-item> </a-form-model-item>
@ -54,28 +54,33 @@
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="分类前缀:"> <a-form-model-item label="分类前缀:">
<template slot="help"> <template slot="help">
<span>{{ options.blog_url }}/{{ options.categories_prefix }}/${slug}{{ options.path_suffix }}</span> <span>{{ options.blog_url }}/{{ options.categories_prefix }}/{slug}{{ options.path_suffix }}</span>
</template> </template>
<a-input v-model="options.categories_prefix" /> <a-input v-model="options.categories_prefix" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="标签前缀:"> <a-form-model-item label="标签前缀:">
<template slot="help"> <template slot="help">
<span>{{ options.blog_url }}/{{ options.tags_prefix }}/${slug}{{ options.path_suffix }}</span> <span>{{ options.blog_url }}/{{ options.tags_prefix }}/{slug}{{ options.path_suffix }}</span>
</template> </template>
<a-input v-model="options.tags_prefix" /> <a-input v-model="options.tags_prefix" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item label="路径后缀:"> <a-form-model-item label="路径后缀:">
<template slot="help"> <template slot="help">
<span>* 格式为<code>.${suffix}</code>仅对内建路径有效</span> <span>* 格式为<code>.{suffix}</code>仅对内建路径有效</span>
</template> </template>
<a-input v-model="options.path_suffix" /> <a-input v-model="options.path_suffix" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -92,6 +97,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -51,11 +51,16 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -71,6 +76,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -33,11 +33,16 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</div> </div>
@ -53,6 +58,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {

View File

@ -38,11 +38,16 @@
<a-input v-model="options.email_from_name" /> <a-input v-model="options.email_from_name" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleSaveOptions" @click="handleSaveOptions"
@callback="$emit('callback')"
:loading="saving" :loading="saving"
>保存</a-button> :errored="errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</a-tab-pane> </a-tab-pane>
@ -68,11 +73,16 @@
/> />
</a-form-model-item> </a-form-model-item>
<a-form-model-item> <a-form-model-item>
<a-button <ReactiveButton
type="primary" type="primary"
@click="handleTestMailClick" @click="handleTestMailClick"
@callback="sendErrored=false"
:loading="sending" :loading="sending"
>发送</a-button> :errored="sendErrored"
text="发送"
loadedText="发送成功"
erroredText="发送失败"
></ReactiveButton>
</a-form-model-item> </a-form-model-item>
</a-form-model> </a-form-model>
</a-tab-pane> </a-tab-pane>
@ -91,6 +101,10 @@ export default {
saving: { saving: {
type: Boolean, type: Boolean,
default: false default: false
},
errored: {
type: Boolean,
default: false
} }
}, },
data() { data() {
@ -103,6 +117,7 @@ export default {
}, },
mailParam: {}, mailParam: {},
sending: false, sending: false,
sendErrored: false,
rules: {} rules: {}
} }
}, },
@ -193,6 +208,9 @@ export default {
.then(response => { .then(response => {
this.$message.info(response.data.message) this.$message.info(response.data.message)
}) })
.catch(() => {
this.sendErrored = true
})
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.sending = false this.sending = false

View File

@ -18,16 +18,16 @@
> >
<a-avatar <a-avatar
:size="104" :size="104"
:src="user.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'" :src="userForm.model.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'"
@click="attachmentDrawerVisible = true" @click="attachmentDrawer.visible = true"
class="cursor-pointer" class="cursor-pointer"
/> />
</a-tooltip> </a-tooltip>
<div <div
class="text-xl leading-5 font-medium mt-4 mb-1" class="text-xl leading-5 font-medium mt-4 mb-1"
style="color: rgba(0, 0, 0, 0.85);" style="color: rgba(0, 0, 0, 0.85);"
>{{ user.nickname }}</div> >{{ userForm.model.nickname }}</div>
<div>{{ user.description }}</div> <div>{{ userForm.model.description }}</div>
</div> </div>
<div> <div>
<p class="mb-3"> <p class="mb-3">
@ -43,27 +43,27 @@
<a-icon <a-icon
type="mail" type="mail"
class="mr-3" class="mr-3"
/>{{ user.email }} />{{ userForm.model.email }}
</p> </p>
<p class="mb-3"> <p class="mb-3">
<a-icon <a-icon
type="calendar" type="calendar"
class="mr-3" class="mr-3"
/>{{ statistics.establishDays || 0 }} />{{ statistics.data.establishDays || 0 }}
</p> </p>
</div> </div>
<a-divider /> <a-divider />
<div> <div>
<a-list <a-list
:loading="statisticsLoading" :loading="statistics.loading"
itemLayout="horizontal" itemLayout="horizontal"
> >
<a-list-item>累计发表了 {{ statistics.postCount || 0 }} 篇文章</a-list-item> <a-list-item>累计发表了 {{ statistics.data.postCount || 0 }} 篇文章</a-list-item>
<a-list-item>累计创建了 {{ statistics.categoryCount || 0 }} 个分类</a-list-item> <a-list-item>累计创建了 {{ statistics.data.categoryCount || 0 }} 个分类</a-list-item>
<a-list-item>累计创建了 {{ statistics.tagCount || 0 }} 个标签</a-list-item> <a-list-item>累计创建了 {{ statistics.data.tagCount || 0 }} 个标签</a-list-item>
<a-list-item>累计获得了 {{ statistics.commentCount || 0 }} 条评论</a-list-item> <a-list-item>累计获得了 {{ statistics.data.commentCount || 0 }} 条评论</a-list-item>
<a-list-item>累计添加了 {{ statistics.linkCount || 0 }} 个友链</a-list-item> <a-list-item>累计添加了 {{ statistics.data.linkCount || 0 }} 个友链</a-list-item>
<a-list-item>文章总阅读 {{ statistics.visitCount || 0 }} </a-list-item> <a-list-item>文章总阅读 {{ statistics.data.visitCount || 0 }} </a-list-item>
<a-list-item></a-list-item> <a-list-item></a-list-item>
</a-list> </a-list>
</div> </div>
@ -85,138 +85,182 @@
<span slot="tab"> <span slot="tab">
<a-icon type="idcard" />基本资料 <a-icon type="idcard" />基本资料
</span> </span>
<a-form layout="vertical"> <a-form-model
<a-form-item label="用户名:"> ref="userForm"
<a-input v-model="user.username" /> :model="userForm.model"
</a-form-item> :rules="userForm.rules"
<a-form-item label="昵称:"> layout="vertical"
<a-input v-model="user.nickname" /> >
</a-form-item> <a-form-model-item
<a-form-item label="邮箱:"> label="用户名:"
<a-input v-model="user.email" /> prop="username"
</a-form-item> >
<a-form-item label="个人说明:"> <a-input v-model="userForm.model.username" />
</a-form-model-item>
<a-form-model-item
label="昵称:"
prop="nickname"
>
<a-input v-model="userForm.model.nickname" />
</a-form-model-item>
<a-form-model-item
label="电子邮箱:"
prop="email"
>
<a-input v-model="userForm.model.email" />
</a-form-model-item>
<a-form-model-item
label="个人说明:"
prop="description"
>
<a-input <a-input
:autoSize="{ minRows: 5 }" :autoSize="{ minRows: 5 }"
type="textarea" type="textarea"
v-model="user.description" v-model="userForm.model.description"
/> />
</a-form-item> </a-form-model-item>
<a-form-item> <a-form-model-item>
<a-button <ReactiveButton
@click="handleUpdateProfile"
type="primary" type="primary"
>保存</a-button> @click="handleUpdateProfile"
</a-form-item> @callback="handleUpdatedProfileCallback"
</a-form> :loading="userForm.saving"
:errored="userForm.errored"
text="保存"
loadedText="保存成功"
erroredText="保存失败"
></ReactiveButton>
</a-form-model-item>
</a-form-model>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2"> <a-tab-pane key="2">
<span slot="tab"> <span slot="tab">
<a-icon type="lock" />密码 <a-icon type="lock" />密码
</span> </span>
<a-form layout="vertical"> <a-form-model
<a-form-item label="原密码:"> ref="passwordForm"
:model="passwordForm.model"
:rules="passwordForm.rules"
layout="vertical"
>
<a-form-model-item
label="原密码:"
prop="oldPassword"
>
<a-input-password <a-input-password
v-model="passwordParam.oldPassword" v-model="passwordForm.model.oldPassword"
autocomplete="new-password" autocomplete="new-password"
/> />
</a-form-item> </a-form-model-item>
<a-form-item label="新密码:"> <a-form-model-item
label="新密码:"
prop="newPassword"
>
<a-input-password <a-input-password
v-model="passwordParam.newPassword" v-model="passwordForm.model.newPassword"
autocomplete="new-password" autocomplete="new-password"
/> />
</a-form-item> </a-form-model-item>
<a-form-item label="确认密码:"> <a-form-model-item
label="确认密码:"
prop="confirmPassword"
>
<a-input-password <a-input-password
v-model="passwordParam.confirmPassword" v-model="passwordForm.model.confirmPassword"
autocomplete="new-password" autocomplete="new-password"
/> />
</a-form-item> </a-form-model-item>
<a-form-item> <a-form-model-item>
<a-button <ReactiveButton
:disabled="passwordUpdateButtonDisabled"
@click="handleUpdatePassword"
type="primary" type="primary"
>确认更改</a-button> @click="handleUpdatePassword"
</a-form-item> @callback="handleUpdatedPasswordCallback"
</a-form> :loading="passwordForm.saving"
:errored="passwordForm.errored"
text="确认更改"
loadedText="更改成功"
erroredText="更改失败"
></ReactiveButton>
</a-form-model-item>
</a-form-model>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="3"> <a-tab-pane key="3">
<span slot="tab"> <span slot="tab">
<a-icon type="safety-certificate" />两步验证 <a-icon type="safety-certificate" />两步验证
</span> </span>
<a-form-item label="两步验证:"> <a-form-model layout="vertical">
<a-switch <a-form-model-item label="两步验证:">
v-model="mfaParam.switch.checked" <a-switch
:loading="mfaParam.switch.loading" v-model="mfaParam.switch.checked"
@change="handleMFASwitch" :loading="mfaParam.switch.loading"
/> @change="handleMFASwitch"
</a-form-item> />
<a-form-item label="两步验证应用:"> </a-form-model-item>
<a-list itemLayout="horizontal"> <a-form-model-item label="两步验证应用:">
<a-list-item> <a-list itemLayout="horizontal">
<b>Authy</b> 功能丰富 专为两步验证码 <a-list-item>
<a-divider type="vertical" /> <b>Authy</b> 功能丰富 专为两步验证码
<a <a-divider type="vertical" />
target="_blank" <a
href="https://authy.com/download/" target="_blank"
> href="https://authy.com/download/"
iOS/Android/Windows/Mac/Linux >
<a-icon type="link" /> iOS/Android/Windows/Mac/Linux
</a> <a-icon type="link" />
<a-divider type="vertical" /> </a>
<a <a-divider type="vertical" />
target="_blank" <a
href="https://chrome.google.com/webstore/detail/authy/gaedmjdfmmahhbjefcbgaolhhanlaolb?hl=cn" target="_blank"
> href="https://chrome.google.com/webstore/detail/authy/gaedmjdfmmahhbjefcbgaolhhanlaolb?hl=cn"
Chrome 扩展 >
<a-icon type="link" /> Chrome 扩展
</a> <a-icon type="link" />
</a-list-item> </a>
<a-list-item> </a-list-item>
<b>Google Authenticator</b> 简单易用但不支持密钥导出备份 <a-list-item>
<a-divider type="vertical" /> <b>Google Authenticator</b> 简单易用但不支持密钥导出备份
<a <a-divider type="vertical" />
target="_blank" <a
href="https://apps.apple.com/us/app/google-authenticator/id388497605" target="_blank"
> href="https://apps.apple.com/us/app/google-authenticator/id388497605"
iOS >
<a-icon type="link" /> iOS
</a> <a-icon type="link" />
<a-divider type="vertical" /> </a>
<a <a-divider type="vertical" />
target="_blank" <a
href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=cn" target="_blank"
> href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=cn"
Android >
<a-icon type="link" /> Android
</a> <a-icon type="link" />
</a-list-item> </a>
<a-list-item> </a-list-item>
<b>Microsoft Authenticator</b> 使用微软全家桶的推荐 <a-list-item>
<a-divider type="vertical" /> <b>Microsoft Authenticator</b> 使用微软全家桶的推荐
<a <a-divider type="vertical" />
target="_blank" <a
href="https://www.microsoft.com/zh-cn/account/authenticator" target="_blank"
> href="https://www.microsoft.com/zh-cn/account/authenticator"
iOS/Android >
<a-icon type="link" /> iOS/Android
</a> <a-icon type="link" />
</a-list-item> </a>
<a-list-item> </a-list-item>
<b>1Password</b> 强大安全的密码管理付费应用 <a-list-item>
<a-divider type="vertical" /> <b>1Password</b> 强大安全的密码管理付费应用
<a <a-divider type="vertical" />
target="_blank" <a
href="https://1password.com/zh-cn/downloads/" target="_blank"
> href="https://1password.com/zh-cn/downloads/"
iOS/Android/Windows/Mac/Linux/ChromeOS >
<a-icon type="link" /> iOS/Android/Windows/Mac/Linux/ChromeOS
</a> <a-icon type="link" />
</a-list-item> </a>
</a-list> </a-list-item>
</a-form-item> </a-list>
</a-form-model-item>
</a-form-model>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
@ -225,7 +269,7 @@
</a-row> </a-row>
<AttachmentSelectDrawer <AttachmentSelectDrawer
v-model="attachmentDrawerVisible" v-model="attachmentDrawer.visible"
@listenToSelect="handleSelectAvatar" @listenToSelect="handleSelectAvatar"
@listenToSelectGravatar="handleSelectGravatar" @listenToSelectGravatar="handleSelectGravatar"
title="选择头像" title="选择头像"
@ -235,9 +279,7 @@
<a-modal <a-modal
:title="mfaParam.modal.title" :title="mfaParam.modal.title"
:visible="mfaParam.modal.visible" :visible="mfaParam.modal.visible"
@ok="handleSetMFAuth"
:confirmLoading="false" :confirmLoading="false"
@cancel="handleCloseMFAuthModal"
:closable="false" :closable="false"
icon="safety-certificate" icon="safety-certificate"
:keyboard="false" :keyboard="false"
@ -245,10 +287,37 @@
:destroyOnClose="true" :destroyOnClose="true"
:width="400" :width="400"
> >
<a-form v-if="mfaUsed"> <template slot="footer">
<a-form-item extra="* 需要验证两步验证码"> <a-button
key="back"
@click="handleCloseMFAuthModal"
>
取消
</a-button>
<ReactiveButton
key="submit"
type="primary"
@click="handleSetMFAuth"
@callback="handleSetMFAuthCallback"
:loading="mfaParam.saving"
:errored="mfaParam.errored"
text="确定"
loadedText="设置成功"
erroredText="设置失败"
></ReactiveButton>
</template>
<a-form-model
ref="mfaForm"
:model="mfaParam"
:rules="mfaParam.rules"
layout="vertical"
>
<a-form-model-item
v-if="mfaUsed"
label="两步验证码"
prop="authcode"
>
<a-input <a-input
placeholder="两步验证码"
v-model="mfaParam.authcode" v-model="mfaParam.authcode"
:maxLength="6" :maxLength="6"
> >
@ -258,11 +327,9 @@
style="color: rgba(0,0,0,.25)" style="color: rgba(0,0,0,.25)"
/> />
</a-input> </a-input>
</a-form-item> </a-form-model-item>
</a-form> <a-form-model-item
v-if="!mfaUsed"
<a-form v-else>
<a-form-item
label="1. 请扫描二维码或导入 key" label="1. 请扫描二维码或导入 key"
:help="`MFAKey:${mfaParam.mfaKey}`" :help="`MFAKey:${mfaParam.mfaKey}`"
> >
@ -273,10 +340,13 @@
width="100%" width="100%"
:src="mfaParam.qrImage" :src="mfaParam.qrImage"
/> />
</a-form-item> </a-form-model-item>
<a-form-item label="2. 验证两步验证码"> <a-form-model-item
v-if="!mfaUsed"
label="2. 验证两步验证码"
prop="authcode"
>
<a-input <a-input
placeholder="两步验证码"
v-model="mfaParam.authcode" v-model="mfaParam.authcode"
:maxLength="6" :maxLength="6"
> >
@ -286,8 +356,8 @@
style="color: rgba(0,0,0,.25)" style="color: rgba(0,0,0,.25)"
/> />
</a-input> </a-input>
</a-form-item> </a-form-model-item>
</a-form> </a-form-model>
</a-modal> </a-modal>
</div> </div>
</template> </template>
@ -300,15 +370,64 @@ import MD5 from 'md5.js'
export default { export default {
data() { data() {
const validateConfirmPassword = (rule, value, callback) => {
if (value && this.passwordForm.model.newPassword !== value) {
callback(new Error('确认密码与新密码不一致'))
} else {
callback()
}
}
return { return {
statisticsLoading: false, attachmentDrawer: {
attachmentDrawerVisible: false, visible: false
user: {}, },
statistics: {}, userForm: {
passwordParam: { model: {},
oldPassword: null, saving: false,
newPassword: null, errored: false,
confirmPassword: null rules: {
username: [
{ required: true, message: '* 用户名不能为空', trigger: ['change', 'blur'] },
{ max: 50, message: '* 用户名的字符长度不能超过 50', trigger: ['change', 'blur'] }
],
nickname: [
{ required: true, message: '* 用户昵称不能为空', trigger: ['change', 'blur'] },
{ max: 255, message: '* 用户昵称的字符长度不能超过 255', trigger: ['change', 'blur'] }
],
email: [
{ required: true, message: '* 电子邮箱地址不能为空', trigger: ['change', 'blur'] },
{ type: 'email', message: '* 电子邮箱地址格式不正确', trigger: ['change', 'blur'] },
{ max: 127, message: '* 电子邮箱的字符长度不能超过 255', trigger: ['change', 'blur'] }
],
description: [{ max: 1023, message: '* 个人说明的字符长度不能超过 1023', trigger: ['change', 'blur'] }]
}
},
statistics: {
data: {},
loading: false
},
passwordForm: {
model: {
oldPassword: null,
newPassword: null,
confirmPassword: null
},
saving: false,
errored: false,
rules: {
oldPassword: [
{ required: true, message: '* 原密码不能为空', trigger: ['change', 'blur'] },
{ max: 100, min: 8, message: '* 密码的字符长度必须在 8 - 100 之间', trigger: ['blur'] }
],
newPassword: [
{ required: true, message: '* 新密码不能为空', trigger: ['change', 'blur'] },
{ max: 100, min: 8, message: '* 密码的字符长度必须在 8 - 100 之间', trigger: ['change', 'blur'] }
],
confirmPassword: [
{ required: true, message: '* 确认密码不能为空', trigger: ['change', 'blur'] },
{ validator: validateConfirmPassword, trigger: ['change', 'blur'] }
]
}
}, },
mfaParam: { mfaParam: {
mfaKey: null, mfaKey: null,
@ -323,15 +442,16 @@ export default {
switch: { switch: {
loading: false, loading: false,
checked: false checked: false
} },
}, rules: {
attachment: {} authcode: [{ required: true, message: '* 两步验证码不能为空', trigger: ['change', 'blur'] }]
},
saving: false,
errored: false
}
} }
}, },
computed: { computed: {
passwordUpdateButtonDisabled() {
return !(this.passwordParam.oldPassword && this.passwordParam.newPassword)
},
...mapGetters(['options']), ...mapGetters(['options']),
mfaType() { mfaType() {
return this.mfaParam.mfaType return this.mfaParam.mfaType
@ -356,68 +476,82 @@ export default {
methods: { methods: {
...mapMutations({ setUser: 'SET_USER' }), ...mapMutations({ setUser: 'SET_USER' }),
handleLoadStatistics() { handleLoadStatistics() {
this.statisticsLoading = true this.statistics.loading = true
statisticsApi statisticsApi
.statisticsWithUser() .statisticsWithUser()
.then(response => { .then(response => {
this.user = response.data.data.user this.userForm.model = response.data.data.user
this.statistics = response.data.data this.statistics.data = response.data.data
this.mfaParam.mfaType = this.user.mfaType && this.user.mfaType this.mfaParam.mfaType = this.userForm.model.mfaType && this.userForm.model.mfaType
}) })
.finally(() => { .finally(() => {
setTimeout(() => { setTimeout(() => {
this.statisticsLoading = false this.statistics.loading = false
}, 200) }, 200)
}) })
}, },
handleUpdatePassword() { handleUpdatePassword() {
// Check confirm password const _this = this
if (this.passwordParam.newPassword !== this.passwordParam.confirmPassword) { _this.$refs.passwordForm.validate(valid => {
this.$message.error('确认密码和新密码不匹配!') if (valid) {
return this.passwordForm.saving = true
} userApi
userApi.updatePassword(this.passwordParam.oldPassword, this.passwordParam.newPassword).then(response => { .updatePassword(this.passwordForm.model.oldPassword, this.passwordForm.model.newPassword)
this.$message.success('密码修改成功!') .catch(() => {
this.passwordParam.oldPassword = null this.passwordForm.errored = true
this.passwordParam.newPassword = null })
this.passwordParam.confirmPassword = null .finally(() => {
setTimeout(() => {
this.passwordForm.saving = false
}, 400)
})
}
}) })
}, },
handleUpdatedPasswordCallback() {
if (this.passwordForm.errored) {
this.passwordForm.errored = false
} else {
this.passwordForm.model.oldPassword = null
this.passwordForm.model.newPassword = null
this.passwordForm.model.confirmPassword = null
}
},
handleUpdateProfile() { handleUpdateProfile() {
if (!this.user.username) { const _this = this
this.$notification['error']({ _this.$refs.userForm.validate(valid => {
message: '提示', if (valid) {
description: '用户名不能为空!' this.userForm.saving = true
}) userApi
return .updateProfile(this.userForm.model)
} .then(response => {
if (!this.user.nickname) { this.userForm.model = response.data.data
this.$notification['error']({ this.setUser(Object.assign({}, this.userForm.model))
message: '提示', })
description: '用户昵称不能为空!' .catch(() => {
}) this.userForm.errored = true
return })
} .finally(() => {
if (!this.user.email) { setTimeout(() => {
this.$notification['error']({ this.userForm.saving = false
message: '提示', }, 400)
description: '邮箱不能为空!' })
}) }
return
}
userApi.updateProfile(this.user).then(response => {
this.user = response.data.data
this.setUser(Object.assign({}, this.user))
this.$message.success('资料更新成功!')
}) })
}, },
handleUpdatedProfileCallback() {
if (this.userForm.errored) {
this.userForm.errored = false
}
},
handleSelectAvatar(data) { handleSelectAvatar(data) {
this.user.avatar = encodeURI(data.path) this.userForm.model.avatar = encodeURI(data.path)
this.attachmentDrawerVisible = false this.attachmentDrawer.visible = false
}, },
handleSelectGravatar() { handleSelectGravatar() {
this.user.avatar = '//cn.gravatar.com/avatar/' + new MD5().update(this.user.email).digest('hex') + '&d=mm' this.userForm.model.avatar =
this.attachmentDrawerVisible = false '//cn.gravatar.com/avatar/' + new MD5().update(this.userForm.model.email).digest('hex') + '&d=mm'
this.attachmentDrawer.visible = false
}, },
handleMFASwitch(useMFAuth) { handleMFASwitch(useMFAuth) {
// loding // loding
@ -440,19 +574,34 @@ export default {
} }
}, },
handleSetMFAuth() { handleSetMFAuth() {
var mfaType = this.mfaUsed ? 'NONE' : 'TFA_TOTP' const _this = this
if (mfaType === 'NONE') { var mfaType = _this.mfaUsed ? 'NONE' : 'TFA_TOTP'
if (!this.mfaParam.authcode) { _this.$refs.mfaForm.validate(valid => {
this.$message.warn('两步验证码不能为空!') if (valid) {
return _this.mfaParam.saving = true
userApi
.mfaUpdate(mfaType, _this.mfaParam.mfaKey, _this.mfaParam.authcode)
.catch(() => {
_this.mfaParam.errored = true
})
.finally(() => {
setTimeout(() => {
_this.mfaParam.saving = false
}, 400)
})
} }
}
userApi.mfaUpdate(mfaType, this.mfaParam.mfaKey, this.mfaParam.authcode).then(response => {
this.handleCloseMFAuthModal()
this.mfaParam.mfaType = response.data.data.mfaType
this.$message.success(this.mfaUsed ? '两步验证已关闭!' : '两步验证已开启,下次登陆生效!')
}) })
}, },
handleSetMFAuthCallback() {
const _this = this
if (_this.mfaParam.errored) {
_this.mfaParam.errored = false
} else {
_this.handleCloseMFAuthModal()
_this.handleLoadStatistics()
_this.$message.success(_this.mfaUsed ? '两步验证已关闭!' : '两步验证已开启,下次登陆生效!')
}
},
handleCloseMFAuthModal() { handleCloseMFAuthModal() {
this.mfaParam.modal.visible = false this.mfaParam.modal.visible = false
this.mfaParam.switch.loading = false this.mfaParam.switch.loading = false