mirror of https://github.com/halo-dev/halo
release 1.1.0 (halo-dev/console#40)
release 1.1.0 Co-authored-by: guqing <1484563614@qq.com>pull/3445/head
commit
3ed70b20c5
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "halo-admin",
|
||||
"version": "1.0.3",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
@ -10,21 +10,23 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"animate.css": "^3.7.0",
|
||||
"ant-design-vue": "~1.3.9",
|
||||
"ant-design-vue": "^1.3.16",
|
||||
"axios": "^0.18.0",
|
||||
"enquire.js": "^2.1.6",
|
||||
"marked": "^0.6.2",
|
||||
"mavon-editor": "^2.7.4",
|
||||
"filepond": "^4.6.1",
|
||||
"filepond-plugin-image-preview": "^4.4.0",
|
||||
"halo-editor": "^2.7.6",
|
||||
"marked": "^0.6.3",
|
||||
"moment": "^2.24.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"viser-vue": "^2.4.6",
|
||||
"verte": "^0.0.12",
|
||||
"vue": "^2.6.10",
|
||||
"vue-clipboard2": "^0.3.0",
|
||||
"vue-codemirror-lite": "^1.0.4",
|
||||
"vue-count-to": "^1.0.13",
|
||||
"vue-cropper": "0.4.9",
|
||||
"vue-filepond": "^5.1.3",
|
||||
"vue-ls": "^3.2.1",
|
||||
"vue-router": "^3.0.6",
|
||||
"vue-router": "^3.1.2",
|
||||
"vue-video-player": "^5.0.2",
|
||||
"vuejs-logger": "^1.5.3",
|
||||
"vuex": "^3.1.1"
|
||||
|
@ -38,14 +40,14 @@
|
|||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.20",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^24.8.0",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-plugin-import": "^1.11.2",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-html": "^5.0.5",
|
||||
"eslint-plugin-vue": "^5.2.2",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"generate-asset-webpack-plugin": "^0.3.0",
|
||||
"less": "^3.9.0",
|
||||
"less": "^3.10.0",
|
||||
"less-loader": "^5.0.0",
|
||||
"vue-svg-icon-loader": "^2.1.1",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-cmn-Hans">
|
||||
<head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
|
@ -8,16 +9,20 @@
|
|||
<meta name="generator" content="Halo" />
|
||||
<link rel="icon" href="<%= BASE_URL %>logo.png" />
|
||||
<title>Halo Dashboard</title>
|
||||
</head>
|
||||
<style>
|
||||
#loader{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;border:solid 3px #e5e5e5;border-top-color:#333;border-radius:50%;width:30px;height:30px;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong
|
||||
>
|
||||
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<div id="app">
|
||||
<div id="loader"></div>
|
||||
</div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -12,6 +12,13 @@ adminApi.counts = () => {
|
|||
})
|
||||
}
|
||||
|
||||
adminApi.isInstalled = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/is_installed`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.environments = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/environments`,
|
||||
|
@ -52,6 +59,22 @@ adminApi.refreshToken = refreshToken => {
|
|||
})
|
||||
}
|
||||
|
||||
adminApi.sendResetCode = param => {
|
||||
return service({
|
||||
url: `${baseUrl}/password/code`,
|
||||
data: param,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.resetPassword = param => {
|
||||
return service({
|
||||
url: `${baseUrl}/password/reset`,
|
||||
data: param,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
adminApi.updateAdminAssets = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/halo-admin`,
|
||||
|
|
|
@ -6,7 +6,7 @@ const backupApi = {}
|
|||
|
||||
backupApi.importMarkdown = (formData, uploadProgress, cancelToken) => {
|
||||
return service({
|
||||
url: `${baseUrl}/import/markdowns`,
|
||||
url: `${baseUrl}/import/markdown`,
|
||||
timeout: 8640000, // 24 hours
|
||||
data: formData, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
|
|
|
@ -45,6 +45,14 @@ commentApi.create = (target, comment) => {
|
|||
})
|
||||
}
|
||||
|
||||
commentApi.update = (target, commentId, comment) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${target}/comments/${commentId}`,
|
||||
data: comment,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comment.
|
||||
* @param {String} target
|
||||
|
|
|
@ -42,4 +42,13 @@ journalApi.commentTree = journalId => {
|
|||
})
|
||||
}
|
||||
|
||||
journalApi.journalType = {
|
||||
PUBLIC: {
|
||||
text: '公开'
|
||||
},
|
||||
INTIMATE: {
|
||||
text: '私密'
|
||||
}
|
||||
}
|
||||
|
||||
export default journalApi
|
||||
|
|
|
@ -35,4 +35,11 @@ photoApi.delete = photoId => {
|
|||
})
|
||||
}
|
||||
|
||||
photoApi.listTeams = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/teams`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export default photoApi
|
||||
|
|
|
@ -66,6 +66,13 @@ postApi.delete = postId => {
|
|||
})
|
||||
}
|
||||
|
||||
postApi.preview = postId => {
|
||||
return service({
|
||||
url: `${baseUrl}/preview/${postId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
postApi.postStatus = {
|
||||
PUBLISHED: {
|
||||
color: 'green',
|
||||
|
@ -81,6 +88,11 @@ postApi.postStatus = {
|
|||
color: 'red',
|
||||
status: 'error',
|
||||
text: '回收站'
|
||||
},
|
||||
INTIMATE: {
|
||||
color: 'blue',
|
||||
status: 'success',
|
||||
text: '私密'
|
||||
}
|
||||
}
|
||||
export default postApi
|
||||
|
|
|
@ -61,6 +61,13 @@ sheetApi.delete = sheetId => {
|
|||
})
|
||||
}
|
||||
|
||||
sheetApi.preview = sheetId => {
|
||||
return service({
|
||||
url: `${baseUrl}/preview/${sheetId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
sheetApi.sheetStatus = {
|
||||
PUBLISHED: {
|
||||
color: 'green',
|
||||
|
|
|
@ -11,9 +11,16 @@ themeApi.listAll = () => {
|
|||
})
|
||||
}
|
||||
|
||||
themeApi.listFiles = () => {
|
||||
themeApi.listFilesActivated = () => {
|
||||
return service({
|
||||
url: `${baseUrl}/files`,
|
||||
url: `${baseUrl}/activation/files`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.listFiles = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/files`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
@ -41,7 +48,7 @@ themeApi.getActivatedTheme = () => {
|
|||
|
||||
themeApi.update = themeId => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}`,
|
||||
url: `${baseUrl}/fetching/${themeId}`,
|
||||
timeout: 60000,
|
||||
method: 'put'
|
||||
})
|
||||
|
@ -94,6 +101,17 @@ themeApi.upload = (formData, uploadProgress, cancelToken) => {
|
|||
})
|
||||
}
|
||||
|
||||
themeApi.updateByUpload = (formData, uploadProgress, cancelToken, themeId) => {
|
||||
return service({
|
||||
url: `${baseUrl}/upload/${themeId}`,
|
||||
timeout: 86400000, // 24 hours
|
||||
data: formData, // form data
|
||||
onUploadProgress: uploadProgress,
|
||||
cancelToken: cancelToken,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.fetching = url => {
|
||||
return service({
|
||||
url: `${baseUrl}/fetching`,
|
||||
|
@ -115,13 +133,34 @@ themeApi.getContent = path => {
|
|||
})
|
||||
}
|
||||
|
||||
themeApi.saveContent = (path, content) => {
|
||||
themeApi.getContent = (themeId, path) => {
|
||||
return service({
|
||||
url: `${baseUrl}/files/content`,
|
||||
url: `${baseUrl}/${themeId}/files/content`,
|
||||
params: {
|
||||
path: path
|
||||
},
|
||||
data: content,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.saveContent = (path, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/files/content`,
|
||||
data: {
|
||||
path: path,
|
||||
content: content
|
||||
},
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
themeApi.saveContent = (themeId, path, content) => {
|
||||
return service({
|
||||
url: `${baseUrl}/${themeId}/files/content`,
|
||||
data: {
|
||||
path: path,
|
||||
content: content
|
||||
},
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<template>
|
||||
<div class="footer">
|
||||
<div class="copyright">
|
||||
<div
|
||||
class="footer"
|
||||
style="padding: 0 16px;margin: 48px 0 0;text-align: center;"
|
||||
>
|
||||
<div
|
||||
class="copyright"
|
||||
style="color: rgba(0, 0, 0, 0.45);font-size: 14px;"
|
||||
>
|
||||
Proudly power by
|
||||
<router-link :to="{ name:'About' }">
|
||||
<a href="javascript:void(0);">Halo</a>
|
||||
|
@ -19,13 +25,4 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.footer {
|
||||
padding: 0 16px;
|
||||
margin: 48px 0 0;
|
||||
text-align: center;
|
||||
.copyright {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -105,7 +105,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
document.body.addEventListener('scroll', this.handleScroll, { passive: true })
|
||||
document.addEventListener('scroll', this.handleScroll, { passive: true })
|
||||
},
|
||||
methods: {
|
||||
handleScroll() {
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
closable
|
||||
@close="onClose"
|
||||
:visible="visible"
|
||||
:zIndex="9999"
|
||||
>
|
||||
<div class="setting-drawer-index-content">
|
||||
<div :style="{ marginBottom: '24px' }">
|
||||
|
@ -114,6 +113,77 @@
|
|||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div :style="{ marginTop: '24px' }">
|
||||
<a-list :split="false">
|
||||
<a-list-item>
|
||||
<a-tooltip slot="actions">
|
||||
<template slot="title">
|
||||
该设定仅 [顶部栏导航] 时有效
|
||||
</template>
|
||||
<a-select
|
||||
size="small"
|
||||
style="width: 80px;"
|
||||
:defaultValue="contentWidth"
|
||||
@change="handleContentWidthChange"
|
||||
>
|
||||
<a-select-option value="Fixed">固定</a-select-option>
|
||||
<a-select-option
|
||||
value="Fluid"
|
||||
v-if="layoutMode != 'sidemenu'"
|
||||
>流式</a-select-option>
|
||||
</a-select>
|
||||
</a-tooltip>
|
||||
<a-list-item-meta>
|
||||
<div slot="title">内容区域宽度</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch
|
||||
slot="actions"
|
||||
size="small"
|
||||
:defaultChecked="fixedHeader"
|
||||
@change="handleFixedHeader"
|
||||
/>
|
||||
<a-list-item-meta>
|
||||
<div slot="title">固定 Header</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch
|
||||
slot="actions"
|
||||
size="small"
|
||||
:disabled="!fixedHeader"
|
||||
:defaultChecked="autoHideHeader"
|
||||
@change="handleFixedHeaderHidden"
|
||||
/>
|
||||
<a-list-item-meta>
|
||||
<a-tooltip
|
||||
slot="title"
|
||||
placement="left"
|
||||
>
|
||||
<template slot="title">固定 Header 时可配置</template>
|
||||
<div :style="{ opacity: !fixedHeader ? '0.5' : '1' }">下滑时隐藏 Header</div>
|
||||
</a-tooltip>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-switch
|
||||
slot="actions"
|
||||
size="small"
|
||||
:disabled="(layoutMode === 'topmenu')"
|
||||
:defaultChecked="fixSiderbar"
|
||||
@change="handleFixSiderbar"
|
||||
/>
|
||||
<a-list-item-meta>
|
||||
<div
|
||||
slot="title"
|
||||
:style="{ opacity: (layoutMode==='topmenu') ? '0.5' : '1' }"
|
||||
>固定侧边菜单</div>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</div>
|
||||
<a-divider />
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
|
@ -122,7 +192,7 @@
|
|||
<script>
|
||||
import SettingItem from '@/components/SettingDrawer/SettingItem'
|
||||
import config from '@/config/defaultSettings'
|
||||
import { updateTheme, colorList } from '@/components/Tools/setting'
|
||||
import { updateTheme, colorList } from './setting'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin'
|
||||
|
||||
export default {
|
||||
|
@ -161,9 +231,10 @@ export default {
|
|||
handleLayout(mode) {
|
||||
this.baseConfig.layout = mode
|
||||
this.$store.dispatch('ToggleLayoutMode', mode)
|
||||
// 因为顶部菜单不能固定左侧菜单栏,所以强制关闭
|
||||
//
|
||||
this.handleFixSiderbar(false)
|
||||
if (mode === 'sidemenu') {
|
||||
this.handleContentWidthChange('Fixed')
|
||||
}
|
||||
},
|
||||
handleContentWidthChange(type) {
|
||||
this.baseConfig.contentWidth = type
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
import { message } from 'ant-design-vue/es'
|
||||
// import defaultSettings from '../defaultSettings';
|
||||
|
||||
let lessNodesAppended
|
||||
|
||||
const colorList = [
|
||||
{
|
||||
key: '薄暮', color: '#F5222D'
|
||||
},
|
||||
{
|
||||
key: '火山', color: '#FA541C'
|
||||
},
|
||||
{
|
||||
key: '日暮', color: '#FAAD14'
|
||||
},
|
||||
{
|
||||
key: '明青', color: '#13C2C2'
|
||||
},
|
||||
{
|
||||
key: '极光绿', color: '#52C41A'
|
||||
},
|
||||
{
|
||||
key: '拂晓蓝(默认)', color: '#1890FF'
|
||||
},
|
||||
{
|
||||
key: '极客蓝', color: '#2F54EB'
|
||||
},
|
||||
{
|
||||
key: '酱紫', color: '#722ED1'
|
||||
}
|
||||
]
|
||||
|
||||
const updateTheme = primaryColor => {
|
||||
// Don't compile less in production!
|
||||
/* if (process.env.NODE_ENV === 'production') {
|
||||
return;
|
||||
} */
|
||||
// Determine if the component is remounted
|
||||
if (!primaryColor) {
|
||||
return
|
||||
}
|
||||
const hideMessage = message.loading('正在编译主题!', 0)
|
||||
function buildIt() {
|
||||
if (!window.less) {
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.less
|
||||
.modifyVars({
|
||||
'@primary-color': primaryColor
|
||||
})
|
||||
.then(() => {
|
||||
hideMessage()
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('Failed to update theme')
|
||||
hideMessage()
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
if (!lessNodesAppended) {
|
||||
// insert less.js and color.less
|
||||
const lessStyleNode = document.createElement('link')
|
||||
const lessConfigNode = document.createElement('script')
|
||||
const lessScriptNode = document.createElement('script')
|
||||
lessStyleNode.setAttribute('rel', 'stylesheet/less')
|
||||
lessStyleNode.setAttribute('href', '/color.less')
|
||||
lessConfigNode.innerHTML = `
|
||||
window.less = {
|
||||
async: true,
|
||||
env: 'production',
|
||||
javascriptEnabled: true
|
||||
};
|
||||
`
|
||||
lessScriptNode.src = 'https://cdnjs.loli.net/ajax/libs/less.js/3.8.1/less.min.js'
|
||||
lessScriptNode.async = true
|
||||
lessScriptNode.onload = () => {
|
||||
buildIt()
|
||||
lessScriptNode.onload = null
|
||||
}
|
||||
document.body.appendChild(lessStyleNode)
|
||||
document.body.appendChild(lessConfigNode)
|
||||
document.body.appendChild(lessScriptNode)
|
||||
lessNodesAppended = true
|
||||
} else {
|
||||
buildIt()
|
||||
}
|
||||
}
|
||||
|
||||
export { updateTheme, colorList }
|
|
@ -30,6 +30,7 @@
|
|||
<a-avatar
|
||||
class="avatar"
|
||||
size="small"
|
||||
style="margin-right: 0.3rem;"
|
||||
:src="user.avatar || '//cn.gravatar.com/avatar/?s=256&d=mm'"
|
||||
/>
|
||||
</span>
|
||||
|
@ -63,7 +64,6 @@
|
|||
import HeaderComment from './HeaderComment'
|
||||
import SettingDrawer from '@/components/SettingDrawer/SettingDrawer'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import optionApi from '@/api/option'
|
||||
|
||||
export default {
|
||||
name: 'UserMenu',
|
||||
|
@ -73,19 +73,14 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
optionVisible: true,
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
optionVisible: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.optionVisible = this.$refs.drawer.visible
|
||||
},
|
||||
created() {
|
||||
this.loadOptions()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user'])
|
||||
...mapGetters(['user', 'options'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['logout']),
|
||||
|
@ -114,18 +109,7 @@ export default {
|
|||
showOptionModal() {
|
||||
this.optionVisible = this.$refs.drawer.visible
|
||||
this.$refs.drawer.toggle()
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.avatar {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div>
|
||||
<file-pond
|
||||
ref="pond"
|
||||
:label-idle="label"
|
||||
:name="name"
|
||||
:allow-multiple="multiple"
|
||||
:allowRevert="false"
|
||||
:accepted-file-types="accept"
|
||||
:maxParallelUploads="options.attachment_upload_max_parallel_uploads"
|
||||
:allowImagePreview="options.attachment_upload_image_preview_enable"
|
||||
:maxFiles="options.attachment_upload_max_files"
|
||||
labelFileProcessing="上传中"
|
||||
labelFileProcessingComplete="上传完成"
|
||||
labelFileProcessingAborted="取消上传"
|
||||
labelFileProcessingError="上传错误"
|
||||
labelTapToCancel="点击取消"
|
||||
labelTapToRetry="点击重试"
|
||||
:files="fileList"
|
||||
:server="server"
|
||||
@init="handleFilePondInit"
|
||||
>
|
||||
</file-pond>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
import vueFilePond from 'vue-filepond'
|
||||
import 'filepond/dist/filepond.min.css'
|
||||
|
||||
// Plugins
|
||||
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
|
||||
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css'
|
||||
|
||||
// Create component and regist plugins
|
||||
const FilePond = vueFilePond(FilePondPluginImagePreview)
|
||||
export default {
|
||||
name: 'FilePondUpload',
|
||||
components: {
|
||||
FilePond
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'file'
|
||||
},
|
||||
filed: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '点击选择文件或将文件拖拽到此处'
|
||||
},
|
||||
uploadHandler: {
|
||||
type: Function,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
server: {
|
||||
process: (fieldName, file, metadata, load, error, progress, abort) => {
|
||||
const formData = new FormData()
|
||||
formData.append(fieldName, file, file.name)
|
||||
|
||||
const CancelToken = axios.CancelToken
|
||||
const source = CancelToken.source()
|
||||
|
||||
this.uploadHandler(
|
||||
formData,
|
||||
progressEvent => {
|
||||
if (progressEvent.total > 0) {
|
||||
progress(progressEvent.lengthComputable, progressEvent.loaded, progressEvent.total)
|
||||
}
|
||||
},
|
||||
source.token,
|
||||
this.filed,
|
||||
file
|
||||
)
|
||||
.then(response => {
|
||||
load(response)
|
||||
this.$log.debug('Uploaded successfully', response)
|
||||
this.$emit('success', response, file)
|
||||
})
|
||||
.catch(failure => {
|
||||
this.$log.debug('Failed to upload file', failure)
|
||||
this.$emit('failure', failure, file)
|
||||
error()
|
||||
})
|
||||
return {
|
||||
abort: () => {
|
||||
abort()
|
||||
this.$log.debug('Upload operation aborted by the user')
|
||||
source.cancel('Upload operation canceled by the user.')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
handleFilePondInit() {
|
||||
console.log('FilePond has initialized')
|
||||
},
|
||||
handleClearFileList() {
|
||||
this.$refs.pond.removeFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -122,7 +122,6 @@ export default {
|
|||
}
|
||||
</script>
|
||||
<style>
|
||||
/* you can make up upload button and sample style by using stylesheets */
|
||||
.ant-upload-select-picture-card i {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
@import './index.less';
|
||||
|
||||
*{
|
||||
* {
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #1890ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
|
@ -539,18 +541,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.ant-card-wider-padding {
|
||||
.ant-card-body {
|
||||
padding: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-tab-wrapper{
|
||||
.ant-card-body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form {
|
||||
.ant-form-item {
|
||||
padding-bottom: 0 !important;
|
||||
|
@ -694,22 +684,108 @@ body {
|
|||
|
||||
margin-bottom: 0;
|
||||
|
||||
p{
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.post-thum {
|
||||
.post-thumb,
|
||||
.sheet-thumb {
|
||||
.img {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.post-thum-remove {
|
||||
}
|
||||
|
||||
.post-thumb-remove,
|
||||
.sheet-thumb-remove {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.ant-calendar-picker {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#editor {
|
||||
.v-note-wrapper {
|
||||
min-height: 580px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-calendar-picker{
|
||||
width: 100%!important;
|
||||
.attach-item {
|
||||
width: 50%;
|
||||
padding-bottom: 28%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.attach-thumb {
|
||||
width: 100%;
|
||||
padding-bottom: 56%;
|
||||
}
|
||||
|
||||
.attach-item,
|
||||
.attach-thumb {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
img,
|
||||
span {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9b9ea0;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-card-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.meta {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
.analysis-card-action {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.number {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 32px;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree-child-tree {
|
||||
li {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
|
@ -4,11 +4,13 @@ import Vue from 'vue'
|
|||
import Ellipsis from '@/components/Ellipsis'
|
||||
import FooterToolbar from '@/components/FooterToolbar'
|
||||
import Upload from '@/components/Upload/Upload'
|
||||
import FilePondUpload from '@/components/Upload/FilePondUpload'
|
||||
|
||||
const _components = {
|
||||
Ellipsis,
|
||||
FooterToolbar,
|
||||
Upload
|
||||
Upload,
|
||||
FilePondUpload
|
||||
}
|
||||
|
||||
const components = {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// eslint-disable-next-line
|
||||
import { BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
|
||||
import { BasicLayout, PageView } from '@/layouts'
|
||||
|
||||
export const asyncRouterMap = [
|
||||
{
|
||||
|
@ -218,6 +218,12 @@ export const constantRouterMap = [
|
|||
meta: { title: '安装向导' },
|
||||
component: () => import('@/views/system/Installation')
|
||||
},
|
||||
{
|
||||
path: '/password/reset',
|
||||
name: 'ResetPassword',
|
||||
meta: { title: '重置密码' },
|
||||
component: () => import('@/views/user/ResetPassword')
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
name: 'NotFound',
|
||||
|
|
|
@ -11,7 +11,8 @@ import {
|
|||
DEFAULT_FIXED_SIDEMENU,
|
||||
DEFAULT_CONTENT_WIDTH_TYPE,
|
||||
USER,
|
||||
API_URL
|
||||
API_URL,
|
||||
OPTIONS
|
||||
} from '@/store/mutation-types'
|
||||
import config from '@/config/defaultSettings'
|
||||
|
||||
|
@ -27,6 +28,6 @@ export default function Initializer() {
|
|||
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
|
||||
store.commit('SET_USER', Vue.ls.get(USER))
|
||||
store.commit('SET_API_URL', Vue.ls.get(API_URL))
|
||||
|
||||
store.commit('SET_OPTIONS', Vue.ls.get(OPTIONS))
|
||||
// last step
|
||||
}
|
||||
|
|
|
@ -4,20 +4,12 @@ import config from '@/config/defaultSettings'
|
|||
|
||||
// base library
|
||||
import '@/core/lazy_lib/components_use'
|
||||
import Viser from 'viser-vue'
|
||||
import VueCropper from 'vue-cropper'
|
||||
import 'ant-design-vue/dist/antd.less'
|
||||
import bootstrap from './bootstrap'
|
||||
|
||||
// ext library
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
|
||||
VueClipboard.config.autoSetContainer = true
|
||||
|
||||
Vue.use(Viser)
|
||||
|
||||
Vue.use(VueStorage, config.storageOptions)
|
||||
Vue.use(VueClipboard)
|
||||
Vue.use(VueCropper)
|
||||
|
||||
bootstrap()
|
||||
|
|
|
@ -4,18 +4,11 @@ import config from '@/config/defaultSettings'
|
|||
|
||||
// base library
|
||||
import Antd from 'ant-design-vue'
|
||||
import Viser from 'viser-vue'
|
||||
import VueCropper from 'vue-cropper'
|
||||
import 'ant-design-vue/dist/antd.less'
|
||||
|
||||
// ext library
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
|
||||
VueClipboard.config.autoSetContainer = true
|
||||
|
||||
Vue.use(Antd)
|
||||
Vue.use(Viser)
|
||||
|
||||
Vue.use(VueStorage, config.storageOptions)
|
||||
Vue.use(VueClipboard)
|
||||
Vue.use(VueCropper)
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import Vue from 'vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
|
||||
import {
|
||||
setDocumentTitle,
|
||||
domTitle
|
||||
} from '@/utils/domUtil'
|
||||
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css' // progress bar style
|
||||
|
||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
NProgress.configure({
|
||||
showSpinner: false
|
||||
}) // NProgress Configuration
|
||||
|
||||
const whiteList = ['Login', 'Install', 'NotFound'] // no redirect whitelist
|
||||
const whiteList = ['Login', 'Install', 'NotFound', 'ResetPassword'] // no redirect whitelist
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start()
|
||||
|
@ -16,12 +21,18 @@ router.beforeEach((to, from, next) => {
|
|||
Vue.$log.debug('Token', store.getters.token)
|
||||
if (store.getters.token) {
|
||||
if (to.name === 'Login') {
|
||||
next({ name: 'Dashboard' })
|
||||
next({
|
||||
name: 'Dashboard'
|
||||
})
|
||||
NProgress.done()
|
||||
return
|
||||
}
|
||||
// TODO Get installation status
|
||||
|
||||
if (!store.getters.options) {
|
||||
store.dispatch('loadOptions').then()
|
||||
}
|
||||
|
||||
next()
|
||||
NProgress.done()
|
||||
return
|
||||
|
@ -35,6 +46,11 @@ router.beforeEach((to, from, next) => {
|
|||
return
|
||||
}
|
||||
|
||||
next({ name: 'Login', query: { redirect: to.fullPath } })
|
||||
next({
|
||||
name: 'Login',
|
||||
query: {
|
||||
redirect: to.fullPath
|
||||
}
|
||||
})
|
||||
NProgress.done()
|
||||
})
|
||||
|
|
|
@ -4,16 +4,14 @@ const getters = {
|
|||
color: state => state.app.color,
|
||||
token: state => state.user.token,
|
||||
user: state => state.user.user,
|
||||
avatar: state => state.user.avatar,
|
||||
nickname: state => state.user.name,
|
||||
roles: state => state.user.roles,
|
||||
addRouters: state => state.permission.addRouters,
|
||||
apiUrl: state => {
|
||||
if (state.app.apiUrl) {
|
||||
return state.app.apiUrl
|
||||
}
|
||||
return `${window.location.protocol}//${window.location.host}`
|
||||
}
|
||||
},
|
||||
options: state => state.option.options
|
||||
}
|
||||
|
||||
export default getters
|
||||
|
|
|
@ -4,6 +4,7 @@ import Vuex from 'vuex'
|
|||
import app from './modules/app'
|
||||
import user from './modules/user'
|
||||
import permission from './modules/permission'
|
||||
import option from './modules/option'
|
||||
import getters from './getters'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
@ -12,7 +13,8 @@ export default new Vuex.Store({
|
|||
modules: {
|
||||
app,
|
||||
user,
|
||||
permission
|
||||
permission,
|
||||
option
|
||||
},
|
||||
state: {
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import Vue from 'vue'
|
||||
import {
|
||||
OPTIONS
|
||||
} from '@/store/mutation-types'
|
||||
import optionApi from '@/api/option'
|
||||
const keys = [
|
||||
'blog_url',
|
||||
'attachment_upload_image_preview_enable',
|
||||
'attachment_upload_max_parallel_uploads',
|
||||
'attachment_upload_max_files'
|
||||
]
|
||||
const option = {
|
||||
state: {
|
||||
options: []
|
||||
},
|
||||
mutations: {
|
||||
SET_OPTIONS: (state, options) => {
|
||||
Vue.ls.set(OPTIONS, options)
|
||||
state.options = options
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
loadOptions({
|
||||
commit
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
optionApi
|
||||
.listAll(keys)
|
||||
.then(response => {
|
||||
commit('SET_OPTIONS', response.data.data)
|
||||
resolve(response)
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default option
|
|
@ -21,22 +21,6 @@ function hasPermission(permission, route) {
|
|||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 单账户多角色时,使用该方法可过滤角色不存在的菜单
|
||||
*
|
||||
* @param roles
|
||||
* @param route
|
||||
* @returns {*}
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
function hasRole(roles, route) {
|
||||
if (route.meta && route.meta.roles) {
|
||||
return route.meta.roles.includes(roles.id)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function filterAsyncRouter(routerMap, roles) {
|
||||
const accessedRouters = routerMap.filter(route => {
|
||||
if (hasPermission(roles.permissionList, route)) {
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN, USER } from '@/store/mutation-types'
|
||||
import {
|
||||
ACCESS_TOKEN,
|
||||
USER
|
||||
} from '@/store/mutation-types'
|
||||
import adminApi from '@/api/admin'
|
||||
import userApi from '@/api/user'
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: null,
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
info: {},
|
||||
user: {}
|
||||
},
|
||||
mutations: {
|
||||
|
@ -17,18 +16,6 @@ const user = {
|
|||
Vue.ls.set(ACCESS_TOKEN, token)
|
||||
state.token = token
|
||||
},
|
||||
SET_NAME: (state, { name }) => {
|
||||
state.name = name
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
},
|
||||
SET_INFO: (state, info) => {
|
||||
state.info = info
|
||||
},
|
||||
CLEAR_TOKEN: state => {
|
||||
Vue.ls.remove(ACCESS_TOKEN)
|
||||
state.token = null
|
||||
|
@ -39,7 +26,9 @@ const user = {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
loadUser({ commit }) {
|
||||
loadUser({
|
||||
commit
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
userApi
|
||||
.getProfile()
|
||||
|
@ -52,7 +41,12 @@ const user = {
|
|||
})
|
||||
})
|
||||
},
|
||||
login({ commit }, { username, password }) {
|
||||
login({
|
||||
commit
|
||||
}, {
|
||||
username,
|
||||
password
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
adminApi
|
||||
.login(username, password)
|
||||
|
@ -68,7 +62,9 @@ const user = {
|
|||
})
|
||||
})
|
||||
},
|
||||
logout({ commit }) {
|
||||
logout({
|
||||
commit
|
||||
}) {
|
||||
return new Promise(resolve => {
|
||||
commit('CLEAR_TOKEN')
|
||||
adminApi
|
||||
|
@ -81,7 +77,9 @@ const user = {
|
|||
})
|
||||
})
|
||||
},
|
||||
refreshToken({ commit }, refreshToken) {
|
||||
refreshToken({
|
||||
commit
|
||||
}, refreshToken) {
|
||||
return new Promise((resolve, reject) => {
|
||||
adminApi
|
||||
.refreshToken(refreshToken)
|
||||
|
|
|
@ -9,6 +9,7 @@ export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
|
|||
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
|
||||
export const USER = 'USER'
|
||||
export const API_URL = 'API_URL'
|
||||
export const OPTIONS = 'OPTIONS'
|
||||
|
||||
export const CONTENT_WIDTH_TYPE = {
|
||||
Fluid: 'Fluid',
|
||||
|
|
|
@ -8,7 +8,7 @@ import router from '@/router'
|
|||
import { isObject } from './util'
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 5000,
|
||||
timeout: 8000,
|
||||
withCredentials: true
|
||||
})
|
||||
|
||||
|
@ -147,7 +147,7 @@ service.interceptors.response.use(
|
|||
message.error(data.message)
|
||||
}
|
||||
} else {
|
||||
message.error('服务异常')
|
||||
message.error('网络异常')
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
404 page
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: '404'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -1,9 +0,0 @@
|
|||
<template>
|
||||
<div> </div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -8,8 +8,12 @@
|
|||
<a-col
|
||||
:span="24"
|
||||
class="search-box"
|
||||
style="padding-bottom: 12px;"
|
||||
>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<a-card :bordered="false">
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="48">
|
||||
|
@ -73,7 +77,7 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="table-operator">
|
||||
<div class="table-operator" style="margin-bottom: 0;">
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
|
@ -84,7 +88,7 @@
|
|||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-list
|
||||
:grid="{ gutter: 12, xs: 1, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
|
||||
:grid="{ gutter: 12, xs: 2, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
|
||||
:dataSource="formattedDatas"
|
||||
:loading="listLoading"
|
||||
>
|
||||
|
@ -100,11 +104,14 @@
|
|||
>
|
||||
<div class="attach-thumb">
|
||||
<span v-show="!handleJudgeMediaType(item)">当前格式不支持预览</span>
|
||||
<img :src="item.thumbPath" v-show="handleJudgeMediaType(item)">
|
||||
<img
|
||||
:src="item.thumbPath"
|
||||
v-show="handleJudgeMediaType(item)"
|
||||
>
|
||||
</div>
|
||||
<a-card-meta>
|
||||
<a-card-meta style="padding: 0.8rem;">
|
||||
<ellipsis
|
||||
:length="isMobile()?36:16"
|
||||
:length="isMobile()?12:16"
|
||||
tooltip
|
||||
slot="description"
|
||||
>{{ item.name }}</ellipsis>
|
||||
|
@ -130,21 +137,15 @@
|
|||
v-model="uploadVisible"
|
||||
:footer="null"
|
||||
:afterClose="onUploadClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<upload
|
||||
name="file"
|
||||
multiple
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
:uploadHandler="uploadHandler"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<a-icon type="inbox" />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击选择文件或将文件拖拽到此处</p>
|
||||
<p class="ant-upload-hint">支持单个或批量上传</p>
|
||||
</upload>
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
<AttachmentDetailDrawer
|
||||
v-model="drawerVisiable"
|
||||
v-model="drawerVisible"
|
||||
v-if="selectAttachment"
|
||||
:attachment="selectAttachment"
|
||||
:addToPhoto="true"
|
||||
|
@ -154,8 +155,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { PageView } from '@/layouts'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { PageView } from '@/layouts'
|
||||
import AttachmentDetailDrawer from './components/AttachmentDetailDrawer'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
|
||||
|
@ -187,8 +188,8 @@ export default {
|
|||
mediaType: null,
|
||||
attachmentType: null
|
||||
},
|
||||
uploadHandler: attachmentApi.upload,
|
||||
drawerVisiable: false
|
||||
drawerVisible: false,
|
||||
uploadHandler: attachmentApi.upload
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -203,6 +204,17 @@ export default {
|
|||
this.loadAttachments()
|
||||
this.loadMediaTypes()
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.drawerVisible) {
|
||||
this.drawerVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.drawerVisible) {
|
||||
this.drawerVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
methods: {
|
||||
loadAttachments() {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
|
@ -222,7 +234,7 @@ export default {
|
|||
},
|
||||
handleShowDetailDrawer(attachment) {
|
||||
this.selectAttachment = attachment
|
||||
this.drawerVisiable = true
|
||||
this.drawerVisible = true
|
||||
},
|
||||
handlePaginationChange(page, size) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${size}`)
|
||||
|
@ -235,12 +247,15 @@ export default {
|
|||
this.queryParam.mediaType = null
|
||||
this.queryParam.attachmentType = null
|
||||
this.loadAttachments()
|
||||
this.loadMediaTypes()
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParam.page = 0
|
||||
this.pagination.page = 1
|
||||
this.loadAttachments()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.loadAttachments()
|
||||
this.loadMediaTypes()
|
||||
},
|
||||
|
@ -264,47 +279,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-divider-horizontal {
|
||||
margin: 24px 0 12px 0;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.attach-thumb {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding-bottom: 56%;
|
||||
overflow: hidden;
|
||||
img, span{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
span {
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9b9ea0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-card-meta {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
|
||||
.attach-detail-img img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-operator {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
title="附件详情"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="visiable"
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
>
|
||||
|
@ -19,13 +19,21 @@
|
|||
>
|
||||
<div class="attach-detail-img">
|
||||
<div v-show="nonsupportPreviewVisible">此文件不支持预览</div>
|
||||
<img :src="attachment.path" v-show="photoPreviewVisible">
|
||||
<a :href="attachment.path" target="_blank">
|
||||
<img
|
||||
:src="attachment.path"
|
||||
v-show="photoPreviewVisible"
|
||||
style="width: 100%;"
|
||||
>
|
||||
</a>
|
||||
<video-player
|
||||
class="video-player-box"
|
||||
v-show="videoPreviewVisible"
|
||||
ref="videoPlayer"
|
||||
:options="playerOptions"
|
||||
:playsinline="true">
|
||||
:playsinline="true"
|
||||
style="width: 100%;"
|
||||
>
|
||||
</video-player>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
|
@ -154,10 +162,10 @@
|
|||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { videoPlayer } from 'vue-video-player'
|
||||
import 'video.js/dist/video-js.css'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import photoApi from '@/api/photo'
|
||||
import 'video.js/dist/video-js.css'
|
||||
import { videoPlayer } from 'vue-video-player'
|
||||
|
||||
export default {
|
||||
name: 'AttachmentDetailDrawer',
|
||||
|
@ -182,10 +190,12 @@ export default {
|
|||
controls: true,
|
||||
loop: false,
|
||||
playbackRates: [0.7, 1.0, 1.5, 2.0],
|
||||
sources: [{
|
||||
sources: [
|
||||
{
|
||||
type: 'video/mp4',
|
||||
src: 'https://cdn.theguardian.tv/webM/2015/07/20/150716YesMen_synd_768k_vp8.webm'
|
||||
}],
|
||||
}
|
||||
],
|
||||
poster: '/static/images/author.jpg',
|
||||
width: document.documentElement.clientWidth,
|
||||
notSupportedMessage: '此视频暂无法播放,请稍后再试'
|
||||
|
@ -193,7 +203,7 @@ export default {
|
|||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'visiable',
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
|
@ -206,7 +216,7 @@ export default {
|
|||
required: false,
|
||||
default: false
|
||||
},
|
||||
visiable: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
|
@ -221,7 +231,7 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
visiable: function(newValue, oldValue) {
|
||||
visible: function(newValue, oldValue) {
|
||||
this.$log.debug('old value', oldValue)
|
||||
this.$log.debug('new value', newValue)
|
||||
if (newValue) {
|
||||
|
@ -253,6 +263,13 @@ export default {
|
|||
this.editable = !this.editable
|
||||
},
|
||||
doUpdateAttachment() {
|
||||
if (!this.attachment.name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '附件名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
attachmentApi.update(this.attachment.id, this.attachment).then(response => {
|
||||
this.$log.debug('Updated attachment', response.data.data)
|
||||
this.$message.success('附件修改成功!')
|
||||
|
@ -339,12 +356,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scope>
|
||||
.attach-detail-img img {
|
||||
width: 100%;
|
||||
}
|
||||
.video-player-box {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
title="附件库"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="visiable"
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
>
|
||||
|
@ -36,7 +36,11 @@
|
|||
:key="index"
|
||||
@click="handleShowDetailDrawer(item)"
|
||||
>
|
||||
<img :src="item.thumbPath">
|
||||
<span v-show="!handleJudgeMediaType(item)">当前格式不支持预览</span>
|
||||
<img
|
||||
:src="item.thumbPath"
|
||||
v-show="handleJudgeMediaType(item)"
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-skeleton>
|
||||
|
@ -51,12 +55,12 @@
|
|||
</div>
|
||||
|
||||
<AttachmentDetailDrawer
|
||||
v-model="detailVisiable"
|
||||
v-model="detailVisible"
|
||||
v-if="selectedAttachment"
|
||||
:attachment="selectedAttachment"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
<a-divider class="divider-transparent"/>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
@click="handleShowUploadModal"
|
||||
|
@ -70,26 +74,20 @@
|
|||
v-model="uploadVisible"
|
||||
:footer="null"
|
||||
:afterClose="onUploadClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<upload
|
||||
name="file"
|
||||
multiple
|
||||
:uploadHandler="attachmentUploadHandler"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<a-icon type="inbox" />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击选择文件或将文件拖拽到此处</p>
|
||||
<p class="ant-upload-hint">支持单个或批量上传</p>
|
||||
</upload>
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
:uploadHandler="uploadHandler"
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import AttachmentDetailDrawer from './AttachmentDetailDrawer'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
|
||||
export default {
|
||||
name: 'AttachmentDrawer',
|
||||
|
@ -98,11 +96,11 @@ export default {
|
|||
AttachmentDetailDrawer
|
||||
},
|
||||
model: {
|
||||
prop: 'visiable',
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
visiable: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
|
@ -111,7 +109,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
attachmentType: attachmentApi.type,
|
||||
detailVisiable: false,
|
||||
detailVisible: false,
|
||||
attachmentDrawerVisible: false,
|
||||
uploadVisible: false,
|
||||
skeletonLoading: true,
|
||||
|
@ -128,7 +126,7 @@ export default {
|
|||
},
|
||||
attachments: [],
|
||||
selectedAttachment: {},
|
||||
attachmentUploadHandler: attachmentApi.upload
|
||||
uploadHandler: attachmentApi.upload
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -144,7 +142,7 @@ export default {
|
|||
this.loadAttachments()
|
||||
},
|
||||
watch: {
|
||||
visiable: function(newValue, oldValue) {
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
}
|
||||
|
@ -163,7 +161,7 @@ export default {
|
|||
handleShowDetailDrawer(attachment) {
|
||||
this.selectedAttachment = attachment
|
||||
this.$log.debug('Show detail of', attachment)
|
||||
this.detailVisiable = true
|
||||
this.detailVisible = true
|
||||
},
|
||||
loadAttachments(isSearch) {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
|
@ -183,34 +181,33 @@ export default {
|
|||
this.loadAttachments()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
},
|
||||
handleDelete() {
|
||||
this.loadAttachments()
|
||||
},
|
||||
handleJudgeMediaType(attachment) {
|
||||
var mediaType = attachment.mediaType
|
||||
// 判断文件类型
|
||||
if (mediaType) {
|
||||
var prefix = mediaType.split('/')[0]
|
||||
|
||||
if (prefix === 'image') {
|
||||
// 是图片
|
||||
return true
|
||||
} else {
|
||||
// 非图片
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 没有获取到文件返回false
|
||||
return false
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scope>
|
||||
.attach-item {
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding-bottom: 28%;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
:title="title"
|
||||
:width="isMobile()?'100%':drawerWidth"
|
||||
closable
|
||||
:visible="visiable"
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
>
|
||||
|
@ -34,7 +34,11 @@
|
|||
:key="index"
|
||||
@click="handleSelectAttachment(item)"
|
||||
>
|
||||
<img :src="item.thumbPath">
|
||||
<span v-show="!handleJudgeMediaType(item)">当前格式不支持预览</span>
|
||||
<img
|
||||
:src="item.thumbPath"
|
||||
v-show="handleJudgeMediaType(item)"
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-skeleton>
|
||||
|
@ -67,18 +71,12 @@
|
|||
v-model="uploadVisible"
|
||||
:footer="null"
|
||||
:afterClose="onUploadClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<upload
|
||||
name="file"
|
||||
multiple
|
||||
:uploadHandler="attachmentUploadHandler"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<a-icon type="inbox" />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击选择文件或将文件拖拽到此处</p>
|
||||
<p class="ant-upload-hint">支持单个或批量上传</p>
|
||||
</upload>
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
:uploadHandler="uploadHandler"
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -91,11 +89,11 @@ export default {
|
|||
name: 'AttachmentSelectDrawer',
|
||||
mixins: [mixin, mixinDevice],
|
||||
model: {
|
||||
prop: 'visiable',
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
visiable: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
|
@ -126,7 +124,7 @@ export default {
|
|||
sort: ''
|
||||
},
|
||||
attachments: [],
|
||||
attachmentUploadHandler: attachmentApi.upload
|
||||
uploadHandler: attachmentApi.upload
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -134,7 +132,7 @@ export default {
|
|||
this.loadAttachments()
|
||||
},
|
||||
watch: {
|
||||
visiable: function(newValue, oldValue) {
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
}
|
||||
|
@ -174,31 +172,30 @@ export default {
|
|||
this.loadAttachments()
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
this.loadSkeleton()
|
||||
this.loadAttachments()
|
||||
},
|
||||
handleJudgeMediaType(attachment) {
|
||||
var mediaType = attachment.mediaType
|
||||
// 判断文件类型
|
||||
if (mediaType) {
|
||||
var prefix = mediaType.split('/')[0]
|
||||
|
||||
if (prefix === 'image') {
|
||||
// 是图片
|
||||
return true
|
||||
} else {
|
||||
// 非图片
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 没有获取到文件返回false
|
||||
return false
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scope>
|
||||
.attach-item {
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
padding-bottom: 28%;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -26,10 +26,6 @@ export default {
|
|||
components: {
|
||||
PageView,
|
||||
CommentTab
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="评论详情"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
>
|
||||
<a-row
|
||||
type="flex"
|
||||
align="middle"
|
||||
>
|
||||
<a-col :span="24">
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="detailLoading"
|
||||
:paragraph="{rows: 8}"
|
||||
>
|
||||
<a-list itemLayout="horizontal">
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="comment.author">
|
||||
<span slot="title">评论者昵称:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="comment.email">
|
||||
<span slot="title">评论者邮箱:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta :description="comment.ipAddress">
|
||||
<span slot="title">评论者 IP:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<a
|
||||
slot="description"
|
||||
target="_blank"
|
||||
:href="comment.authorUrl"
|
||||
>{{ comment.authorUrl }}</a>
|
||||
<span slot="title">评论者网址:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<span slot="description">
|
||||
<a-badge :status="comment.statusProperty.status" :text="comment.statusProperty.text"/>
|
||||
</span>
|
||||
<span slot="title">评论状态:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<a
|
||||
slot="description"
|
||||
target="_blank"
|
||||
:href="options.blog_url+'/archives/'+comment.post.url"
|
||||
v-if="this.type=='posts'"
|
||||
>{{ comment.post.title }}</a>
|
||||
<a
|
||||
slot="description"
|
||||
target="_blank"
|
||||
:href="options.blog_url+'/s/'+comment.sheet.url"
|
||||
v-else-if="this.type=='sheets'"
|
||||
>{{ comment.sheet.title }}</a>
|
||||
<span
|
||||
slot="title"
|
||||
v-if="this.type=='posts'"
|
||||
>评论文章:</span>
|
||||
<span
|
||||
slot="title"
|
||||
v-else-if="this.type=='sheets'"
|
||||
>评论页面:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template
|
||||
slot="description"
|
||||
v-if="editable"
|
||||
>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="comment.content"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
slot="description"
|
||||
v-html="comment.content"
|
||||
v-else
|
||||
></span>
|
||||
<span slot="title">评论内容:</span>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="marginRight: 8px"
|
||||
@click="handleEditComment"
|
||||
v-if="!editable"
|
||||
>编辑</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="marginRight: 8px"
|
||||
@click="handleUpdateComment"
|
||||
v-if="editable"
|
||||
>保存</a-button>
|
||||
<a-popconfirm
|
||||
title="你确定要将此评论者加入黑名单?"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-button type="danger">加入黑名单</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import commentApi from '@/api/comment'
|
||||
export default {
|
||||
name: 'CommentDetail',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
detailLoading: true,
|
||||
editable: false,
|
||||
commentStatus: commentApi.commentStatus,
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
comment: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'posts',
|
||||
validator: function(value) {
|
||||
return ['posts', 'sheets', 'journals'].indexOf(value) !== -1
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
watch: {
|
||||
visible: function(newValue, oldValue) {
|
||||
this.$log.debug('old value', oldValue)
|
||||
this.$log.debug('new value', newValue)
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadSkeleton() {
|
||||
this.detailLoading = true
|
||||
setTimeout(() => {
|
||||
this.detailLoading = false
|
||||
}, 500)
|
||||
},
|
||||
handleEditComment() {
|
||||
this.editable = true
|
||||
},
|
||||
handleUpdateComment() {
|
||||
commentApi.update(this.type, this.comment.id, this.comment).then(response => {
|
||||
this.$log.debug('Updated comment', response.data.data)
|
||||
this.$message.success('评论修改成功!')
|
||||
})
|
||||
this.editable = false
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<div class="comment-tab-wrapper">
|
||||
<a-card :bordered="false">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="48">
|
||||
|
@ -105,6 +108,22 @@
|
|||
:loading="loading"
|
||||
:pagination="false"
|
||||
>
|
||||
<template
|
||||
slot="author"
|
||||
slot-scope="text,record"
|
||||
>
|
||||
<a-icon
|
||||
type="user"
|
||||
v-if="record.isAdmin"
|
||||
style="margin-right: 3px;"
|
||||
/>
|
||||
<a
|
||||
:href="record.authorUrl"
|
||||
target="_blank"
|
||||
v-if="record.authorUrl"
|
||||
>{{ text }}</a>
|
||||
<span v-else>{{ text }}</span>
|
||||
</template>
|
||||
<p
|
||||
class="comment-content-wrapper"
|
||||
slot="content"
|
||||
|
@ -116,8 +135,10 @@
|
|||
slot="status"
|
||||
slot-scope="statusProperty"
|
||||
>
|
||||
<a-badge :status="statusProperty.status" />
|
||||
{{ statusProperty.text }}
|
||||
<a-badge
|
||||
:status="statusProperty.status"
|
||||
:text="statusProperty.text"
|
||||
/>
|
||||
</span>
|
||||
<a
|
||||
v-if="type==='posts'"
|
||||
|
@ -203,6 +224,13 @@
|
|||
>
|
||||
<a href="javascript:;">删除</a>
|
||||
</a-popconfirm>
|
||||
|
||||
<!-- <a-divider type="vertical" />
|
||||
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="handleShowDetailDrawer(record)"
|
||||
>详情</a> -->
|
||||
</span>
|
||||
</a-table>
|
||||
<div class="page-wrapper">
|
||||
|
@ -223,6 +251,7 @@
|
|||
:title="'回复给:'+selectComment.author"
|
||||
v-model="replyCommentVisible"
|
||||
@close="onReplyClose"
|
||||
destroyOnClose
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button
|
||||
|
@ -243,16 +272,24 @@
|
|||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<!-- <CommentDetail
|
||||
v-model="commentDetailVisible"
|
||||
v-if="selectComment"
|
||||
:comment="selectComment"
|
||||
:type="this.type"
|
||||
/> -->
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import commentApi from '@/api/comment'
|
||||
import optionApi from '@/api/option'
|
||||
import { mapGetters } from 'vuex'
|
||||
import CommentDetail from './CommentDetail'
|
||||
import marked from 'marked'
|
||||
import commentApi from '@/api/comment'
|
||||
const postColumns = [
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'author'
|
||||
dataIndex: 'author',
|
||||
scopedSlots: { customRender: 'author' }
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
|
@ -263,29 +300,33 @@ const postColumns = [
|
|||
title: '状态',
|
||||
className: 'status',
|
||||
dataIndex: 'statusProperty',
|
||||
width: '100px',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: '评论文章',
|
||||
dataIndex: 'post',
|
||||
width: '200px',
|
||||
scopedSlots: { customRender: 'post' }
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
dataIndex: 'createTime',
|
||||
width: '170px',
|
||||
scopedSlots: { customRender: 'createTime' }
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: '150px',
|
||||
width: '180px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
const sheetColumns = [
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'author'
|
||||
dataIndex: 'author',
|
||||
scopedSlots: { customRender: 'author' }
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
|
@ -296,27 +337,33 @@ const sheetColumns = [
|
|||
title: '状态',
|
||||
className: 'status',
|
||||
dataIndex: 'statusProperty',
|
||||
width: '100px',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: '评论页面',
|
||||
dataIndex: 'sheet',
|
||||
width: '200px',
|
||||
scopedSlots: { customRender: 'sheet' }
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
dataIndex: 'createTime',
|
||||
width: '150px',
|
||||
scopedSlots: { customRender: 'createTime' }
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: '150px',
|
||||
width: '180px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
export default {
|
||||
name: 'CommentTab',
|
||||
components: {
|
||||
CommentDetail
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
|
@ -350,13 +397,11 @@ export default {
|
|||
replyComment: {},
|
||||
loading: false,
|
||||
commentStatus: commentApi.commentStatus,
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
commentDetailVisible: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadComments()
|
||||
this.loadOptions()
|
||||
},
|
||||
computed: {
|
||||
formattedComments() {
|
||||
|
@ -365,7 +410,8 @@ export default {
|
|||
comment.content = marked(comment.content, { sanitize: true })
|
||||
return comment
|
||||
})
|
||||
}
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
loadComments() {
|
||||
|
@ -381,13 +427,9 @@ export default {
|
|||
},
|
||||
handleQuery() {
|
||||
this.queryParam.page = 0
|
||||
this.pagination.current = 1
|
||||
this.loadComments()
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
handleEditStatusClick(commentId, status) {
|
||||
commentApi.updateStatus(this.type, commentId, status).then(response => {
|
||||
this.$message.success('操作成功!')
|
||||
|
@ -415,6 +457,13 @@ export default {
|
|||
}
|
||||
},
|
||||
handleCreateClick() {
|
||||
if (!this.replyComment.content) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '评论内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
commentApi.create(this.type, this.replyComment).then(response => {
|
||||
this.$message.success('回复成功!')
|
||||
this.replyComment = {}
|
||||
|
@ -492,6 +541,10 @@ export default {
|
|||
name: comment.author
|
||||
}
|
||||
}
|
||||
},
|
||||
handleShowDetailDrawer(comment) {
|
||||
this.selectComment = comment
|
||||
this.commentDetailVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,10 +129,30 @@
|
|||
>
|
||||
<a-list-item-meta>
|
||||
<a
|
||||
v-if="item.status=='PUBLISHED'"
|
||||
slot="title"
|
||||
:href="options.blog_url+'/archives/'+item.url"
|
||||
target="_blank"
|
||||
>{{ item.title }}</a>
|
||||
<a
|
||||
v-else-if="item.status == 'INTIMATE'"
|
||||
slot="title"
|
||||
:href="options.blog_url+'/archives/'+item.url+'/password'"
|
||||
target="_blank"
|
||||
>{{ item.title }}</a>
|
||||
<a
|
||||
v-else-if="item.status=='DRAFT'"
|
||||
slot="title"
|
||||
href="javascript:void(0)"
|
||||
@click="handlePostPreview(item.id)"
|
||||
>{{ item.title }}</a>
|
||||
<a
|
||||
v-else
|
||||
href="javascript:void(0);"
|
||||
disabled
|
||||
>
|
||||
{{ text }}
|
||||
</a>
|
||||
</a-list-item-meta>
|
||||
<div>{{ item.createTime | timeAgo }}</div>
|
||||
</a-list-item>
|
||||
|
@ -271,9 +291,14 @@
|
|||
title="操作日志"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="logDrawerVisiable"
|
||||
:visible="logDrawerVisible"
|
||||
destroyOnClose
|
||||
@close="()=>this.logDrawerVisiable = false"
|
||||
@close="()=>this.logDrawerVisible = false"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="logsLoading"
|
||||
:paragraph="{rows: 18}"
|
||||
>
|
||||
<a-row
|
||||
type="flex"
|
||||
|
@ -306,6 +331,7 @@
|
|||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-skeleton>
|
||||
<a-divider class="divider-transparent" />
|
||||
<div class="bottom-control">
|
||||
<a-popconfirm
|
||||
|
@ -322,18 +348,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { PageView } from '@/layouts'
|
||||
import AnalysisCard from './components/AnalysisCard'
|
||||
import RecentCommentTab from './components/RecentCommentTab'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import optionApi from '@/api/option'
|
||||
import countTo from 'vue-count-to'
|
||||
import UploadPhoto from '../../components/Upload/UploadPhoto.vue'
|
||||
|
||||
import postApi from '@/api/post'
|
||||
import logApi from '@/api/log'
|
||||
import adminApi from '@/api/admin'
|
||||
import journalApi from '@/api/journal'
|
||||
import countTo from 'vue-count-to'
|
||||
import UploadPhoto from '../../components/Upload/UploadPhoto.vue'
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
mixins: [mixin, mixinDevice],
|
||||
|
@ -347,14 +373,15 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
photoList: [],
|
||||
showMoreOptions: false,
|
||||
// showMoreOptions: false,
|
||||
startVal: 0,
|
||||
logType: logApi.logType,
|
||||
activityLoading: true,
|
||||
writeLoading: true,
|
||||
logLoading: true,
|
||||
logsLoading: true,
|
||||
countsLoading: true,
|
||||
logDrawerVisiable: false,
|
||||
logDrawerVisible: false,
|
||||
postData: [],
|
||||
logData: [],
|
||||
countsData: {},
|
||||
|
@ -364,8 +391,6 @@ export default {
|
|||
},
|
||||
journalPhotos: [], // 日志图片集合最多九张
|
||||
logs: [],
|
||||
options: [],
|
||||
keys: ['blog_url'],
|
||||
logPagination: {
|
||||
page: 1,
|
||||
size: 50,
|
||||
|
@ -378,11 +403,6 @@ export default {
|
|||
this.getCounts()
|
||||
this.listLatestPosts()
|
||||
this.listLatestLogs()
|
||||
this.loadOptions()
|
||||
|
||||
// this.interval = setInterval(() => {
|
||||
// this.getCounts()
|
||||
// }, 5000)
|
||||
},
|
||||
computed: {
|
||||
formattedPostData() {
|
||||
|
@ -403,6 +423,12 @@ export default {
|
|||
log.type = this.logType[log.type].text
|
||||
return log
|
||||
})
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.logDrawerVisible) {
|
||||
this.logDrawerVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
|
@ -418,26 +444,24 @@ export default {
|
|||
this.interval = null
|
||||
this.$log.debug('Cleared interval')
|
||||
}
|
||||
if (this.logDrawerVisible) {
|
||||
this.logDrawerVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
methods: {
|
||||
handlerPhotoUploadSuccess(response, file) {
|
||||
var callData = response.data.data
|
||||
var photo = {
|
||||
name: callData.name,
|
||||
url: callData.path,
|
||||
thumbnail: callData.thumbPath,
|
||||
suffix: callData.suffix,
|
||||
width: callData.width,
|
||||
height: callData.height
|
||||
}
|
||||
this.journalPhotos.push(photo)
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
// handlerPhotoUploadSuccess(response, file) {
|
||||
// var callData = response.data.data
|
||||
// var photo = {
|
||||
// name: callData.name,
|
||||
// url: callData.path,
|
||||
// thumbnail: callData.thumbPath,
|
||||
// suffix: callData.suffix,
|
||||
// width: callData.width,
|
||||
// height: callData.height
|
||||
// }
|
||||
// this.journalPhotos.push(photo)
|
||||
// },
|
||||
listLatestPosts() {
|
||||
postApi.listLatest(5).then(response => {
|
||||
this.postData = response.data.data
|
||||
|
@ -462,24 +486,34 @@ export default {
|
|||
},
|
||||
handleCreateJournalClick() {
|
||||
// 给属性填充数据
|
||||
this.journal.photos = this.journalPhotos
|
||||
|
||||
// this.journal.photos = this.journalPhotos
|
||||
if (!this.journal.content) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
journalApi.create(this.journal).then(response => {
|
||||
this.$message.success('发表成功!')
|
||||
this.journal = {}
|
||||
this.photoList = []
|
||||
this.showMoreOptions = false
|
||||
// this.photoList = []
|
||||
// this.showMoreOptions = false
|
||||
})
|
||||
},
|
||||
handleUploadPhotoWallClick() {
|
||||
// 是否显示上传照片墙组件
|
||||
this.showMoreOptions = !this.showMoreOptions
|
||||
},
|
||||
// handleUploadPhotoWallClick() {
|
||||
// // 是否显示上传照片墙组件
|
||||
// this.showMoreOptions = !this.showMoreOptions
|
||||
// },
|
||||
handleShowLogDrawer() {
|
||||
this.logDrawerVisiable = true
|
||||
this.logDrawerVisible = true
|
||||
this.loadLogs()
|
||||
},
|
||||
loadLogs() {
|
||||
this.logsLoading = true
|
||||
setTimeout(() => {
|
||||
this.logsLoading = false
|
||||
}, 500)
|
||||
this.logPagination.page = this.logPagination.page - 1
|
||||
logApi.pageBy(this.logPagination).then(response => {
|
||||
this.logs = response.data.data.content
|
||||
|
@ -493,6 +527,11 @@ export default {
|
|||
this.listLatestLogs()
|
||||
})
|
||||
},
|
||||
handlePostPreview(postId) {
|
||||
postApi.preview(postId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
onPaginationChange(page, pageSize) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${pageSize}`)
|
||||
this.logPagination.page = page
|
||||
|
@ -503,12 +542,12 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style scoped="scoped">
|
||||
.more-options-btn{
|
||||
<style lang="less" scoped>
|
||||
/* .more-options-btn {
|
||||
margin-left: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
a {
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
} */
|
||||
</style>
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
</div>
|
||||
<div class="number">
|
||||
<slot name="number">
|
||||
<!-- <span>{{ typeof number === 'function' && number() || number }}</span> -->
|
||||
<countTo
|
||||
:startVal="startNumber"
|
||||
:endVal="typeof number === 'function' && number() || number"
|
||||
|
@ -64,37 +63,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.analysis-card-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
.meta {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
.analysis-card-action {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.number {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 32px;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -18,9 +18,22 @@
|
|||
:href="item.authorUrl"
|
||||
target="_blank"
|
||||
>{{ item.author }}</a> 发表在 《<a
|
||||
v-if="item.post.status=='PUBLISHED'"
|
||||
:href="options.blog_url+'/archives/'+item.post.url"
|
||||
target="_blank"
|
||||
>{{ item.post.title }}</a>》
|
||||
>{{ item.post.title }}</a><a
|
||||
v-else-if="item.post.status=='INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+item.post.url+'/password'"
|
||||
target="_blank"
|
||||
>{{ item.post.title }}</a><a
|
||||
v-else-if="item.post.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
@click="handlePostPreview(item.post.id)"
|
||||
>{{ item.post.title }}</a><a
|
||||
v-else
|
||||
href="javascript:void(0)"
|
||||
>{{ item.post.title }}</a>
|
||||
》
|
||||
</template>
|
||||
<template
|
||||
slot="author"
|
||||
|
@ -30,8 +43,16 @@
|
|||
:href="item.authorUrl"
|
||||
target="_blank"
|
||||
>{{ item.author }}</a> 发表在 《<a
|
||||
v-if="item.sheet.status=='PUBLISHED'"
|
||||
:href="options.blog_url+'/s/'+item.sheet.url"
|
||||
target="_blank"
|
||||
>{{ item.sheet.title }}</a><a
|
||||
v-else-if="item.sheet.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
@click="handleSheetPreview(item.sheet.id)"
|
||||
>{{ item.sheet.title }}</a><a
|
||||
v-else
|
||||
href="javascript:void(0)"
|
||||
>{{ item.sheet.title }}</a>》
|
||||
</template>
|
||||
<!-- <template slot="actions">
|
||||
|
@ -54,8 +75,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import commentApi from '@/api/comment'
|
||||
import optionApi from '@/api/option'
|
||||
import postApi from '@/api/post'
|
||||
import sheetApi from '@/api/sheet'
|
||||
|
||||
import marked from 'marked'
|
||||
export default {
|
||||
|
@ -73,9 +96,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
comments: [],
|
||||
loading: false,
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -84,24 +105,29 @@ export default {
|
|||
comment.content = marked(comment.content, { sanitize: true })
|
||||
return comment
|
||||
})
|
||||
}
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
created() {
|
||||
this.loadComments()
|
||||
this.loadOptions()
|
||||
},
|
||||
methods: {
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
loadComments() {
|
||||
this.loading = true
|
||||
commentApi.latestComment(this.type, 5, 'PUBLISHED').then(response => {
|
||||
this.comments = response.data.data
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handlePostPreview(postId) {
|
||||
postApi.preview(postId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
handleSheetPreview(sheetId) {
|
||||
sheetApi.preview(sheetId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
@ -11,6 +11,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card :title="title">
|
||||
<a-card
|
||||
:title="title"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-form-item
|
||||
label="名称:"
|
||||
|
@ -42,6 +45,12 @@
|
|||
>
|
||||
<a-input v-model="menuToCreate.icon" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="分组:"
|
||||
:style="{ display: fieldExpand ? 'block' : 'none' }"
|
||||
>
|
||||
<a-input v-model="menuToCreate.team" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="打开方式:"
|
||||
:style="{ display: fieldExpand ? 'block' : 'none' }"
|
||||
|
@ -90,7 +99,10 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card title="所有菜单">
|
||||
<a-card
|
||||
title="所有菜单"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="menus"
|
||||
|
@ -141,6 +153,10 @@ const columns = [
|
|||
title: '地址',
|
||||
dataIndex: 'url'
|
||||
},
|
||||
{
|
||||
title: '分组',
|
||||
dataIndex: 'team'
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'priority'
|
||||
|
@ -159,7 +175,9 @@ export default {
|
|||
loading: false,
|
||||
columns,
|
||||
menus: [],
|
||||
menuToCreate: {},
|
||||
menuToCreate: {
|
||||
target: '_self'
|
||||
},
|
||||
fieldExpand: false
|
||||
}
|
||||
},
|
||||
|
@ -219,6 +237,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:xs="24"
|
||||
:style="{'padding-bottom':'12px'}"
|
||||
>
|
||||
<a-card>
|
||||
<a-card :bodyStyle="{ padding: '16px' }">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<codemirror
|
||||
|
@ -35,8 +35,27 @@
|
|||
:xs="24"
|
||||
:style="{'padding-bottom':'12px'}"
|
||||
>
|
||||
<a-card :title="activatedTheme.name+' 主题'">
|
||||
<a-card :bodyStyle="{ padding: '16px' }">
|
||||
<template slot="title">
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
@change="onSelectTheme"
|
||||
v-model="selectedTheme.id"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(theme,index) in themes"
|
||||
:key="index"
|
||||
:value="theme.id"
|
||||
>{{ theme.name }}
|
||||
<a-icon
|
||||
v-if="theme.activated"
|
||||
type="check"
|
||||
/>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<theme-file
|
||||
v-if="files"
|
||||
:files="files"
|
||||
@listenToSelect="handleSelectFile"
|
||||
/>
|
||||
|
@ -65,25 +84,38 @@ export default {
|
|||
lineNumbers: true,
|
||||
line: true
|
||||
},
|
||||
files: [],
|
||||
files: null,
|
||||
file: {},
|
||||
content: '',
|
||||
activatedTheme: {}
|
||||
themes: [],
|
||||
selectedTheme: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadFiles()
|
||||
this.loadActivatedTheme()
|
||||
this.loadFiles()
|
||||
this.loadThemes()
|
||||
},
|
||||
methods: {
|
||||
loadActivatedTheme() {
|
||||
themeApi.getActivatedTheme().then(response => {
|
||||
this.selectedTheme = response.data.data
|
||||
})
|
||||
},
|
||||
loadFiles() {
|
||||
themeApi.listFiles().then(response => {
|
||||
themeApi.listFilesActivated().then(response => {
|
||||
this.files = response.data.data
|
||||
})
|
||||
},
|
||||
loadActivatedTheme() {
|
||||
themeApi.getActivatedTheme().then(response => {
|
||||
this.activatedTheme = response.data.data
|
||||
loadThemes() {
|
||||
themeApi.listAll().then(response => {
|
||||
this.themes = response.data.data
|
||||
})
|
||||
},
|
||||
onSelectTheme(themeId) {
|
||||
this.files = null
|
||||
themeApi.listFiles(themeId).then(response => {
|
||||
this.files = response.data.data
|
||||
})
|
||||
},
|
||||
handleSelectFile(file) {
|
||||
|
@ -95,7 +127,12 @@ export default {
|
|||
this.buttonDisabled = true
|
||||
return
|
||||
}
|
||||
if (file.name === 'settings.yaml' || file.name === 'settings.yml' || file.name === 'theme.yaml' || file.name === 'theme.yml') {
|
||||
if (
|
||||
file.name === 'settings.yaml' ||
|
||||
file.name === 'settings.yml' ||
|
||||
file.name === 'theme.yaml' ||
|
||||
file.name === 'theme.yml'
|
||||
) {
|
||||
this.$confirm({
|
||||
title: '警告:请谨慎修改该配置文件',
|
||||
content: '修改之后可能会产生不可预料的问题!',
|
||||
|
@ -106,14 +143,14 @@ export default {
|
|||
}
|
||||
})
|
||||
}
|
||||
themeApi.getContent(file.path).then(response => {
|
||||
themeApi.getContent(this.selectedTheme.id, file.path).then(response => {
|
||||
this.content = response.data.data
|
||||
this.file = file
|
||||
this.buttonDisabled = false
|
||||
})
|
||||
},
|
||||
handlerSaveContent() {
|
||||
themeApi.saveContent(this.file.path, this.content).then(response => {
|
||||
themeApi.saveContent(this.selectedTheme.id, this.file.path, this.content).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
})
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
style="margin-right:3px"
|
||||
/>启用
|
||||
</div>
|
||||
<div @click="handleEditClick(item)">
|
||||
<div @click="handleShowThemeSetting(item)">
|
||||
<a-icon
|
||||
type="setting"
|
||||
style="margin-right:3px"
|
||||
|
@ -90,7 +90,10 @@
|
|||
/>删除
|
||||
</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item :key="2">
|
||||
<a-menu-item
|
||||
:key="2"
|
||||
v-if="item.repo"
|
||||
>
|
||||
<a-popconfirm
|
||||
:title="'确定更新【' + item.name + '】主题?'"
|
||||
@confirm="handleUpdateTheme(item.id)"
|
||||
|
@ -98,11 +101,20 @@
|
|||
cancelText="取消"
|
||||
>
|
||||
<a-icon
|
||||
type="download"
|
||||
type="cloud"
|
||||
style="margin-right:3px"
|
||||
/>更新
|
||||
/>在线更新
|
||||
</a-popconfirm>
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
:key="3"
|
||||
@click="handleShowUpdateNewThemeModal(item)"
|
||||
>
|
||||
<a-icon
|
||||
type="file"
|
||||
style="margin-right:3px"
|
||||
/>从主题包更新
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
@ -111,157 +123,13 @@
|
|||
</a-list>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-drawer
|
||||
v-if="themeProperty"
|
||||
:title="themeProperty.name + ' 主题设置'"
|
||||
width="100%"
|
||||
closable
|
||||
@close="onClose"
|
||||
:visible="visible"
|
||||
destroyOnClose
|
||||
>
|
||||
<a-row
|
||||
:gutter="12"
|
||||
type="flex"
|
||||
>
|
||||
<a-col
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="optionLoading"
|
||||
:paragraph="{rows: 10}"
|
||||
>
|
||||
<a-card :bordered="false">
|
||||
<img
|
||||
:alt="themeProperty.name"
|
||||
:src="themeProperty.screenshots"
|
||||
slot="cover"
|
||||
>
|
||||
<a-card-meta :description="themeProperty.description">
|
||||
<template slot="title">
|
||||
<a
|
||||
:href="themeProperty.author.website"
|
||||
target="_blank"
|
||||
>{{ themeProperty.author.name }}</a>
|
||||
</template>
|
||||
<a-avatar
|
||||
v-if="themeProperty.logo"
|
||||
:src="themeProperty.logo"
|
||||
size="large"
|
||||
slot="avatar"
|
||||
/>
|
||||
<a-avatar
|
||||
v-else
|
||||
size="large"
|
||||
slot="avatar"
|
||||
>{{ themeProperty.author.name }}</a-avatar>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
style="padding-bottom: 50px;"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="optionLoading"
|
||||
:paragraph="{rows: 20}"
|
||||
>
|
||||
<div class="card-container">
|
||||
<a-tabs
|
||||
type="card"
|
||||
defaultActiveKey="0"
|
||||
v-if="themeConfiguration.length>0"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="(group, index) in themeConfiguration"
|
||||
:key="index.toString()"
|
||||
:tab="group.label"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
v-for="(item, index1) in group.items"
|
||||
:label="item.label + ':'"
|
||||
:key="index1"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
:placeholder="item.placeholder"
|
||||
v-if="item.type == 'TEXT'"
|
||||
/>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="themeSettings[item.name]"
|
||||
:placeholder="item.placeholder"
|
||||
v-else-if="item.type == 'TEXTAREA'"
|
||||
/>
|
||||
<a-radio-group
|
||||
v-decorator="['radio-group']"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-model="themeSettings[item.name]"
|
||||
v-else-if="item.type == 'RADIO'"
|
||||
>
|
||||
<a-radio
|
||||
v-for="(option, index2) in item.options"
|
||||
:key="index2"
|
||||
:value="option.value"
|
||||
>{{ option.label }}</a-radio>
|
||||
</a-radio-group>
|
||||
<a-select
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type == 'SELECT'"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="option in item.options"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>{{ option.label }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-alert
|
||||
message="当前主题暂无设置选项"
|
||||
banner
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<footer-tool-bar
|
||||
v-if="themeConfiguration.length>0"
|
||||
:style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleSaveSettings"
|
||||
>保存</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click="()=>this.attachmentDrawerVisible = true"
|
||||
style="margin-left: 8px;"
|
||||
>附件库</a-button>
|
||||
</footer-tool-bar>
|
||||
<ThemeSetting
|
||||
:theme="selectedTheme"
|
||||
v-if="themeSettingVisible"
|
||||
@close="onThemeSettingsClose"
|
||||
/>
|
||||
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
</a-drawer>
|
||||
<div class="upload-button">
|
||||
<a-dropdown
|
||||
placement="topLeft"
|
||||
|
@ -278,7 +146,7 @@
|
|||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="javascript:void(0);"
|
||||
@click="()=>this.uploadVisible = true"
|
||||
@click="()=>this.uploadThemeVisible = true"
|
||||
>安装主题</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
|
@ -293,9 +161,11 @@
|
|||
</div>
|
||||
<a-modal
|
||||
title="安装主题"
|
||||
v-model="uploadVisible"
|
||||
v-model="uploadThemeVisible"
|
||||
destroyOnClose
|
||||
:footer="null"
|
||||
:bodyStyle="{ padding: '0 24px 24px' }"
|
||||
:afterClose="onThemeUploadClose"
|
||||
>
|
||||
<div class="custom-tab-wrapper">
|
||||
<a-tabs>
|
||||
|
@ -333,59 +203,61 @@
|
|||
tab="本地上传"
|
||||
key="2"
|
||||
>
|
||||
<upload
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
name="file"
|
||||
multiple
|
||||
accept="application/zip"
|
||||
label="点击选择主题包或将主题包拖拽到此处<br>仅支持 ZIP 格式的文件"
|
||||
:uploadHandler="uploadHandler"
|
||||
@change="handleChange"
|
||||
@success="handleUploadSuccess"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<a-icon type="inbox" />
|
||||
</p>
|
||||
<p class="ant-upload-text">点击选择主题或将主题拖拽到此处</p>
|
||||
<p class="ant-upload-hint">支持单个或批量上传,仅支持 ZIP 格式的文件</p>
|
||||
</upload>
|
||||
</FilePondUpload>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
title="更新主题"
|
||||
v-model="uploadNewThemeVisible"
|
||||
:footer="null"
|
||||
destroyOnClose
|
||||
:afterClose="onThemeUploadClose"
|
||||
>
|
||||
<FilePondUpload
|
||||
ref="updateByupload"
|
||||
name="file"
|
||||
accept="application/zip"
|
||||
label="点击选择主题更新包或将主题更新包拖拽到此处<br>仅支持 ZIP 格式的文件"
|
||||
:uploadHandler="updateByUploadHandler"
|
||||
:filed="prepareUpdateTheme.id"
|
||||
:multiple="false"
|
||||
@success="handleUploadSuccess"
|
||||
>
|
||||
</FilePondUpload>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import ThemeSetting from './components/ThemeSetting'
|
||||
import themeApi from '@/api/theme'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AttachmentDrawer,
|
||||
FooterToolBar
|
||||
ThemeSetting
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
themeLoading: false,
|
||||
optionLoading: true,
|
||||
uploadVisible: false,
|
||||
uploadThemeVisible: false,
|
||||
uploadNewThemeVisible: false,
|
||||
fetchButtonLoading: false,
|
||||
wrapperCol: {
|
||||
xl: { span: 12 },
|
||||
lg: { span: 12 },
|
||||
sm: { span: 24 },
|
||||
xs: { span: 24 }
|
||||
},
|
||||
attachmentDrawerVisible: false,
|
||||
themes: [],
|
||||
visible: false,
|
||||
themeConfiguration: [],
|
||||
themeSettings: [],
|
||||
themeProperty: null,
|
||||
themeSettingVisible: false,
|
||||
selectedTheme: {},
|
||||
fetchingUrl: null,
|
||||
uploadHandler: themeApi.upload
|
||||
uploadHandler: themeApi.upload,
|
||||
updateByUploadHandler: themeApi.updateByUpload,
|
||||
prepareUpdateTheme: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -400,13 +272,13 @@ export default {
|
|||
this.loadThemes()
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.visible) {
|
||||
this.visible = false
|
||||
if (this.themeSettingVisible) {
|
||||
this.themeSettingVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.visible) {
|
||||
this.visible = false
|
||||
if (this.themeSettingVisible) {
|
||||
this.themeSettingVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
|
@ -418,21 +290,7 @@ export default {
|
|||
this.themeLoading = false
|
||||
})
|
||||
},
|
||||
settingDrawer(theme) {
|
||||
this.visible = true
|
||||
this.optionLoading = true
|
||||
this.themeProperty = theme
|
||||
|
||||
themeApi.fetchConfiguration(theme.id).then(response => {
|
||||
this.themeConfiguration = response.data.data
|
||||
themeApi.fetchSettings(theme.id).then(response => {
|
||||
this.themeSettings = response.data.data
|
||||
setTimeout(() => {
|
||||
this.optionLoading = false
|
||||
}, 300)
|
||||
})
|
||||
})
|
||||
},
|
||||
activeTheme(themeId) {
|
||||
themeApi.active(themeId).then(response => {
|
||||
this.$message.success('设置成功!')
|
||||
|
@ -451,32 +309,15 @@ export default {
|
|||
this.loadThemes()
|
||||
})
|
||||
},
|
||||
handleSaveSettings() {
|
||||
themeApi.saveSettings(this.themeProperty.id, this.themeSettings).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.visible = false
|
||||
this.optionLoading = false
|
||||
this.themeConfiguration = []
|
||||
this.themeProperty = null
|
||||
},
|
||||
handleChange(info) {
|
||||
const status = info.file.status
|
||||
if (status === 'done') {
|
||||
this.$message.success(`${info.file.name} 主题上传成功!`)
|
||||
} else if (status === 'error') {
|
||||
this.$message.error(`${info.file.name} 主题上传失败!`)
|
||||
}
|
||||
},
|
||||
handleUploadSuccess() {
|
||||
this.uploadVisible = false
|
||||
if (this.uploadThemeVisible) {
|
||||
this.uploadThemeVisible = false
|
||||
}
|
||||
if (this.uploadNewThemeVisible) {
|
||||
this.uploadNewThemeVisible = false
|
||||
}
|
||||
this.loadThemes()
|
||||
},
|
||||
handleEllipsisClick(theme) {
|
||||
this.$log.debug('Ellipsis clicked', theme)
|
||||
},
|
||||
handleEditClick(theme) {
|
||||
this.settingDrawer(theme)
|
||||
},
|
||||
|
@ -484,12 +325,19 @@ export default {
|
|||
this.activeTheme(theme.id)
|
||||
},
|
||||
handleFetching() {
|
||||
if (!this.fetchingUrl) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '远程地址不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.fetchButtonLoading = true
|
||||
themeApi
|
||||
.fetching(this.fetchingUrl)
|
||||
.then(response => {
|
||||
this.$message.success('拉取成功!')
|
||||
this.uploadVisible = false
|
||||
this.uploadThemeVisible = false
|
||||
this.loadThemes()
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -501,12 +349,33 @@ export default {
|
|||
this.loadThemes()
|
||||
this.$message.success('刷新成功!')
|
||||
})
|
||||
},
|
||||
handleShowUpdateNewThemeModal(item) {
|
||||
this.prepareUpdateTheme = item
|
||||
this.uploadNewThemeVisible = true
|
||||
},
|
||||
handleShowThemeSetting(theme) {
|
||||
this.selectedTheme = theme
|
||||
this.themeSettingVisible = true
|
||||
},
|
||||
onThemeUploadClose() {
|
||||
if (this.uploadThemeVisible) {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
}
|
||||
if (this.uploadNewThemeVisible) {
|
||||
this.$refs.updateByupload.handleClearFileList()
|
||||
}
|
||||
this.loadThemes()
|
||||
},
|
||||
onThemeSettingsClose() {
|
||||
this.themeSettingVisible = false
|
||||
this.selectedTheme = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
<style lang="less">
|
||||
@keyframes scaleDraw {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
|
|
|
@ -55,6 +55,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
@ -56,11 +56,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-tree-child-tree {
|
||||
li {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
:title="selectedTheme.name + ' 主题设置'"
|
||||
width="100%"
|
||||
placement="right"
|
||||
closable
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
:visible="visible"
|
||||
>
|
||||
<a-row
|
||||
:gutter="12"
|
||||
type="flex"
|
||||
>
|
||||
<a-col
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
v-if="!viewMode"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="settingLoading"
|
||||
:paragraph="{rows: 10}"
|
||||
>
|
||||
<a-card :bordered="false">
|
||||
<img
|
||||
:alt="selectedTheme.name"
|
||||
:src="selectedTheme.screenshots"
|
||||
slot="cover"
|
||||
>
|
||||
<a-card-meta :description="selectedTheme.description">
|
||||
<template slot="title">
|
||||
<a
|
||||
:href="selectedTheme.author.website"
|
||||
target="_blank"
|
||||
>{{ selectedTheme.author.name }}</a>
|
||||
</template>
|
||||
<a-avatar
|
||||
v-if="selectedTheme.logo"
|
||||
:src="selectedTheme.logo"
|
||||
size="large"
|
||||
slot="avatar"
|
||||
/>
|
||||
<a-avatar
|
||||
v-else
|
||||
size="large"
|
||||
slot="avatar"
|
||||
>{{ selectedTheme.author.name }}</a-avatar>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
<a-col
|
||||
:xl="formColValue"
|
||||
:lg="formColValue"
|
||||
:md="formColValue"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
style="padding-bottom: 50px;"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="settingLoading"
|
||||
:paragraph="{rows: 20}"
|
||||
>
|
||||
<div class="card-container">
|
||||
<a-tabs
|
||||
type="card"
|
||||
defaultActiveKey="0"
|
||||
v-if="themeConfiguration.length>0"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="(group, index) in themeConfiguration"
|
||||
:key="index.toString()"
|
||||
:tab="group.label"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
v-for="(item, index1) in group.items"
|
||||
:label="item.label + ':'"
|
||||
:key="index1"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
:placeholder="item.placeholder"
|
||||
v-if="item.type == 'TEXT'"
|
||||
/>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="themeSettings[item.name]"
|
||||
:placeholder="item.placeholder"
|
||||
v-else-if="item.type == 'TEXTAREA'"
|
||||
/>
|
||||
<a-radio-group
|
||||
v-decorator="['radio-group']"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-model="themeSettings[item.name]"
|
||||
v-else-if="item.type == 'RADIO'"
|
||||
>
|
||||
<a-radio
|
||||
v-for="(option, index2) in item.options"
|
||||
:key="index2"
|
||||
:value="option.value"
|
||||
>{{ option.label }}</a-radio>
|
||||
</a-radio-group>
|
||||
<a-select
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type == 'SELECT'"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="option in item.options"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>{{ option.label }}</a-select-option>
|
||||
</a-select>
|
||||
<verte
|
||||
picker="square"
|
||||
model="hex"
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type == 'COLOR'"
|
||||
style="display: inline-block;height: 24px;"
|
||||
></verte>
|
||||
<a-input
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
v-else-if="item.type == 'ATTACHMENT'"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click="handleShowSelectAttachment(item.name)"
|
||||
>
|
||||
<a-icon type="picture" />
|
||||
</a>
|
||||
</a-input>
|
||||
<a-input
|
||||
v-model="themeSettings[item.name]"
|
||||
:defaultValue="item.defaultValue"
|
||||
:placeholder="item.placeholder"
|
||||
v-else
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<a-alert
|
||||
message="当前主题暂无设置选项"
|
||||
banner
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
</a-skeleton>
|
||||
</a-col>
|
||||
|
||||
<a-col
|
||||
:xl="20"
|
||||
:lg="20"
|
||||
:md="20"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
v-if="viewMode"
|
||||
style="padding-bottom: 50px;"
|
||||
>
|
||||
<a-card
|
||||
:bordered="true"
|
||||
:bodyStyle="{ padding: 0}"
|
||||
>
|
||||
<iframe
|
||||
id="themeViewIframe"
|
||||
title="主题预览"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
border="0"
|
||||
:src="options.blog_url"
|
||||
width="100%"
|
||||
:height="clientHeight-165"
|
||||
> </iframe>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<AttachmentSelectDrawer
|
||||
v-model="attachmentDrawerVisible"
|
||||
@listenToSelect="handleSelectAttachment"
|
||||
title="选择附件"
|
||||
/>
|
||||
|
||||
<footer-tool-bar
|
||||
v-if="themeConfiguration.length>0"
|
||||
:style="{ width : '100%'}"
|
||||
>
|
||||
<a-button
|
||||
v-if="!this.isMobile() && theme.activated && viewMode"
|
||||
type="primary"
|
||||
@click="toggleViewMode"
|
||||
style="marginRight: 8px"
|
||||
ghost
|
||||
>普通模式</a-button>
|
||||
<a-button
|
||||
v-else-if="!this.isMobile() && theme.activated && !viewMode"
|
||||
type="dashed"
|
||||
@click="toggleViewMode"
|
||||
style="marginRight: 8px"
|
||||
>预览模式</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleSaveSettings"
|
||||
>保存</a-button>
|
||||
</footer-tool-bar>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import Verte from 'verte'
|
||||
import 'verte/dist/verte.css'
|
||||
import themeApi from '@/api/theme'
|
||||
export default {
|
||||
name: 'ThemeSetting',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
AttachmentSelectDrawer,
|
||||
FooterToolBar,
|
||||
Verte
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachmentDrawerVisible: false,
|
||||
selectedTheme: this.theme,
|
||||
themeConfiguration: [],
|
||||
themeSettings: [],
|
||||
settingLoading: true,
|
||||
selectedField: '',
|
||||
wrapperCol: {
|
||||
xl: { span: 12 },
|
||||
lg: { span: 12 },
|
||||
sm: { span: 24 },
|
||||
xs: { span: 24 }
|
||||
},
|
||||
viewMode: false,
|
||||
formColValue: 12,
|
||||
clientHeight: document.documentElement.clientHeight
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'visible',
|
||||
event: 'close'
|
||||
},
|
||||
props: {
|
||||
theme: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
this.initData()
|
||||
},
|
||||
watch: {
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
loadSkeleton() {
|
||||
this.settingLoading = true
|
||||
setTimeout(() => {
|
||||
this.settingLoading = false
|
||||
}, 500)
|
||||
},
|
||||
initData() {
|
||||
this.settingLoading = true
|
||||
|
||||
themeApi.fetchConfiguration(this.selectedTheme.id).then(response => {
|
||||
this.themeConfiguration = response.data.data
|
||||
themeApi.fetchSettings(this.selectedTheme.id).then(response => {
|
||||
this.themeSettings = response.data.data
|
||||
setTimeout(() => {
|
||||
this.settingLoading = false
|
||||
}, 300)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleSaveSettings() {
|
||||
themeApi.saveSettings(this.selectedTheme.id, this.themeSettings).then(response => {
|
||||
this.$message.success('保存成功!')
|
||||
if (this.viewMode) {
|
||||
document.getElementById('themeViewIframe').contentWindow.location.reload(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
},
|
||||
handleShowSelectAttachment(field) {
|
||||
this.selectedField = field
|
||||
this.attachmentDrawerVisible = true
|
||||
},
|
||||
handleSelectAttachment(data) {
|
||||
this.themeSettings[this.selectedField] = encodeURI(data.path)
|
||||
this.attachmentDrawerVisible = false
|
||||
},
|
||||
toggleViewMode() {
|
||||
this.viewMode = !this.viewMode
|
||||
if (this.viewMode) {
|
||||
this.formColValue = 4
|
||||
this.wrapperCol = {
|
||||
xl: { span: 24 },
|
||||
lg: { span: 24 },
|
||||
sm: { span: 24 },
|
||||
xs: { span: 24 }
|
||||
}
|
||||
} else {
|
||||
this.formColValue = 12
|
||||
this.wrapperCol = {
|
||||
xl: { span: 12 },
|
||||
lg: { span: 12 },
|
||||
sm: { span: 24 },
|
||||
xs: { span: 24 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -9,7 +9,7 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card :title="title">
|
||||
<a-card :title="title" :bodyStyle="{ padding: '16px' }">
|
||||
<a-form layout="horizontal">
|
||||
<a-form-item
|
||||
label="名称:"
|
||||
|
@ -68,7 +68,7 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '1rem' }"
|
||||
>
|
||||
<a-card title="分类列表">
|
||||
<a-card title="分类列表" :bodyStyle="{ padding: '16px' }">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="categories"
|
||||
|
@ -206,6 +206,13 @@ export default {
|
|||
})
|
||||
},
|
||||
createOrUpdateCategory() {
|
||||
if (!this.categoryToCreate.name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '分类名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.categoryToCreate.id) {
|
||||
categoryApi.update(this.categoryToCreate.id, this.categoryToCreate).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
|
@ -232,9 +239,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.category-tree {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,187 +12,47 @@
|
|||
</div>
|
||||
|
||||
<div id="editor">
|
||||
<mavon-editor
|
||||
<halo-editor
|
||||
ref="md"
|
||||
v-model="postToStage.originalContent"
|
||||
:boxShadow="false"
|
||||
:toolbars="toolbars"
|
||||
:ishljs="true"
|
||||
:autofocus="false"
|
||||
@imgAdd="pictureUploadHandle"
|
||||
@imgAdd="handleAttachmentUpload"
|
||||
@keydown.ctrl.83.native="handleSaveDraft"
|
||||
@keydown.meta.83.native="handleSaveDraft"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-drawer
|
||||
title="文章设置"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
placement="right"
|
||||
closable
|
||||
@close="()=>this.postSettingVisible=false"
|
||||
<PostSetting
|
||||
:post="postToStage"
|
||||
:tagIds="selectedTagIds"
|
||||
:categoryIds="selectedCategoryIds"
|
||||
:visible="postSettingVisible"
|
||||
>
|
||||
<div class="post-setting-drawer-content">
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">基本设置</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="文章路径:"
|
||||
:help="options.blog_url+'/archives/' + (postToStage.url ? postToStage.url : '{auto_generate}')"
|
||||
>
|
||||
<a-input v-model="postToStage.url" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="发表时间:"
|
||||
>
|
||||
<a-date-picker
|
||||
showTime
|
||||
:defaultValue="pickerDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="Select Publish Time"
|
||||
@change="onChange"
|
||||
@ok="onOk"
|
||||
@close="onPostSettingsClose"
|
||||
@onRefreshPost="onRefreshPostFromSetting"
|
||||
@onRefreshTagIds="onRefreshTagIdsFromSetting"
|
||||
@onRefreshCategoryIds="onRefreshCategoryIdsFromSetting"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="开启评论:">
|
||||
<a-radio-group
|
||||
v-model="postToStage.disallowComment"
|
||||
:defaultValue="false"
|
||||
>
|
||||
<a-radio :value="false">开启</a-radio>
|
||||
<a-radio :value="true">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">分类目录</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<category-tree
|
||||
v-model="selectedCategoryIds"
|
||||
:categories="categories"
|
||||
/>
|
||||
<div>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item v-if="categoryForm">
|
||||
<category-select-tree
|
||||
:categories="categories"
|
||||
v-model="categoryToCreate.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryForm">
|
||||
<a-input
|
||||
placeholder="分类名称"
|
||||
v-model="categoryToCreate.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryForm">
|
||||
<a-input
|
||||
placeholder="分类路径"
|
||||
v-model="categoryToCreate.slugNames"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="marginRight: 8px"
|
||||
v-if="categoryForm"
|
||||
@click="handlerCreateCategory"
|
||||
>保存</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="marginRight: 8px"
|
||||
v-if="!categoryForm"
|
||||
@click="toggleCategoryForm"
|
||||
>新增</a-button>
|
||||
<a-button
|
||||
v-if="categoryForm"
|
||||
@click="toggleCategoryForm"
|
||||
>取消</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">标签</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<TagSelect v-model="selectedTagIds" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">摘要</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="postToStage.summary"
|
||||
placeholder="不填写则会自动生成"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">缩略图</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<div class="post-thum">
|
||||
<img
|
||||
class="img"
|
||||
:src="postToStage.thumbnail || '//i.loli.net/2019/05/05/5ccf007c0a01d.png'"
|
||||
@click="()=>this.thumDrawerVisible=true"
|
||||
>
|
||||
<a-button
|
||||
class="post-thum-remove"
|
||||
type="dashed"
|
||||
@click="handlerRemoveThumb"
|
||||
>移除</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
<AttachmentSelectDrawer
|
||||
v-model="thumDrawerVisible"
|
||||
@listenToSelect="handleSelectPostThumb"
|
||||
:drawerWidth="460"
|
||||
/>
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
style="marginRight: 8px"
|
||||
@click="handleDraftClick"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePublishClick"
|
||||
type="primary"
|
||||
>发布</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
|
||||
<footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}">
|
||||
<a-button
|
||||
type="danger"
|
||||
@click="handleSaveDraft"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePreview"
|
||||
style="margin-left: 8px;"
|
||||
>预览</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="()=>this.postSettingVisible = true"
|
||||
@click="handleShowPostSetting"
|
||||
style="margin-left: 8px;"
|
||||
>发布</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
|
@ -204,32 +64,26 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import CategoryTree from './components/CategoryTree'
|
||||
import TagSelect from './components/TagSelect'
|
||||
import { mavonEditor } from 'mavon-editor'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import CategorySelectTree from './components/CategorySelectTree'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { toolbars } from '@/core/const'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
import categoryApi from '@/api/category'
|
||||
import postApi from '@/api/post'
|
||||
import optionApi from '@/api/option'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import PostSetting from './components/PostSetting'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { toolbars } from '@/core/const'
|
||||
import { haloEditor } from 'halo-editor'
|
||||
import 'halo-editor/dist/css/index.css'
|
||||
|
||||
import postApi from '@/api/post'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
export default {
|
||||
components: {
|
||||
TagSelect,
|
||||
mavonEditor,
|
||||
CategoryTree,
|
||||
FooterToolBar,
|
||||
AttachmentDrawer,
|
||||
AttachmentSelectDrawer,
|
||||
CategorySelectTree
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
PostSetting,
|
||||
haloEditor,
|
||||
FooterToolBar,
|
||||
AttachmentDrawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
toolbars,
|
||||
|
@ -240,41 +94,14 @@ export default {
|
|||
},
|
||||
attachmentDrawerVisible: false,
|
||||
postSettingVisible: false,
|
||||
thumDrawerVisible: false,
|
||||
categoryForm: false,
|
||||
categories: [],
|
||||
selectedCategoryIds: [],
|
||||
selectedTagIds: [],
|
||||
postToStage: {},
|
||||
categoryToCreate: {},
|
||||
timer: null,
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
selectedTagIds: [],
|
||||
selectedCategoryIds: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCategories()
|
||||
this.loadOptions()
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
this.autoSaveTimer()
|
||||
},
|
||||
destroyed: function() {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.timer !== null) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
// Auto save the post
|
||||
this.autoSavePost()
|
||||
next()
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
// Get post id from query
|
||||
const postId = to.query.postId
|
||||
|
||||
next(vm => {
|
||||
if (postId) {
|
||||
postApi.get(postId).then(response => {
|
||||
|
@ -286,122 +113,108 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.postSettingVisible) {
|
||||
this.postSettingVisible = false
|
||||
}
|
||||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.postSettingVisible) {
|
||||
this.postSettingVisible = false
|
||||
}
|
||||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
computed: {
|
||||
pickerDefaultValue() {
|
||||
if (this.postToStage.createTime) {
|
||||
var date = new Date(this.postToStage.createTime)
|
||||
return moment(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
loadCategories() {
|
||||
categoryApi.listAll().then(response => {
|
||||
this.categories = response.data.data
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
createOrUpdatePost(createSuccess, updateSuccess, autoSave) {
|
||||
// Set category ids
|
||||
this.postToStage.categoryIds = this.selectedCategoryIds
|
||||
// Set tag ids
|
||||
this.postToStage.tagIds = this.selectedTagIds
|
||||
|
||||
handleSaveDraft() {
|
||||
this.postToStage.status = 'DRAFT'
|
||||
if (!this.postToStage.title) {
|
||||
this.postToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.postToStage.originalContent) {
|
||||
this.postToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
if (this.postToStage.id) {
|
||||
// Update the post
|
||||
postApi.update(this.postToStage.id, this.postToStage, autoSave).then(response => {
|
||||
postApi.update(this.postToStage.id, this.postToStage, false).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
if (updateSuccess) {
|
||||
updateSuccess()
|
||||
}
|
||||
this.$message.success('保存草稿成功!')
|
||||
})
|
||||
} else {
|
||||
// Create the post
|
||||
postApi.create(this.postToStage, autoSave).then(response => {
|
||||
postApi.create(this.postToStage, false).then(response => {
|
||||
this.$log.debug('Created post', response.data.data)
|
||||
if (createSuccess) {
|
||||
createSuccess()
|
||||
}
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.postToStage = response.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
savePost() {
|
||||
this.createOrUpdatePost(
|
||||
() => this.$message.success('文章创建成功'),
|
||||
() => this.$message.success('文章更新成功'),
|
||||
false
|
||||
)
|
||||
},
|
||||
autoSavePost() {
|
||||
if (this.postToStage.title != null && this.postToStage.originalContent != null) {
|
||||
this.createOrUpdatePost(null, null, true)
|
||||
}
|
||||
},
|
||||
toggleCategoryForm() {
|
||||
this.categoryForm = !this.categoryForm
|
||||
},
|
||||
handlePublishClick() {
|
||||
this.postToStage.status = 'PUBLISHED'
|
||||
this.savePost()
|
||||
},
|
||||
handleDraftClick() {
|
||||
this.postToStage.status = 'DRAFT'
|
||||
this.savePost()
|
||||
},
|
||||
handlerRemoveThumb() {
|
||||
this.postToStage.thumbnail = null
|
||||
},
|
||||
handlerCreateCategory() {
|
||||
categoryApi.create(this.categoryToCreate).then(response => {
|
||||
this.loadCategories()
|
||||
this.categoryToCreate = {}
|
||||
})
|
||||
},
|
||||
handleSelectPostThumb(data) {
|
||||
this.postToStage.thumbnail = encodeURI(data.path)
|
||||
this.thumDrawerVisible = false
|
||||
},
|
||||
autoSaveTimer() {
|
||||
if (this.timer == null) {
|
||||
this.timer = setInterval(() => {
|
||||
this.autoSavePost()
|
||||
}, 15000)
|
||||
}
|
||||
},
|
||||
pictureUploadHandle(pos, $file) {
|
||||
handleAttachmentUpload(pos, $file) {
|
||||
var formdata = new FormData()
|
||||
formdata.append('file', $file)
|
||||
attachmentApi.upload(formdata).then((response) => {
|
||||
attachmentApi.upload(formdata).then(response => {
|
||||
var responseObject = response.data
|
||||
|
||||
if (responseObject.status === 200) {
|
||||
var MavonEditor = this.$refs.md
|
||||
MavonEditor.$img2Url(pos, encodeURI(responseObject.data.path))
|
||||
this.$message.success('图片上传成功')
|
||||
var HaloEditor = this.$refs.md
|
||||
HaloEditor.$img2Url(pos, encodeURI(responseObject.data.path))
|
||||
this.$message.success('图片上传成功!')
|
||||
} else {
|
||||
this.$message.error('图片上传失败:' + responseObject.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
onChange(value, dateString) {
|
||||
this.postToStage.createTime = value.valueOf()
|
||||
handleShowPostSetting() {
|
||||
this.postSettingVisible = true
|
||||
},
|
||||
onOk(value) {
|
||||
this.postToStage.createTime = value.valueOf()
|
||||
handlePreview() {
|
||||
this.postToStage.status = 'DRAFT'
|
||||
if (!this.postToStage.title) {
|
||||
this.postToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.postToStage.originalContent) {
|
||||
this.postToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
if (this.postToStage.id) {
|
||||
// Update the post
|
||||
postApi.update(this.postToStage.id, this.postToStage, false).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
postApi.preview(this.postToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Create the post
|
||||
postApi.create(this.postToStage, false).then(response => {
|
||||
this.$log.debug('Created post', response.data.data)
|
||||
this.postToStage = response.data.data
|
||||
postApi.preview(this.postToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
// 关闭文章设置抽屉
|
||||
onPostSettingsClose() {
|
||||
this.postSettingVisible = false
|
||||
},
|
||||
onRefreshPostFromSetting(post) {
|
||||
this.postToStage = post
|
||||
},
|
||||
onRefreshTagIdsFromSetting(tagIds) {
|
||||
this.selectedTagIds = tagIds
|
||||
},
|
||||
onRefreshCategoryIdsFromSetting(categoryIds) {
|
||||
this.selectedCategoryIds = categoryIds
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.v-note-wrapper {
|
||||
z-index: 1000;
|
||||
min-height: 580px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<div class="page-header-index-wide">
|
||||
<a-card :bordered="false">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="48">
|
||||
|
@ -89,7 +92,7 @@
|
|||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="2"
|
||||
v-if="queryParam.status === 'PUBLISHED' || queryParam.status ==='DRAFT'"
|
||||
v-if="queryParam.status === 'PUBLISHED' || queryParam.status ==='DRAFT' || queryParam.status === 'INTIMATE'"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
|
@ -128,33 +131,68 @@
|
|||
:loading="postsLoading"
|
||||
:pagination="false"
|
||||
>
|
||||
<!-- ellipsis内嵌a标签后文本会被置空
|
||||
<ellipsis
|
||||
:length="25"
|
||||
tooltip
|
||||
slot="postTitle"
|
||||
slot-scope="text,record"
|
||||
>
|
||||
{{ text }}
|
||||
</ellipsis> -->
|
||||
<span
|
||||
slot="postTitle"
|
||||
slot-scope="text,record"
|
||||
class="post-title"
|
||||
style="max-width: 150px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
|
||||
>
|
||||
<a-icon
|
||||
type="pushpin"
|
||||
v-if="record.topPriority!=0"
|
||||
theme="twoTone"
|
||||
twoToneColor="red"
|
||||
style="margin-right: 3px;"
|
||||
/>
|
||||
<a
|
||||
v-if="record.status=='PUBLISHED'"
|
||||
:href="options.blog_url+'/archives/'+record.url"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<a-tooltip placement="topLeft" :title="'点击预览 '+text">{{ text }}</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:title="'点击访问【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="record.status == 'INTIMATE'"
|
||||
:href="options.blog_url+'/archives/'+record.url+'/password'"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:title="'点击访问【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="record.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
style="text-decoration: none;"
|
||||
@click="handlePreview(record.id)"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
:title="'点击预览【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
href="javascript:void(0);"
|
||||
style="text-decoration: none;"
|
||||
disabled
|
||||
>
|
||||
{{ text }}
|
||||
</a>
|
||||
</span>
|
||||
<span
|
||||
slot="status"
|
||||
slot-scope="statusProperty"
|
||||
>
|
||||
<a-badge :status="statusProperty.status" />
|
||||
{{ statusProperty.text }}
|
||||
<a-badge
|
||||
:status="statusProperty.status"
|
||||
:text="statusProperty.text"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
|
@ -165,6 +203,7 @@
|
|||
v-for="(category,index) in categoriesOfPost"
|
||||
:key="index"
|
||||
color="blue"
|
||||
style="margin-bottom: 8px"
|
||||
>{{ category.name }}</a-tag>
|
||||
</span>
|
||||
|
||||
|
@ -176,13 +215,45 @@
|
|||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
color="green"
|
||||
style="margin-bottom: 8px"
|
||||
>{{ tag.name }}</a-tag>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="commentCount"
|
||||
slot-scope="commentCount"
|
||||
>
|
||||
<a-badge
|
||||
:count="commentCount"
|
||||
:numberStyle="{backgroundColor: '#f38181'} "
|
||||
:showZero="true"
|
||||
:overflowCount="999"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="visits"
|
||||
slot-scope="visits"
|
||||
>
|
||||
<a-badge
|
||||
:count="visits"
|
||||
:numberStyle="{backgroundColor: '#00e0ff'} "
|
||||
:showZero="true"
|
||||
:overflowCount="9999"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="createTime"
|
||||
slot-scope="createTime"
|
||||
>{{ createTime | timeAgo }}</span>
|
||||
>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ createTime | moment }}
|
||||
</template>
|
||||
{{ createTime | timeAgo }}
|
||||
</a-tooltip>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="action"
|
||||
|
@ -191,7 +262,7 @@
|
|||
<a
|
||||
href="javascript:;"
|
||||
@click="handleEditClick(post)"
|
||||
v-if="post.status === 'PUBLISHED' || post.status === 'DRAFT'"
|
||||
v-if="post.status === 'PUBLISHED' || post.status === 'DRAFT' || post.status === 'INTIMATE'"
|
||||
>编辑</a>
|
||||
<a-popconfirm
|
||||
:title="'你确定要发布【' + post.title + '】文章?'"
|
||||
|
@ -210,7 +281,7 @@
|
|||
@confirm="handleEditStatusClick(post.id,'RECYCLE')"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
v-if="post.status === 'PUBLISHED' || post.status === 'DRAFT'"
|
||||
v-if="post.status === 'PUBLISHED' || post.status === 'DRAFT' || post.status === 'INTIMATE'"
|
||||
>
|
||||
<a href="javascript:;">回收站</a>
|
||||
</a-popconfirm>
|
||||
|
@ -229,7 +300,7 @@
|
|||
|
||||
<a
|
||||
href="javascript:;"
|
||||
@click="handlePostSettingsDrawer(post)"
|
||||
@click="handleShowPostSettings(post)"
|
||||
>设置</a>
|
||||
</span>
|
||||
</a-table>
|
||||
|
@ -246,164 +317,32 @@
|
|||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-drawer
|
||||
title="文章设置"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
placement="right"
|
||||
closable
|
||||
@close="onPostSettingsClose"
|
||||
<PostSetting
|
||||
:post="selectedPost"
|
||||
:tagIds="selectedTagIds"
|
||||
:categoryIds="selectedCategoryIds"
|
||||
:needTitle="true"
|
||||
:saveDraftButton="false"
|
||||
:savePublishButton="false"
|
||||
:saveButton="true"
|
||||
:visible="postSettingVisible"
|
||||
>
|
||||
<div class="post-setting-drawer-content">
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">基本设置</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="文章标题:">
|
||||
<a-input v-model="selectedPost.title" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="文章路径:"
|
||||
:help="options.blog_url+'/archives/' + (selectedPost.url ? selectedPost.url : '{auto_generate}')"
|
||||
>
|
||||
<a-input v-model="selectedPost.url" />
|
||||
</a-form-item>
|
||||
<a-form-item label="开启评论:">
|
||||
<a-radio-group
|
||||
v-model="selectedPost.disallowComment"
|
||||
:defaultValue="false"
|
||||
>
|
||||
<a-radio :value="false">开启</a-radio>
|
||||
<a-radio :value="true">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">分类目录</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<category-tree
|
||||
v-model="selectedCategoryIds"
|
||||
:categories="categories"
|
||||
@close="onPostSettingsClose"
|
||||
@onRefreshPost="onRefreshPostFromSetting"
|
||||
@onRefreshTagIds="onRefreshTagIdsFromSetting"
|
||||
@onRefreshCategoryIds="onRefreshCategoryIdsFromSetting"
|
||||
/>
|
||||
<div>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item v-if="categoryForm">
|
||||
<category-select-tree
|
||||
:categories="categories"
|
||||
v-model="categoryToCreate.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryForm">
|
||||
<a-input
|
||||
placeholder="分类名称"
|
||||
v-model="categoryToCreate.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryForm">
|
||||
<a-input
|
||||
placeholder="分类路径"
|
||||
v-model="categoryToCreate.slugNames"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="marginRight: 8px"
|
||||
v-if="categoryForm"
|
||||
@click="handlerCreateCategory"
|
||||
>保存</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="marginRight: 8px"
|
||||
v-if="!categoryForm"
|
||||
@click="toggleCategoryForm"
|
||||
>新增</a-button>
|
||||
<a-button
|
||||
v-if="categoryForm"
|
||||
@click="toggleCategoryForm"
|
||||
>取消</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">标签</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<TagSelect v-model="selectedTagIds" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">摘要</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="selectedPost.summary"
|
||||
placeholder="不填写则会自动生成"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">缩略图</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<div class="post-thum">
|
||||
<img
|
||||
class="img"
|
||||
:src="selectedPost.thumbnail || '//i.loli.net/2019/05/05/5ccf007c0a01d.png'"
|
||||
@click="()=>this.thumDrawerVisible=true"
|
||||
>
|
||||
<a-button
|
||||
class="post-thum-remove"
|
||||
type="dashed"
|
||||
@click="handlerRemoveThumb"
|
||||
>移除</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
<AttachmentSelectDrawer
|
||||
v-model="thumDrawerVisible"
|
||||
@listenToSelect="handleSelectPostThumb"
|
||||
:drawerWidth="460"
|
||||
/>
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
@click="handleSavePostSettingsClick"
|
||||
type="primary"
|
||||
>保存</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import categoryApi from '@/api/category'
|
||||
import postApi from '@/api/post'
|
||||
import optionApi from '@/api/option'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import PostSetting from './components/PostSetting'
|
||||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import TagSelect from './components/TagSelect'
|
||||
import CategoryTree from './components/CategoryTree'
|
||||
import { mapGetters } from 'vuex'
|
||||
import categoryApi from '@/api/category'
|
||||
import postApi from '@/api/post'
|
||||
const columns = [
|
||||
{
|
||||
title: '标题',
|
||||
|
@ -415,10 +354,11 @@ const columns = [
|
|||
title: '状态',
|
||||
className: 'status',
|
||||
dataIndex: 'statusProperty',
|
||||
width: '100px',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
title: '分类目录',
|
||||
title: '分类',
|
||||
dataIndex: 'categories',
|
||||
scopedSlots: { customRender: 'categories' }
|
||||
},
|
||||
|
@ -428,16 +368,21 @@ const columns = [
|
|||
scopedSlots: { customRender: 'tags' }
|
||||
},
|
||||
{
|
||||
title: '评论量',
|
||||
dataIndex: 'commentCount'
|
||||
title: '评论',
|
||||
width: '70px',
|
||||
dataIndex: 'commentCount',
|
||||
scopedSlots: { customRender: 'commentCount' }
|
||||
},
|
||||
{
|
||||
title: '访问量',
|
||||
dataIndex: 'visits'
|
||||
title: '访问',
|
||||
width: '70px',
|
||||
dataIndex: 'visits',
|
||||
scopedSlots: { customRender: 'visits' }
|
||||
},
|
||||
{
|
||||
title: '发布时间',
|
||||
dataIndex: 'createTime',
|
||||
width: '170px',
|
||||
scopedSlots: { customRender: 'createTime' }
|
||||
},
|
||||
{
|
||||
|
@ -451,7 +396,8 @@ export default {
|
|||
components: {
|
||||
AttachmentSelectDrawer,
|
||||
TagSelect,
|
||||
CategoryTree
|
||||
CategoryTree,
|
||||
PostSetting
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
|
@ -478,14 +424,9 @@ export default {
|
|||
posts: [],
|
||||
postsLoading: false,
|
||||
postSettingVisible: false,
|
||||
thumDrawerVisible: false,
|
||||
selectedPost: {},
|
||||
selectedCategoryIds: [],
|
||||
selectedTagIds: [],
|
||||
categoryForm: false,
|
||||
categoryToCreate: {},
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
selectedCategoryIds: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -494,12 +435,23 @@ export default {
|
|||
post.statusProperty = this.postStatus[post.status]
|
||||
return post
|
||||
})
|
||||
}
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
created() {
|
||||
this.loadCategories()
|
||||
this.loadPosts()
|
||||
this.loadOptions()
|
||||
this.loadCategories()
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.postSettingVisible) {
|
||||
this.postSettingVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.postSettingVisible) {
|
||||
this.postSettingVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
methods: {
|
||||
loadPosts() {
|
||||
|
@ -519,11 +471,6 @@ export default {
|
|||
this.categories = response.data.data
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
handleEditClick(post) {
|
||||
this.$router.push({ name: 'PostEdit', query: { postId: post.id } })
|
||||
},
|
||||
|
@ -553,6 +500,7 @@ export default {
|
|||
},
|
||||
handleQuery() {
|
||||
this.queryParam.page = 0
|
||||
this.pagination.current = 1
|
||||
this.loadPosts()
|
||||
},
|
||||
handleEditStatusClick(postId, status) {
|
||||
|
@ -609,61 +557,34 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
// 打开文章设置抽屉
|
||||
handlePostSettingsDrawer(post) {
|
||||
this.postSettingVisible = true
|
||||
handleShowPostSettings(post) {
|
||||
postApi.get(post.id).then(response => {
|
||||
const post = response.data.data
|
||||
this.selectedPost = post
|
||||
this.selectedTagIds = post.tagIds
|
||||
this.selectedCategoryIds = post.categoryIds
|
||||
this.selectedPost = response.data.data
|
||||
this.selectedTagIds = this.selectedPost.tagIds
|
||||
this.selectedCategoryIds = this.selectedPost.categoryIds
|
||||
this.postSettingVisible = true
|
||||
})
|
||||
},
|
||||
handleSelectPostThumb(data) {
|
||||
this.selectedPost.thumbnail = encodeURI(data.path)
|
||||
this.thumDrawerVisible = false
|
||||
},
|
||||
handlerRemoveThumb() {
|
||||
this.selectedPost.thumbnail = null
|
||||
},
|
||||
// 保存文章设置
|
||||
handleSavePostSettingsClick() {
|
||||
this.selectedPost.categoryIds = this.selectedCategoryIds
|
||||
this.selectedPost.tagIds = this.selectedTagIds
|
||||
postApi.update(this.selectedPost.id, this.selectedPost, false).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
this.loadPosts()
|
||||
this.$message.success('文章更新成功')
|
||||
})
|
||||
},
|
||||
toggleCategoryForm() {
|
||||
this.categoryForm = !this.categoryForm
|
||||
},
|
||||
handlerCreateCategory() {
|
||||
categoryApi.create(this.categoryToCreate).then(response => {
|
||||
this.loadCategories()
|
||||
this.categoryToCreate = {}
|
||||
handlePreview(postId) {
|
||||
postApi.preview(postId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
// 关闭文章设置抽屉
|
||||
onPostSettingsClose() {
|
||||
this.postSettingVisible = false
|
||||
this.selectedPost = {}
|
||||
this.selectedTagIds = []
|
||||
this.selectedCategoryIds = []
|
||||
this.loadPosts()
|
||||
},
|
||||
onRefreshPostFromSetting(post) {
|
||||
this.selectedPost = post
|
||||
},
|
||||
onRefreshTagIdsFromSetting(tagIds) {
|
||||
this.selectedTagIds = tagIds
|
||||
},
|
||||
onRefreshCategoryIdsFromSetting(categoryIds) {
|
||||
this.selectedCategoryIds = categoryIds
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.post-title {
|
||||
max-width: 150px;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card :title="title">
|
||||
<a-card :title="title" :bodyStyle="{ padding: '16px' }">
|
||||
<a-form layout="horizontal">
|
||||
<a-form-item
|
||||
label="名称:"
|
||||
|
@ -64,7 +64,7 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card title="所有标签">
|
||||
<a-card title="所有标签" :bodyStyle="{ padding: '16px' }">
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
v-for="tag in tags"
|
||||
|
@ -132,6 +132,13 @@ export default {
|
|||
})
|
||||
},
|
||||
createOrUpdateTag() {
|
||||
if (!this.tagToCreate.name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '标签名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.tagToCreate.id) {
|
||||
tagApi.update(this.tagToCreate.id, this.tagToCreate).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
|
|
|
@ -58,6 +58,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
:checkedKeys="categoryIds"
|
||||
@check="onCheck"
|
||||
>
|
||||
<span
|
||||
slot="title0010"
|
||||
style="color: #1890ff"
|
||||
>sss</span>
|
||||
</a-tree>
|
||||
</template>
|
||||
|
||||
|
@ -56,6 +52,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,435 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="文章设置"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
placement="right"
|
||||
closable
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
:visible="visible"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="settingLoading"
|
||||
:paragraph="{ rows: 24 }"
|
||||
>
|
||||
<div class="post-setting-drawer-content">
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">基本设置</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="文章标题:"
|
||||
v-if="needTitle"
|
||||
>
|
||||
<a-input v-model="selectedPost.title" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="文章路径:"
|
||||
:help="options.blog_url+'/archives/' + (selectedPost.url ? selectedPost.url : '{auto_generate}')"
|
||||
>
|
||||
<a-input v-model="selectedPost.url" />
|
||||
</a-form-item>
|
||||
<a-form-item label="访问密码:">
|
||||
<a-input
|
||||
v-model="selectedPost.password"
|
||||
v-if="passwordVisible"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click="togglePasswordVisible"
|
||||
>
|
||||
<a-icon type="eye-invisible" />
|
||||
</a>
|
||||
</a-input>
|
||||
<a-input
|
||||
type="password"
|
||||
v-model="selectedPost.password"
|
||||
v-else
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click="togglePasswordVisible"
|
||||
>
|
||||
<a-icon type="eye" />
|
||||
</a>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="发表时间:">
|
||||
<a-date-picker
|
||||
showTime
|
||||
:defaultValue="pickerDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择文章发表时间"
|
||||
@change="onPostDateChange"
|
||||
@ok="onPostDateOk"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="开启评论:">
|
||||
<a-radio-group
|
||||
v-model="selectedPost.disallowComment"
|
||||
:defaultValue="false"
|
||||
>
|
||||
<a-radio :value="false">开启</a-radio>
|
||||
<a-radio :value="true">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否置顶:">
|
||||
<a-radio-group
|
||||
v-model="selectedPost.topPriority"
|
||||
:defaultValue="0"
|
||||
>
|
||||
<a-radio :value="1">是</a-radio>
|
||||
<a-radio :value="0">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">分类目录</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<category-tree
|
||||
v-model="selectedCategoryIds"
|
||||
:categories="categories"
|
||||
/>
|
||||
<div>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<category-select-tree
|
||||
:categories="categories"
|
||||
v-model="categoryToCreate.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<a-input
|
||||
placeholder="分类名称"
|
||||
v-model="categoryToCreate.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="categoryFormVisible">
|
||||
<a-input
|
||||
placeholder="分类路径"
|
||||
v-model="categoryToCreate.slugNames"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="marginRight: 8px"
|
||||
v-if="categoryFormVisible"
|
||||
@click="handlerCreateCategory"
|
||||
>保存</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="marginRight: 8px"
|
||||
v-if="!categoryFormVisible"
|
||||
@click="toggleCategoryForm"
|
||||
>新增</a-button>
|
||||
<a-button
|
||||
v-if="categoryFormVisible"
|
||||
@click="toggleCategoryForm"
|
||||
>取消</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">标签</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<TagSelect v-model="selectedTagIds" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">摘要</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="selectedPost.summary"
|
||||
placeholder="不填写则会自动生成"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">缩略图</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<div class="post-thumb">
|
||||
<img
|
||||
class="img"
|
||||
:src="selectedPost.thumbnail || '//i.loli.net/2019/05/05/5ccf007c0a01d.png'"
|
||||
@click="()=>this.thumbDrawerVisible=true"
|
||||
>
|
||||
<a-button
|
||||
class="post-thumb-remove"
|
||||
type="dashed"
|
||||
@click="handlerRemoveThumb"
|
||||
>移除</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
</a-skeleton>
|
||||
<AttachmentSelectDrawer
|
||||
v-model="thumbDrawerVisible"
|
||||
@listenToSelect="handleSelectPostThumb"
|
||||
:drawerWidth="460"
|
||||
/>
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
style="marginRight: 8px"
|
||||
@click="handleDraftClick"
|
||||
v-if="saveDraftButton"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePublishClick"
|
||||
type="primary"
|
||||
v-if="savePublishButton"
|
||||
>发布</a-button>
|
||||
<a-button
|
||||
@click="handlePublishClick"
|
||||
type="primary"
|
||||
v-if="saveButton"
|
||||
>保存</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import moment from 'moment'
|
||||
import CategoryTree from './CategoryTree'
|
||||
import CategorySelectTree from './CategorySelectTree'
|
||||
import TagSelect from './TagSelect'
|
||||
import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer'
|
||||
import { mapGetters } from 'vuex'
|
||||
import categoryApi from '@/api/category'
|
||||
import postApi from '@/api/post'
|
||||
export default {
|
||||
name: 'PostSetting',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
CategoryTree,
|
||||
CategorySelectTree,
|
||||
TagSelect,
|
||||
AttachmentSelectDrawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thumbDrawerVisible: false,
|
||||
categoryFormVisible: false,
|
||||
settingLoading: true,
|
||||
passwordVisible: false,
|
||||
selectedPost: this.post,
|
||||
selectedTagIds: this.tagIds,
|
||||
selectedCategoryIds: this.categoryIds,
|
||||
categories: [],
|
||||
categoryToCreate: {}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
post: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
tagIds: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
categoryIds: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
needTitle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
saveDraftButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
savePublishButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
saveButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
this.loadCategories()
|
||||
},
|
||||
watch: {
|
||||
post(val) {
|
||||
this.selectedPost = val
|
||||
},
|
||||
selectedPost(val) {
|
||||
this.$emit('onRefreshPost', val)
|
||||
},
|
||||
tagIds(val) {
|
||||
this.selectedTagIds = val
|
||||
},
|
||||
selectedTagIds(val) {
|
||||
this.$emit('onRefreshTagIds', val)
|
||||
},
|
||||
categoryIds(val) {
|
||||
this.selectedCategoryIds = val
|
||||
},
|
||||
selectedCategoryIds(val) {
|
||||
this.$emit('onRefreshCategoryIds', val)
|
||||
},
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pickerDefaultValue() {
|
||||
if (this.selectedPost.createTime) {
|
||||
var date = new Date(this.selectedPost.createTime)
|
||||
return moment(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
loadSkeleton() {
|
||||
this.settingLoading = true
|
||||
setTimeout(() => {
|
||||
this.settingLoading = false
|
||||
}, 500)
|
||||
},
|
||||
loadCategories() {
|
||||
categoryApi.listAll().then(response => {
|
||||
this.categories = response.data.data
|
||||
})
|
||||
},
|
||||
handleSelectPostThumb(data) {
|
||||
this.selectedPost.thumbnail = encodeURI(data.path)
|
||||
this.thumbDrawerVisible = false
|
||||
},
|
||||
handlerRemoveThumb() {
|
||||
this.selectedPost.thumbnail = null
|
||||
},
|
||||
handlerCreateCategory() {
|
||||
if (!this.categoryToCreate.name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '分类名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
categoryApi.create(this.categoryToCreate).then(response => {
|
||||
this.loadCategories()
|
||||
this.categoryToCreate = {}
|
||||
this.toggleCategoryForm()
|
||||
})
|
||||
},
|
||||
toggleCategoryForm() {
|
||||
this.categoryFormVisible = !this.categoryFormVisible
|
||||
},
|
||||
handleDraftClick() {
|
||||
this.selectedPost.status = 'DRAFT'
|
||||
this.savePost()
|
||||
},
|
||||
handlePublishClick() {
|
||||
this.selectedPost.status = 'PUBLISHED'
|
||||
this.savePost()
|
||||
},
|
||||
savePost() {
|
||||
this.createOrUpdatePost(
|
||||
() => this.$message.success('文章发布成功'),
|
||||
() => this.$message.success('文章发布成功'),
|
||||
false
|
||||
)
|
||||
},
|
||||
createOrUpdatePost(createSuccess, updateSuccess, autoSave) {
|
||||
if (!this.selectedPost.title) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '文章标题不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.selectedPost.originalContent) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '文章内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
// Set category ids
|
||||
this.selectedPost.categoryIds = this.selectedCategoryIds
|
||||
// Set tag ids
|
||||
this.selectedPost.tagIds = this.selectedTagIds
|
||||
|
||||
if (this.selectedPost.id) {
|
||||
// Update the post
|
||||
postApi.update(this.selectedPost.id, this.selectedPost, autoSave).then(response => {
|
||||
this.$log.debug('Updated post', response.data.data)
|
||||
if (updateSuccess) {
|
||||
updateSuccess()
|
||||
this.$router.push({ name: 'PostList' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Create the post
|
||||
postApi.create(this.selectedPost, autoSave).then(response => {
|
||||
this.$log.debug('Created post', response.data.data)
|
||||
if (createSuccess) {
|
||||
createSuccess()
|
||||
this.$router.push({ name: 'PostList' })
|
||||
}
|
||||
this.selectedPost = response.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
togglePasswordVisible() {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
this.passwordVisible = false
|
||||
},
|
||||
onPostDateChange(value, dateString) {
|
||||
this.selectedPost.createTime = value.valueOf()
|
||||
},
|
||||
onPostDateOk(value) {
|
||||
this.selectedPost.createTime = value.valueOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -105,6 +105,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
@ -11,122 +11,42 @@
|
|||
/>
|
||||
</div>
|
||||
<div id="editor">
|
||||
<mavon-editor
|
||||
<halo-editor
|
||||
ref="md"
|
||||
v-model="sheetToStage.originalContent"
|
||||
:boxShadow="false"
|
||||
:toolbars="toolbars"
|
||||
:ishljs="true"
|
||||
:autofocus="false"
|
||||
@imgAdd="pictureUploadHandle"
|
||||
@imgAdd="handleAttachmentUpload"
|
||||
@keydown.ctrl.83.native="handleSaveDraft"
|
||||
@keydown.meta.83.native="handleSaveDraft"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col
|
||||
:xl="24"
|
||||
:lg="24"
|
||||
:md="24"
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
>
|
||||
<a-drawer
|
||||
title="页面设置"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
:closable="true"
|
||||
@close="()=>this.sheetSettingVisible = false"
|
||||
:visible="sheetSettingVisible"
|
||||
>
|
||||
<div class="post-setting-drawer-content">
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">基本设置</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="页面路径:"
|
||||
:help="options.blog_url+'/s/'+ (sheetToStage.url ? sheetToStage.url : '{auto_generate}')"
|
||||
>
|
||||
<a-input v-model="sheetToStage.url" />
|
||||
</a-form-item>
|
||||
<a-form-item label="发表时间:">
|
||||
<a-date-picker
|
||||
showTime
|
||||
:defaultValue="pickerDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="Select Publish Time"
|
||||
@change="onChange"
|
||||
@ok="onOk"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="开启评论:">
|
||||
<a-radio-group
|
||||
v-model="sheetToStage.disallowComment"
|
||||
:defaultValue="false"
|
||||
>
|
||||
<a-radio :value="false">开启</a-radio>
|
||||
<a-radio :value="true">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义模板:">
|
||||
<a-select v-model="sheetToStage.template">
|
||||
<a-select-option
|
||||
key=""
|
||||
value=""
|
||||
>无</a-select-option>
|
||||
<a-select-option
|
||||
v-for="tpl in customTpls"
|
||||
:key="tpl"
|
||||
:value="tpl"
|
||||
>{{ tpl }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">缩略图</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<div class="sheet-thum">
|
||||
<img
|
||||
class="img"
|
||||
:src="sheetToStage.thumbnail || '//i.loli.net/2019/05/05/5ccf007c0a01d.png'"
|
||||
@click="()=>this.thumDrawerVisible = true"
|
||||
>
|
||||
<a-button
|
||||
class="sheet-thum-remove"
|
||||
type="dashed"
|
||||
@click="handlerRemoveThumb"
|
||||
>移除</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
<AttachmentSelectDrawer
|
||||
v-model="thumDrawerVisible"
|
||||
@listenToSelect="handleSelectSheetThumb"
|
||||
:drawerWidth="460"
|
||||
/>
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
style="marginRight: 8px"
|
||||
@click="handleDraftClick"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handlePublishClick"
|
||||
>发布</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<SheetSetting
|
||||
:sheet="sheetToStage"
|
||||
:visible="sheetSettingVisible"
|
||||
@close="onSheetSettingsClose"
|
||||
@onRefreshSheet="onRefreshSheetFromSetting"
|
||||
/>
|
||||
|
||||
<AttachmentDrawer v-model="attachmentDrawerVisible" />
|
||||
<footer-tool-bar :style="{ width: isSideMenu() && isDesktop() ? `calc(100% - ${sidebarOpened ? 256 : 80}px)` : '100%'}">
|
||||
<a-button
|
||||
type="danger"
|
||||
@click="handleSaveDraft"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
@click="handlePreview"
|
||||
style="margin-left: 8px;"
|
||||
>预览</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="()=>this.sheetSettingVisible = true"
|
||||
style="margin-left: 8px;"
|
||||
@click="handleShowSheetSetting"
|
||||
>发布</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
|
@ -138,24 +58,23 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mavonEditor } from 'mavon-editor'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { toolbars } from '@/core/const'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
import sheetApi from '@/api/sheet'
|
||||
import themeApi from '@/api/theme'
|
||||
import optionApi from '@/api/option'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
import { toolbars } from '@/core/const'
|
||||
import SheetSetting from './components/SheetSetting'
|
||||
import AttachmentDrawer from '../attachment/components/AttachmentDrawer'
|
||||
import FooterToolBar from '@/components/FooterToolbar'
|
||||
import { haloEditor } from 'halo-editor'
|
||||
import 'halo-editor/dist/css/index.css'
|
||||
import sheetApi from '@/api/sheet'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
export default {
|
||||
components: {
|
||||
mavonEditor,
|
||||
haloEditor,
|
||||
FooterToolBar,
|
||||
AttachmentDrawer,
|
||||
AttachmentSelectDrawer
|
||||
SheetSetting
|
||||
},
|
||||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
|
@ -167,34 +86,10 @@ export default {
|
|||
xs: { span: 24 }
|
||||
},
|
||||
attachmentDrawerVisible: false,
|
||||
thumDrawerVisible: false,
|
||||
sheetSettingVisible: false,
|
||||
customTpls: [],
|
||||
sheetToStage: {},
|
||||
timer: null,
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
sheetToStage: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCustomTpls()
|
||||
this.loadOptions()
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
this.autoSaveTimer()
|
||||
},
|
||||
destroyed: function() {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.timer !== null) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
// Auto save the sheet
|
||||
this.autoSaveSheet()
|
||||
next()
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
// Get sheetId id from query
|
||||
const sheetId = to.query.sheetId
|
||||
|
@ -208,116 +103,97 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.sheetSettingVisible) {
|
||||
this.sheetSettingVisible = false
|
||||
}
|
||||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.sheetSettingVisible) {
|
||||
this.sheetSettingVisible = false
|
||||
}
|
||||
if (this.attachmentDrawerVisible) {
|
||||
this.attachmentDrawerVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
computed: {
|
||||
pickerDefaultValue() {
|
||||
if (this.sheetToStage.createTime) {
|
||||
var date = new Date(this.sheetToStage.createTime)
|
||||
return moment(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
loadCustomTpls() {
|
||||
themeApi.customTpls().then(response => {
|
||||
this.customTpls = response.data.data
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
handlePublishClick() {
|
||||
this.sheetToStage.status = 'PUBLISHED'
|
||||
this.saveSheet()
|
||||
},
|
||||
handleDraftClick() {
|
||||
handleSaveDraft() {
|
||||
this.sheetToStage.status = 'DRAFT'
|
||||
this.saveSheet()
|
||||
},
|
||||
handlerRemoveThumb() {
|
||||
this.sheetToStage.thumbnail = null
|
||||
},
|
||||
createOrUpdateSheet(createSuccess, updateSuccess, autoSave) {
|
||||
if (this.sheetToStage.id) {
|
||||
sheetApi.update(this.sheetToStage.id, this.sheetToStage, autoSave).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
if (updateSuccess) {
|
||||
updateSuccess()
|
||||
if (!this.sheetToStage.title) {
|
||||
this.sheetToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.sheetToStage.originalContent) {
|
||||
this.sheetToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
if (this.sheetToStage.id) {
|
||||
sheetApi.update(this.sheetToStage.id, this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
this.$message.success('保存草稿成功!')
|
||||
})
|
||||
} else {
|
||||
sheetApi.create(this.sheetToStage, autoSave).then(response => {
|
||||
sheetApi.create(this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Created sheet', response.data.data)
|
||||
if (createSuccess) {
|
||||
createSuccess()
|
||||
}
|
||||
this.$message.success('保存草稿成功!')
|
||||
this.sheetToStage = response.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
saveSheet() {
|
||||
this.createOrUpdateSheet(
|
||||
() => this.$message.success('页面创建成功!'),
|
||||
() => this.$message.success('页面更新成功!'),
|
||||
false
|
||||
)
|
||||
},
|
||||
autoSaveSheet() {
|
||||
if (this.sheetToStage.title != null && this.sheetToStage.originalContent != null) {
|
||||
this.createOrUpdateSheet(null, null, true)
|
||||
}
|
||||
},
|
||||
handleSelectSheetThumb(data) {
|
||||
this.sheetToStage.thumbnail = encodeURI(data.path)
|
||||
this.thumDrawerVisible = false
|
||||
},
|
||||
autoSaveTimer() {
|
||||
if (this.timer == null) {
|
||||
this.timer = setInterval(() => {
|
||||
this.autoSaveSheet()
|
||||
}, 15000)
|
||||
}
|
||||
},
|
||||
pictureUploadHandle(pos, $file) {
|
||||
handleAttachmentUpload(pos, $file) {
|
||||
var formdata = new FormData()
|
||||
formdata.append('file', $file)
|
||||
attachmentApi.upload(formdata).then(response => {
|
||||
var responseObject = response.data
|
||||
|
||||
if (responseObject.status === 200) {
|
||||
var MavonEditor = this.$refs.md
|
||||
MavonEditor.$img2Url(pos, encodeURI(responseObject.data.path))
|
||||
this.$message.success('图片上传成功')
|
||||
var HaloEditor = this.$refs.md
|
||||
HaloEditor.$img2Url(pos, encodeURI(responseObject.data.path))
|
||||
this.$message.success('图片上传成功!')
|
||||
} else {
|
||||
this.$message.error('图片上传失败:' + responseObject.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
onChange(value, dateString) {
|
||||
this.sheetToStage.createTime = value.valueOf()
|
||||
handleShowSheetSetting() {
|
||||
this.sheetSettingVisible = true
|
||||
},
|
||||
onOk(value) {
|
||||
this.sheetToStage.createTime = value.valueOf()
|
||||
handlePreview() {
|
||||
this.sheetToStage.status = 'DRAFT'
|
||||
if (!this.sheetToStage.title) {
|
||||
this.sheetToStage.title = moment(new Date()).format('YYYY-MM-DD-HH-mm-ss')
|
||||
}
|
||||
if (!this.sheetToStage.originalContent) {
|
||||
this.sheetToStage.originalContent = '开始编辑...'
|
||||
}
|
||||
if (this.sheetToStage.id) {
|
||||
sheetApi.update(this.sheetToStage.id, this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
sheetApi.preview(this.sheetToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
})
|
||||
} else {
|
||||
sheetApi.create(this.sheetToStage, false).then(response => {
|
||||
this.$log.debug('Created sheet', response.data.data)
|
||||
this.sheetToStage = response.data.data
|
||||
sheetApi.preview(this.sheetToStage.id).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
onSheetSettingsClose() {
|
||||
this.sheetSettingVisible = false
|
||||
},
|
||||
onRefreshSheetFromSetting(sheet) {
|
||||
this.sheetToStage = sheet
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.v-note-wrapper {
|
||||
z-index: 1000;
|
||||
min-height: 580px;
|
||||
}
|
||||
|
||||
.sheet-thum {
|
||||
.img {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.sheet-thum-remove {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,26 +9,6 @@
|
|||
<a-icon type="pushpin" />内置页面
|
||||
</span>
|
||||
|
||||
<!-- TODO 移动端展示 -->
|
||||
<!-- <a-collapse
|
||||
:bordered="false"
|
||||
v-if="isMobile()"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(item,index) in internalSheets"
|
||||
:key="index"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="header"
|
||||
> {{ item.name }} </a>
|
||||
<div>
|
||||
访问路径:{{ item.url }}
|
||||
操作:{{ item.url }}
|
||||
</div>
|
||||
</a-collapse-panel>
|
||||
</a-collapse> -->
|
||||
|
||||
<a-table
|
||||
:columns="internalColumns"
|
||||
:dataSource="internalSheets"
|
||||
|
@ -95,33 +75,90 @@
|
|||
:columns="customColumns"
|
||||
:dataSource="formattedSheets"
|
||||
:pagination="false"
|
||||
:loading="sheetsLoading"
|
||||
>
|
||||
<span
|
||||
slot="sheetTitle"
|
||||
slot-scope="text,record"
|
||||
class="sheet-title"
|
||||
style="max-width: 150px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
|
||||
>
|
||||
<a
|
||||
v-if="record.status=='PUBLISHED'"
|
||||
:href="options.blog_url+'/s/'+record.url"
|
||||
target="_blank"
|
||||
style="text-decoration: none;"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="top"
|
||||
:title="'点击访问【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else-if="record.status=='DRAFT'"
|
||||
href="javascript:void(0)"
|
||||
style="text-decoration: none;"
|
||||
@click="handlePreview(record.id)"
|
||||
>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
:title="'点击预览 '+text"
|
||||
:title="'点击预览【'+text+'】'"
|
||||
>{{ text }}</a-tooltip>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
href="javascript:void(0);"
|
||||
style="text-decoration: none;"
|
||||
disabled
|
||||
>
|
||||
{{ text }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="status"
|
||||
slot-scope="statusProperty"
|
||||
>
|
||||
<a-badge :status="statusProperty.status" />
|
||||
{{ statusProperty.text }}
|
||||
<a-badge
|
||||
:status="statusProperty.status"
|
||||
:text="statusProperty.text"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="commentCount"
|
||||
slot-scope="commentCount"
|
||||
>
|
||||
<a-badge
|
||||
:count="commentCount"
|
||||
:numberStyle="{backgroundColor: '#f38181'} "
|
||||
:showZero="true"
|
||||
:overflowCount="999"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="visits"
|
||||
slot-scope="visits"
|
||||
>
|
||||
<a-badge
|
||||
:count="visits"
|
||||
:numberStyle="{backgroundColor: '#00e0ff'} "
|
||||
:showZero="true"
|
||||
:overflowCount="9999"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="createTime"
|
||||
slot-scope="createTime"
|
||||
>{{ createTime | timeAgo }}</span>
|
||||
>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ createTime | moment }}
|
||||
</template>
|
||||
{{ createTime | timeAgo }}
|
||||
</a-tooltip>
|
||||
</span>
|
||||
|
||||
<span
|
||||
slot="action"
|
||||
|
@ -172,6 +209,12 @@
|
|||
>更多</a>
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="1">
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleShowSheetSettings(sheet)"
|
||||
>设置</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="2">
|
||||
<a-popconfirm
|
||||
:title="'你确定要添加【' + sheet.title + '】到菜单?'"
|
||||
@confirm="handleSheetToMenu(sheet)"
|
||||
|
@ -190,13 +233,23 @@
|
|||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<SheetSetting
|
||||
:sheet="selectedSheet"
|
||||
:visible="sheetSettingVisible"
|
||||
:needTitle="true"
|
||||
@close="onSheetSettingsClose"
|
||||
@onRefreshSheet="onRefreshSheetFromSetting"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import SheetSetting from './components/SheetSetting'
|
||||
import sheetApi from '@/api/sheet'
|
||||
import optionApi from '@/api/option'
|
||||
import menuApi from '@/api/menu'
|
||||
|
||||
const internalColumns = [
|
||||
|
@ -234,11 +287,13 @@ const customColumns = [
|
|||
},
|
||||
{
|
||||
title: '评论量',
|
||||
dataIndex: 'commentCount'
|
||||
dataIndex: 'commentCount',
|
||||
scopedSlots: { customRender: 'commentCount' }
|
||||
},
|
||||
{
|
||||
title: '访问量',
|
||||
dataIndex: 'visits'
|
||||
dataIndex: 'visits',
|
||||
scopedSlots: { customRender: 'visits' }
|
||||
},
|
||||
{
|
||||
title: '发布时间',
|
||||
|
@ -253,16 +308,20 @@ const customColumns = [
|
|||
]
|
||||
export default {
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
SheetSetting
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sheetsLoading: false,
|
||||
sheetStatus: sheetApi.sheetStatus,
|
||||
internalColumns,
|
||||
customColumns,
|
||||
selectedSheet: {},
|
||||
sheetSettingVisible: false,
|
||||
internalSheets: [],
|
||||
sheets: [],
|
||||
options: [],
|
||||
menu: {},
|
||||
keys: ['blog_url']
|
||||
menu: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -271,17 +330,30 @@ export default {
|
|||
sheet.statusProperty = this.sheetStatus[sheet.status]
|
||||
return sheet
|
||||
})
|
||||
}
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
created() {
|
||||
this.loadSheets()
|
||||
this.loadInternalSheets()
|
||||
this.loadOptions()
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.sheetSettingVisible) {
|
||||
this.sheetSettingVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.sheetSettingVisible) {
|
||||
this.sheetSettingVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
methods: {
|
||||
loadSheets() {
|
||||
this.sheetsLoading = true
|
||||
sheetApi.list().then(response => {
|
||||
this.sheets = response.data.data.content
|
||||
this.sheetsLoading = false
|
||||
})
|
||||
},
|
||||
loadInternalSheets() {
|
||||
|
@ -289,11 +361,6 @@ export default {
|
|||
this.internalSheets = response.data.data
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
handleEditClick(sheet) {
|
||||
this.$router.push({ name: 'SheetEdit', query: { sheetId: sheet.id } })
|
||||
},
|
||||
|
@ -316,19 +383,26 @@ export default {
|
|||
this.$message.success('添加到菜单成功!')
|
||||
this.menu = {}
|
||||
})
|
||||
},
|
||||
handleShowSheetSettings(sheet) {
|
||||
sheetApi.get(sheet.id).then(response => {
|
||||
this.selectedSheet = response.data.data
|
||||
this.sheetSettingVisible = true
|
||||
})
|
||||
},
|
||||
handlePreview(sheetId) {
|
||||
sheetApi.preview(sheetId).then(response => {
|
||||
window.open(response.data, '_blank')
|
||||
})
|
||||
},
|
||||
onSheetSettingsClose() {
|
||||
this.sheetSettingVisible = false
|
||||
this.selectedSheet = {}
|
||||
this.loadSheets()
|
||||
},
|
||||
onRefreshSheetFromSetting(sheet) {
|
||||
this.selectedSheet = sheet
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.sheet-title {
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
<template>
|
||||
<a-drawer
|
||||
title="页面设置"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
placement="right"
|
||||
closable
|
||||
destroyOnClose
|
||||
@close="onClose"
|
||||
:visible="visible"
|
||||
>
|
||||
<a-skeleton
|
||||
active
|
||||
:loading="settingLoading"
|
||||
:paragraph="{ rows: 18 }"
|
||||
>
|
||||
<div class="post-setting-drawer-content">
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">基本设置</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="页面标题:"
|
||||
v-if="needTitle"
|
||||
>
|
||||
<a-input v-model="selectedSheet.title" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="页面路径:"
|
||||
:help="options.blog_url+'/s/'+ (selectedSheet.url ? selectedSheet.url : '{auto_generate}')"
|
||||
>
|
||||
<a-input v-model="selectedSheet.url" />
|
||||
</a-form-item>
|
||||
<a-form-item label="发表时间:">
|
||||
<a-date-picker
|
||||
showTime
|
||||
:defaultValue="pickerDefaultValue"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择页面发表时间"
|
||||
@change="onSheetDateChange"
|
||||
@ok="onSheetDateOk"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="开启评论:">
|
||||
<a-radio-group
|
||||
v-model="selectedSheet.disallowComment"
|
||||
:defaultValue="false"
|
||||
>
|
||||
<a-radio :value="false">开启</a-radio>
|
||||
<a-radio :value="true">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="自定义模板:">
|
||||
<a-select v-model="selectedSheet.template">
|
||||
<a-select-option
|
||||
key=""
|
||||
value=""
|
||||
>无</a-select-option>
|
||||
<a-select-option
|
||||
v-for="tpl in customTpls"
|
||||
:key="tpl"
|
||||
:value="tpl"
|
||||
>{{ tpl }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider />
|
||||
|
||||
<div :style="{ marginBottom: '16px' }">
|
||||
<h3 class="post-setting-drawer-title">缩略图</h3>
|
||||
<div class="post-setting-drawer-item">
|
||||
<div class="sheet-thumb">
|
||||
<img
|
||||
class="img"
|
||||
:src="selectedSheet.thumbnail || '//i.loli.net/2019/05/05/5ccf007c0a01d.png'"
|
||||
@click="()=>this.thumbDrawerVisible = true"
|
||||
>
|
||||
<a-button
|
||||
class="sheet-thumb-remove"
|
||||
type="dashed"
|
||||
@click="handlerRemoveThumb"
|
||||
>移除</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="divider-transparent" />
|
||||
</div>
|
||||
</a-skeleton>
|
||||
<AttachmentSelectDrawer
|
||||
v-model="thumbDrawerVisible"
|
||||
@listenToSelect="handleSelectSheetThumb"
|
||||
:drawerWidth="460"
|
||||
/>
|
||||
<div class="bottom-control">
|
||||
<a-button
|
||||
style="marginRight: 8px"
|
||||
@click="handleDraftClick"
|
||||
>保存草稿</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handlePublishClick"
|
||||
>发布</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import moment from 'moment'
|
||||
import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer'
|
||||
import { mapGetters } from 'vuex'
|
||||
import themeApi from '@/api/theme'
|
||||
import sheetApi from '@/api/sheet'
|
||||
export default {
|
||||
name: 'SheetSetting',
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: {
|
||||
AttachmentSelectDrawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thumbDrawerVisible: false,
|
||||
settingLoading: true,
|
||||
selectedSheet: this.sheet,
|
||||
customTpls: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
sheet: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
needTitle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadSkeleton()
|
||||
this.loadCustomTpls()
|
||||
},
|
||||
watch: {
|
||||
sheet(val) {
|
||||
this.selectedSheet = val
|
||||
},
|
||||
selectedSheet(val) {
|
||||
this.$emit('onRefreshSheet', val)
|
||||
},
|
||||
visible: function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.loadSkeleton()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pickerDefaultValue() {
|
||||
if (this.selectedSheet.createTime) {
|
||||
var date = new Date(this.selectedSheet.createTime)
|
||||
return moment(date, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
return moment(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
methods: {
|
||||
loadSkeleton() {
|
||||
this.settingLoading = true
|
||||
setTimeout(() => {
|
||||
this.settingLoading = false
|
||||
}, 500)
|
||||
},
|
||||
loadCustomTpls() {
|
||||
themeApi.customTpls().then(response => {
|
||||
this.customTpls = response.data.data
|
||||
})
|
||||
},
|
||||
handleSelectSheetThumb(data) {
|
||||
this.selectedSheet.thumbnail = encodeURI(data.path)
|
||||
this.thumbDrawerVisible = false
|
||||
},
|
||||
handlerRemoveThumb() {
|
||||
this.selectedSheet.thumbnail = null
|
||||
},
|
||||
handlePublishClick() {
|
||||
this.selectedSheet.status = 'PUBLISHED'
|
||||
this.saveSheet()
|
||||
},
|
||||
handleDraftClick() {
|
||||
this.selectedSheet.status = 'DRAFT'
|
||||
this.saveSheet()
|
||||
},
|
||||
saveSheet() {
|
||||
this.createOrUpdateSheet(
|
||||
() => this.$message.success('页面发布成功!'),
|
||||
() => this.$message.success('页面发布成功!'),
|
||||
false
|
||||
)
|
||||
},
|
||||
createOrUpdateSheet(createSuccess, updateSuccess, autoSave) {
|
||||
if (!this.selectedSheet.title) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '页面标题不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.selectedSheet.originalContent) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '页面内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.selectedSheet.id) {
|
||||
sheetApi.update(this.selectedSheet.id, this.selectedSheet, autoSave).then(response => {
|
||||
this.$log.debug('Updated sheet', response.data.data)
|
||||
if (updateSuccess) {
|
||||
updateSuccess()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
sheetApi.create(this.selectedSheet, autoSave).then(response => {
|
||||
this.$log.debug('Created sheet', response.data.data)
|
||||
if (createSuccess) {
|
||||
createSuccess()
|
||||
}
|
||||
this.selectedSheet = response.data.data
|
||||
})
|
||||
}
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close', false)
|
||||
},
|
||||
onSheetDateChange(value, dateString) {
|
||||
this.selectedSheet.createTime = value.valueOf()
|
||||
},
|
||||
onSheetDateOk(value) {
|
||||
this.selectedSheet.createTime = value.valueOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -2,36 +2,65 @@
|
|||
<div class="page-header-index-wide">
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-card :bordered="false">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="48">
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-col
|
||||
:md="6"
|
||||
:sm="24"
|
||||
>
|
||||
<a-form-item label="关键词">
|
||||
<a-input v-model="queryParam.keyword"/>
|
||||
<a-input v-model="queryParam.keyword" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-col
|
||||
:md="6"
|
||||
:sm="24"
|
||||
>
|
||||
<a-form-item label="状态">
|
||||
<a-select placeholder="请选择状态">
|
||||
<a-select-option value="1">公开</a-select-option>
|
||||
<a-select-option value="0">私密</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择状态"
|
||||
v-model="queryParam.type"
|
||||
@change="loadJournals(true)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="type in Object.keys(journalType)"
|
||||
:key="type"
|
||||
:value="type"
|
||||
>{{ journalType[type].text }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-col
|
||||
:md="6"
|
||||
:sm="24"
|
||||
>
|
||||
<span class="table-page-search-submitButtons">
|
||||
<a-button type="primary" @click="loadJournals(true)">查询</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="resetParam">重置</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="loadJournals(true)"
|
||||
>查询</a-button>
|
||||
<a-button
|
||||
style="margin-left: 8px;"
|
||||
@click="resetParam"
|
||||
>重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<div class="table-operator">
|
||||
<a-button type="primary" icon="plus" @click="handleNew">写日志</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="handleNew"
|
||||
>写日志</a-button>
|
||||
</div>
|
||||
<a-divider/>
|
||||
<a-divider />
|
||||
<div style="margin-top:15px">
|
||||
<a-list
|
||||
itemLayout="vertical"
|
||||
|
@ -39,7 +68,11 @@
|
|||
:dataSource="journals"
|
||||
:loading="listLoading"
|
||||
>
|
||||
<a-list-item slot="renderItem" slot-scope="item, index" :key="index">
|
||||
<a-list-item
|
||||
slot="renderItem"
|
||||
slot-scope="item, index"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 日志图片集合 -->
|
||||
<!-- <a-card
|
||||
hoverable
|
||||
|
@ -51,34 +84,63 @@
|
|||
<img alt="example" :src="photo.thumbnail" slot="cover">
|
||||
</a-card> -->
|
||||
|
||||
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancelPreview">
|
||||
<!-- <a-modal
|
||||
:visible="previewVisible"
|
||||
:footer="null"
|
||||
@cancel="handleCancelPreview"
|
||||
>
|
||||
<img
|
||||
:alt="previewPhoto.name + previewPhoto.description"
|
||||
style="width: 100%"
|
||||
:src="previewPhoto.url"
|
||||
>
|
||||
</a-modal>
|
||||
</a-modal> -->
|
||||
|
||||
<template slot="actions">
|
||||
<span>
|
||||
<a href="javascript:void(0);">
|
||||
<a-icon type="like-o" style="margin-right: 8px"/>
|
||||
<a-icon
|
||||
type="like-o"
|
||||
style="margin-right: 8px"
|
||||
/>
|
||||
{{ item.likes }}
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
<a href="javascript:void(0);" @click="handleCommentShow(item)">
|
||||
<a-icon type="message" style="margin-right: 8px"/>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleCommentShow(item)"
|
||||
>
|
||||
<a-icon
|
||||
type="message"
|
||||
style="margin-right: 8px"
|
||||
/>
|
||||
{{ item.commentCount }}
|
||||
</a>
|
||||
</span>
|
||||
<span v-if="item.type=='INTIMATE'">
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
disabled
|
||||
>
|
||||
<a-icon type="lock" />
|
||||
</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<a href="javascript:void(0);">
|
||||
<a-icon type="unlock" />
|
||||
</a>
|
||||
</span>
|
||||
<!-- <span>
|
||||
From 微信
|
||||
</span>-->
|
||||
</template>
|
||||
<template slot="extra">
|
||||
<a href="javascript:void(0);" @click="handleEdit(item)">编辑</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
@click="handleEdit(item)"
|
||||
>编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="你确定要删除这条日志?"
|
||||
@confirm="handleDelete(item.id)"
|
||||
|
@ -91,7 +153,11 @@
|
|||
|
||||
<a-list-item-meta :description="item.content">
|
||||
<span slot="title">{{ item.createTime | moment }}</span>
|
||||
<a-avatar slot="avatar" size="large" :src="user.avatar"/>
|
||||
<a-avatar
|
||||
slot="avatar"
|
||||
size="large"
|
||||
:src="user.avatar"
|
||||
/>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<div class="page-wrapper">
|
||||
|
@ -115,16 +181,35 @@
|
|||
<a-modal v-model="visible">
|
||||
<template slot="title">
|
||||
{{ title }}
|
||||
<a-tooltip slot="action" title="只能输入250字">
|
||||
<a-icon type="info-circle-o"/>
|
||||
<a-tooltip
|
||||
slot="action"
|
||||
title="只能输入250字"
|
||||
>
|
||||
<a-icon type="info-circle-o" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<a-button key="submit" type="primary" @click="createOrUpdateJournal">发布</a-button>
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="createOrUpdateJournal"
|
||||
>发布</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input type="textarea" :autosize="{ minRows: 8 }" v-model="journal.content"/>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="journal.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-switch
|
||||
checkedChildren="公开"
|
||||
unCheckedChildren="私密"
|
||||
v-model="isPublic"
|
||||
defaultChecked
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item v-show="showMoreOptions">
|
||||
<UploadPhoto
|
||||
|
@ -154,11 +239,19 @@
|
|||
v-model="selectCommentVisible"
|
||||
>
|
||||
<template slot="footer">
|
||||
<a-button key="submit" type="primary" @click="handleReplyComment">回复</a-button>
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
@click="handleReplyComment"
|
||||
>回复</a-button>
|
||||
</template>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item>
|
||||
<a-input type="textarea" :autosize="{ minRows: 8 }" v-model="replyComment.content"/>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 8 }"
|
||||
v-model="replyComment.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
@ -168,20 +261,27 @@
|
|||
title="评论列表"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="commentVisiable"
|
||||
:visible="commentVisible"
|
||||
destroyOnClose
|
||||
@close="()=>this.commentVisiable = false"
|
||||
@close="()=>this.commentVisible = false"
|
||||
>
|
||||
<a-row
|
||||
type="flex"
|
||||
align="middle"
|
||||
>
|
||||
<a-row type="flex" align="middle">
|
||||
<a-col :span="24">
|
||||
<a-comment>
|
||||
<a-avatar :src="user.avatar" :alt="user.nickname" slot="avatar"/>
|
||||
<a-avatar
|
||||
:src="user.avatar"
|
||||
:alt="user.nickname"
|
||||
slot="avatar"
|
||||
/>
|
||||
<p slot="content">{{ journal.content }}</p>
|
||||
|
||||
<span slot="datetime">{{ journal.createTime | moment }}</span>
|
||||
</a-comment>
|
||||
</a-col>
|
||||
<a-divider/>
|
||||
<a-divider />
|
||||
<a-col :span="24">
|
||||
<journal-comment-tree
|
||||
v-for="(comment,index) in comments"
|
||||
|
@ -199,29 +299,30 @@
|
|||
<script>
|
||||
import JournalCommentTree from './components/JournalCommentTree'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import journalApi from '@/api/journal'
|
||||
import journalCommentApi from '@/api/journalComment'
|
||||
import userApi from '@/api/user'
|
||||
import UploadPhoto from '@/components/Upload/UploadPhoto.vue'
|
||||
export default {
|
||||
mixins: [mixin, mixinDevice],
|
||||
components: { JournalCommentTree, UploadPhoto },
|
||||
data() {
|
||||
return {
|
||||
plusPhotoVisible: true,
|
||||
photoList: [], // 编辑图片时回显所需对象
|
||||
previewVisible: false,
|
||||
journalType: journalApi.journalType,
|
||||
// plusPhotoVisible: true,
|
||||
// photoList: [], // 编辑图片时回显所需对象
|
||||
// previewVisible: false,
|
||||
showMoreOptions: false,
|
||||
previewPhoto: {
|
||||
// 图片预览信息临时对象
|
||||
name: '',
|
||||
description: '',
|
||||
url: ''
|
||||
},
|
||||
// previewPhoto: {
|
||||
// // 图片预览信息临时对象
|
||||
// name: '',
|
||||
// description: '',
|
||||
// url: ''
|
||||
// },
|
||||
title: '发表',
|
||||
listLoading: false,
|
||||
visible: false,
|
||||
commentVisiable: false,
|
||||
commentVisible: false,
|
||||
selectCommentVisible: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -232,50 +333,49 @@ export default {
|
|||
page: 0,
|
||||
size: 10,
|
||||
sort: null,
|
||||
keyword: null
|
||||
keyword: null,
|
||||
type: null
|
||||
},
|
||||
journals: [],
|
||||
comments: [],
|
||||
journal: {
|
||||
id: undefined,
|
||||
content: '',
|
||||
photos: []
|
||||
},
|
||||
journal: {},
|
||||
isPublic: true,
|
||||
journalPhotos: [], // 日志图片集合最多九张
|
||||
selectComment: null,
|
||||
replyComment: {},
|
||||
user: {}
|
||||
replyComment: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadJournals()
|
||||
this.loadUser()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user'])
|
||||
},
|
||||
methods: {
|
||||
handleCancelPreview() {
|
||||
this.previewVisible = false
|
||||
},
|
||||
handlerPhotoPreview(photo) {
|
||||
// 日志图片预览
|
||||
this.previewVisible = true
|
||||
this.previewPhoto = photo
|
||||
},
|
||||
handlerPhotoUploadSuccess(response, file) {
|
||||
var callData = response.data.data
|
||||
var photo = {
|
||||
name: callData.name,
|
||||
url: callData.path,
|
||||
thumbnail: callData.thumbPath,
|
||||
suffix: callData.suffix,
|
||||
width: callData.width,
|
||||
height: callData.height
|
||||
}
|
||||
this.journalPhotos.push(photo)
|
||||
},
|
||||
handleUploadPhotoWallClick() {
|
||||
// 是否显示上传照片墙组件
|
||||
this.showMoreOptions = !this.showMoreOptions
|
||||
},
|
||||
// handleCancelPreview() {
|
||||
// this.previewVisible = false
|
||||
// },
|
||||
// handlerPhotoPreview(photo) {
|
||||
// // 日志图片预览
|
||||
// this.previewVisible = true
|
||||
// this.previewPhoto = photo
|
||||
// },
|
||||
// handlerPhotoUploadSuccess(response, file) {
|
||||
// var callData = response.data.data
|
||||
// var photo = {
|
||||
// name: callData.name,
|
||||
// url: callData.path,
|
||||
// thumbnail: callData.thumbPath,
|
||||
// suffix: callData.suffix,
|
||||
// width: callData.width,
|
||||
// height: callData.height
|
||||
// }
|
||||
// this.journalPhotos.push(photo)
|
||||
// },
|
||||
// handleUploadPhotoWallClick() {
|
||||
// // 是否显示上传照片墙组件
|
||||
// this.showMoreOptions = !this.showMoreOptions
|
||||
// },
|
||||
loadJournals(isSearch) {
|
||||
this.queryParam.page = this.pagination.page - 1
|
||||
this.queryParam.size = this.pagination.size
|
||||
|
@ -290,28 +390,23 @@ export default {
|
|||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
loadUser() {
|
||||
userApi.getProfile().then(response => {
|
||||
this.user = response.data.data
|
||||
})
|
||||
},
|
||||
handleNew() {
|
||||
this.title = '新建'
|
||||
this.visible = true
|
||||
this.journal = {}
|
||||
|
||||
// 显示图片上传框
|
||||
this.plusPhotoVisible = true
|
||||
this.photoList = []
|
||||
// this.plusPhotoVisible = true
|
||||
// this.photoList = []
|
||||
},
|
||||
handleEdit(item) {
|
||||
this.title = '编辑'
|
||||
this.journal = item
|
||||
this.isPublic = item.type !== 'INTIMATE'
|
||||
this.visible = true
|
||||
|
||||
// 为编辑时需要回显图片数组赋值,并隐藏图片上传框
|
||||
this.plusPhotoVisible = false
|
||||
this.photoList = item.photos
|
||||
// this.plusPhotoVisible = false
|
||||
// this.photoList = item.photos
|
||||
},
|
||||
handleDelete(id) {
|
||||
journalApi.delete(id).then(response => {
|
||||
|
@ -323,7 +418,7 @@ export default {
|
|||
this.journal = journal
|
||||
journalApi.commentTree(this.journal.id).then(response => {
|
||||
this.comments = response.data.data.content
|
||||
this.commentVisiable = true
|
||||
this.commentVisible = true
|
||||
})
|
||||
},
|
||||
handleCommentReplyClick(comment) {
|
||||
|
@ -349,18 +444,29 @@ export default {
|
|||
},
|
||||
createOrUpdateJournal() {
|
||||
// 给属性填充数据
|
||||
this.journal.photos = this.journalPhotos
|
||||
// this.journal.photos = this.journalPhotos
|
||||
this.journal.type = this.isPublic ? 'PUBLIC' : 'INTIMATE'
|
||||
|
||||
if (!this.journal.content) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '发布内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.journal.id) {
|
||||
journalApi.update(this.journal.id, this.journal).then(response => {
|
||||
this.$message.success('更新成功!')
|
||||
this.loadJournals()
|
||||
this.isPublic = true
|
||||
})
|
||||
} else {
|
||||
journalApi.create(this.journal).then(response => {
|
||||
this.$message.success('发表成功!')
|
||||
this.loadJournals()
|
||||
this.photoList = []
|
||||
// this.photoList = []
|
||||
this.isPublic = true
|
||||
})
|
||||
}
|
||||
this.visible = false
|
||||
|
@ -373,21 +479,21 @@ export default {
|
|||
},
|
||||
resetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.type = null
|
||||
this.loadJournals()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped="scoped">
|
||||
.more-options-btn {
|
||||
/* .more-options-btn {
|
||||
margin-left: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 日志图片卡片样式 */
|
||||
.photo-card {
|
||||
width: 104px;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
} */
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card :title="title">
|
||||
<a-card :title="title" :bodyStyle="{ padding: '16px' }">
|
||||
<a-form layout="horizontal">
|
||||
<a-form-item label="网站名称:">
|
||||
<a-input v-model="link.name" />
|
||||
|
@ -65,7 +65,7 @@
|
|||
:xs="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card title="所有友情链接">
|
||||
<a-card title="所有友情链接" :bodyStyle="{ padding: '16px' }">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="links"
|
||||
|
@ -201,6 +201,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
:span="24"
|
||||
class="search-box"
|
||||
>
|
||||
<a-card :bordered="false">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="48">
|
||||
|
@ -26,9 +29,15 @@
|
|||
:sm="24"
|
||||
>
|
||||
<a-form-item label="分组">
|
||||
<a-select>
|
||||
<a-select-option value="11">11</a-select-option>
|
||||
<a-select-option value="22">22</a-select-option>
|
||||
<a-select
|
||||
v-model="queryParam.team"
|
||||
@change="loadPhotos(true)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item,index) in teams"
|
||||
:key="index"
|
||||
:value="item"
|
||||
>{{ item }}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -61,7 +70,7 @@
|
|||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-list
|
||||
:grid="{ gutter: 12, xs: 1, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
|
||||
:grid="{ gutter: 12, xs: 2, sm: 2, md: 4, lg: 6, xl: 6, xxl: 6 }"
|
||||
:dataSource="photos"
|
||||
:loading="listLoading"
|
||||
>
|
||||
|
@ -80,7 +89,7 @@
|
|||
</div>
|
||||
<a-card-meta>
|
||||
<ellipsis
|
||||
:length="isMobile()?36:18"
|
||||
:length="isMobile()?12:16"
|
||||
tooltip
|
||||
slot="description"
|
||||
>{{ item.name }}</ellipsis>
|
||||
|
@ -104,7 +113,7 @@
|
|||
title="图片详情"
|
||||
:width="isMobile()?'100%':'460'"
|
||||
closable
|
||||
:visible="drawerVisiable"
|
||||
:visible="drawerVisible"
|
||||
destroyOnClose
|
||||
@close="onDrawerClose"
|
||||
>
|
||||
|
@ -272,8 +281,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer'
|
||||
import { mixin, mixinDevice } from '@/utils/mixin.js'
|
||||
import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer'
|
||||
import photoApi from '@/api/photo'
|
||||
|
||||
export default {
|
||||
|
@ -283,12 +292,13 @@ export default {
|
|||
mixins: [mixin, mixinDevice],
|
||||
data() {
|
||||
return {
|
||||
drawerVisiable: false,
|
||||
drawerVisible: false,
|
||||
drawerLoading: false,
|
||||
listLoading: true,
|
||||
thumDrawerVisible: false,
|
||||
photo: {},
|
||||
photos: [],
|
||||
teams: [],
|
||||
editable: false,
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -299,12 +309,14 @@ export default {
|
|||
page: 0,
|
||||
size: 18,
|
||||
sort: null,
|
||||
keyword: null
|
||||
keyword: null,
|
||||
team: null
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadPhotos()
|
||||
this.loadTeams()
|
||||
},
|
||||
methods: {
|
||||
loadPhotos(isSearch) {
|
||||
|
@ -321,6 +333,11 @@ export default {
|
|||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
loadTeams() {
|
||||
photoApi.listTeams().then(response => {
|
||||
this.teams = response.data.data
|
||||
})
|
||||
},
|
||||
handleCreateOrUpdate() {
|
||||
if (this.photo.id) {
|
||||
photoApi.update(this.photo.id, this.photo).then(response => {
|
||||
|
@ -338,7 +355,7 @@ export default {
|
|||
},
|
||||
showDrawer(photo) {
|
||||
this.photo = photo
|
||||
this.drawerVisiable = true
|
||||
this.drawerVisible = true
|
||||
},
|
||||
handlePaginationChange(page, size) {
|
||||
this.$log.debug(`Current: ${page}, PageSize: ${size}`)
|
||||
|
@ -348,7 +365,7 @@ export default {
|
|||
},
|
||||
handleAddClick() {
|
||||
this.editable = true
|
||||
this.drawerVisiable = true
|
||||
this.drawerVisible = true
|
||||
},
|
||||
handleEditClick() {
|
||||
this.editable = true
|
||||
|
@ -365,14 +382,17 @@ export default {
|
|||
},
|
||||
selectPhotoThumb(data) {
|
||||
this.photo.url = encodeURI(data.path)
|
||||
this.photo.thumbnail = encodeURI(data.thumbPath)
|
||||
this.thumDrawerVisible = false
|
||||
},
|
||||
resetParam() {
|
||||
this.queryParam.keyword = null
|
||||
this.queryParam.team = null
|
||||
this.loadPhotos()
|
||||
this.loadTeams()
|
||||
},
|
||||
onDrawerClose() {
|
||||
this.drawerVisiable = false
|
||||
this.drawerVisible = false
|
||||
this.photo = {}
|
||||
this.editable = false
|
||||
}
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
<div class="page-header-index-wide">
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-card :bordered="false">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
class="environment-info"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<template slot="title">
|
||||
环境信息
|
||||
|
@ -39,7 +43,7 @@
|
|||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<ul>
|
||||
<ul style="margin: 0;padding: 0;list-style: none;">
|
||||
<li>Server 版本:{{ environments.version }}</li>
|
||||
<li>Admin 版本:{{ adminVersion }}</li>
|
||||
<li>数据库:{{ environments.database }}</li>
|
||||
|
@ -50,16 +54,19 @@
|
|||
<a
|
||||
href="https://github.com/halo-dev"
|
||||
target="_blank"
|
||||
style="margin-right: 10px;"
|
||||
>开源地址
|
||||
<a-icon type="link" /></a>
|
||||
<a
|
||||
href="https://halo.run/guide"
|
||||
target="_blank"
|
||||
style="margin-right: 10px;"
|
||||
>用户文档
|
||||
<a-icon type="link" /></a>
|
||||
<a
|
||||
href="https://bbs.halo.run"
|
||||
target="_blank"
|
||||
style="margin-right: 10px;"
|
||||
>在线社区
|
||||
<a-icon type="link" /></a>
|
||||
</a-card>
|
||||
|
@ -67,6 +74,7 @@
|
|||
<a-card
|
||||
title="开发者"
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<a
|
||||
:href="item.github"
|
||||
|
@ -90,6 +98,7 @@
|
|||
<a-card
|
||||
title="时间轴"
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<a-timeline>
|
||||
<a-timeline-item>...</a-timeline-item>
|
||||
|
@ -107,6 +116,7 @@
|
|||
|
||||
<script>
|
||||
import adminApi from '@/api/admin'
|
||||
import axios from 'axios'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -145,6 +155,10 @@ export default {
|
|||
}
|
||||
],
|
||||
steps: [
|
||||
{
|
||||
date: '2019-09-11',
|
||||
content: 'Halo v1.1.0 发布'
|
||||
},
|
||||
{
|
||||
date: '2019-07-09',
|
||||
content: 'Halo v1.0.3 发布'
|
||||
|
@ -199,6 +213,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.getEnvironments()
|
||||
this.checkUpdate()
|
||||
},
|
||||
computed: {
|
||||
updateText() {
|
||||
|
@ -229,7 +244,8 @@ export default {
|
|||
const text = `Server 版本:${this.environments.version}
|
||||
Admin 版本:${this.adminVersion}
|
||||
数据库:${this.environments.database}
|
||||
运行模式:${this.environments.mode}`
|
||||
运行模式:${this.environments.mode}
|
||||
UA 信息:${navigator.userAgent}`
|
||||
this.$copyText(text)
|
||||
.then(message => {
|
||||
console.log('copy', message)
|
||||
|
@ -239,25 +255,64 @@ Admin 版本:${this.adminVersion}
|
|||
console.log('copy.err', err)
|
||||
this.$message.error('复制失败!')
|
||||
})
|
||||
},
|
||||
async checkUpdate() {
|
||||
const _this = this
|
||||
|
||||
axios
|
||||
.get('https://api.github.com/repos/halo-dev/halo/releases/latest')
|
||||
.then(response => {
|
||||
const data = response.data
|
||||
if (data.draft || data.prerelease) {
|
||||
return
|
||||
}
|
||||
const current = _this.calculateIntValue(_this.environments.version)
|
||||
const latest = _this.calculateIntValue(data.name)
|
||||
if (current >= latest) {
|
||||
return
|
||||
}
|
||||
const title = '新版本提醒'
|
||||
const content = '检测到新版本:' + data.name + ',点击下方按钮查看最新版本。'
|
||||
const url = data.html_url
|
||||
this.$notification.open({
|
||||
message: title,
|
||||
description: content,
|
||||
icon: <a-icon type="smile" style="color: #108ee9" />,
|
||||
btn: h => {
|
||||
return h(
|
||||
'a-button',
|
||||
{
|
||||
props: {
|
||||
type: 'primary',
|
||||
size: 'small'
|
||||
},
|
||||
on: {
|
||||
click: () => window.open(url, '_blank')
|
||||
}
|
||||
},
|
||||
'去看看'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Check update fail', error)
|
||||
})
|
||||
},
|
||||
calculateIntValue(version) {
|
||||
version = version.replace(/v/g, '')
|
||||
const ss = version.split('.')
|
||||
if (ss == null || ss.length !== 3) {
|
||||
return -1
|
||||
}
|
||||
const major = parseInt(ss[0])
|
||||
const minor = parseInt(ss[1])
|
||||
const micro = parseInt(ss[2])
|
||||
if (isNaN(major) || isNaN(minor) || isNaN(micro)) {
|
||||
return -1
|
||||
}
|
||||
return major * 1000000 + minor * 1000 + micro
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scope>
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.environment-info {
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
a {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-row
|
||||
class="height-100"
|
||||
type="flex"
|
||||
justify="center"
|
||||
align="middle"
|
||||
style="height: 100vh;"
|
||||
>
|
||||
<a-col
|
||||
:xl="8"
|
||||
|
@ -16,7 +16,7 @@
|
|||
<a-card
|
||||
:bordered="false"
|
||||
title="Halo 安装向导"
|
||||
class="install-card"
|
||||
style="box-shadow: 0px 10px 20px 0px rgba(236, 236, 236, 0.86);"
|
||||
>
|
||||
|
||||
<a-steps :current="stepCurrent">
|
||||
|
@ -91,7 +91,7 @@
|
|||
<a-input
|
||||
v-model="installation.password"
|
||||
type="password"
|
||||
placeholder="用户密码"
|
||||
placeholder="用户密码(8-100位)"
|
||||
v-decorator="[
|
||||
'password',
|
||||
{rules: [{ required: true, message: '请输入密码(8-100位)' }]}
|
||||
|
@ -189,12 +189,14 @@
|
|||
class="install-action"
|
||||
type="flex"
|
||||
justify="space-between"
|
||||
style="margin-top: 1rem;"
|
||||
>
|
||||
<div>
|
||||
<a-button
|
||||
class="previus-button"
|
||||
v-if="stepCurrent != 0"
|
||||
@click="stepCurrent--"
|
||||
style="margin-right: 1rem;"
|
||||
>上一步</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -218,7 +220,6 @@
|
|||
|
||||
<script>
|
||||
import adminApi from '@/api/admin'
|
||||
import optionApi from '@/api/option'
|
||||
import recoveryApi from '@/api/recovery'
|
||||
|
||||
export default {
|
||||
|
@ -244,8 +245,7 @@ export default {
|
|||
migrationUploadName: 'file',
|
||||
migrationData: null,
|
||||
stepCurrent: 0,
|
||||
bloggerForm: this.$form.createForm(this),
|
||||
keys: ['is_installed']
|
||||
bloggerForm: this.$form.createForm(this)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -254,8 +254,8 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
verifyIsInstall() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
if (response.data.data.is_installed) {
|
||||
adminApi.isInstalled().then(response => {
|
||||
if (response.data.data) {
|
||||
this.$router.push({ name: 'Login' })
|
||||
}
|
||||
})
|
||||
|
@ -292,7 +292,7 @@ export default {
|
|||
this.$log.debug('Installation response', response)
|
||||
this.$message.success('安装成功!')
|
||||
setTimeout(() => {
|
||||
this.$router.push({ name: 'Dashboard' })
|
||||
this.$router.push({ name: 'Login' })
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
|
@ -322,20 +322,3 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.height-100 {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.install-action {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.previus-button {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.install-card {
|
||||
box-shadow: 0px 10px 20px 0px rgba(236, 236, 236, 0.86);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="options.blog_footer_info"
|
||||
placeholder="支持 HTML 格式的文本"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
|
@ -85,26 +86,21 @@
|
|||
label="关键词: "
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-tooltip
|
||||
:trigger="['focus']"
|
||||
placement="right"
|
||||
title="多个关键词以英文逗号隔开"
|
||||
>
|
||||
<a-input v-model="options.seo_keywords" />
|
||||
</a-tooltip>
|
||||
<a-input
|
||||
v-model="options.seo_keywords"
|
||||
placeholder="多个关键词以英文状态下的逗号隔开"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="博客描述:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.seo_description" />
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="options.seo_description"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item
|
||||
label="百度推送 Token: "
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.seo_baidu_token" />
|
||||
</a-form-item> -->
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -118,6 +114,16 @@
|
|||
<a-icon type="form" />文章设置
|
||||
</span>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="首页文章排序:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-select v-model="options.post_index_sort">
|
||||
<a-select-option value="createTime">创建时间</a-select-option>
|
||||
<a-select-option value="editTime">最后编辑时间</a-select-option>
|
||||
<a-select-option value="visits">点击量</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="首页显示条数:"
|
||||
:wrapper-col="wrapperCol"
|
||||
|
@ -196,6 +202,17 @@
|
|||
>
|
||||
<a-switch v-model="options.comment_api_enabled" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="评论模块 JS:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2 }"
|
||||
v-model="options.comment_internal_plugin_js"
|
||||
placeholder="该设置仅对内置的评论模块有效"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="每页显示条数: "
|
||||
:wrapper-col="wrapperCol"
|
||||
|
@ -234,6 +251,30 @@
|
|||
<a-icon type="picture" />附件设置
|
||||
</span>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="上传图片时预览:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-switch v-model="options.attachment_upload_image_preview_enable" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="最大上传文件数:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
type="number"
|
||||
v-model="options.attachment_upload_max_files"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="同时上传文件数:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
type="number"
|
||||
v-model="options.attachment_upload_max_parallel_uploads"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="存储位置:"
|
||||
:wrapper-col="wrapperCol"
|
||||
|
@ -250,20 +291,31 @@
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
<div
|
||||
class="upyunForm"
|
||||
v-show="upyunFormHidden"
|
||||
class="smmsForm"
|
||||
v-show="smmsFormVisible"
|
||||
>
|
||||
<a-form-item
|
||||
label="域名:"
|
||||
label="Secret Token:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-tooltip
|
||||
:trigger="['focus']"
|
||||
placement="right"
|
||||
title="需要加上 http:// 或者 https://"
|
||||
<a-input
|
||||
v-model="options.smms_api_secret_token"
|
||||
placeholder="需要到 sm.ms 官网注册后获取"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div
|
||||
class="upyunForm"
|
||||
v-show="upyunFormVisible"
|
||||
>
|
||||
<a-input v-model="options.oss_upyun_domain" />
|
||||
</a-tooltip>
|
||||
<a-form-item
|
||||
label="绑定域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.oss_upyun_domain"
|
||||
placeholder="需要加上 http:// 或者 https://"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="空间名称:"
|
||||
|
@ -292,17 +344,38 @@
|
|||
>
|
||||
<a-input v-model="options.oss_upyun_source" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="图片处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.oss_upyun_style_rule"
|
||||
placeholder="间隔标识符+图片处理版本名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="缩略图处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_upyun_style_rule" />
|
||||
<a-input
|
||||
v-model="options.oss_upyun_thumbnail_style_rule"
|
||||
placeholder="间隔标识符+图片处理版本名称,一般为后台展示所用"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div
|
||||
class="qnyunForm"
|
||||
v-show="qnyunFormHidden"
|
||||
v-show="qnyunFormVisible"
|
||||
>
|
||||
<a-form-item
|
||||
label="绑定域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.oss_qiniu_domain"
|
||||
placeholder="需要加上 http:// 或者 https://"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="区域:"
|
||||
:wrapper-col="wrapperCol"
|
||||
|
@ -316,18 +389,6 @@
|
|||
<a-select-option value="as0">东南亚</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-tooltip
|
||||
:trigger="['focus']"
|
||||
placement="right"
|
||||
title="需要加上 http:// 或者 https://"
|
||||
>
|
||||
<a-input v-model="options.oss_qiniu_domain" />
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Access Key:"
|
||||
:wrapper-col="wrapperCol"
|
||||
|
@ -338,30 +399,60 @@
|
|||
label="Secret Key:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_qiniu_secret_key" />
|
||||
<a-input
|
||||
type="password"
|
||||
v-model="options.oss_qiniu_secret_key"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Bucket:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_qiniu_bucket" />
|
||||
<a-input
|
||||
v-model="options.oss_qiniu_bucket"
|
||||
placeholder="存储空间名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="图片处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.oss_qiniu_style_rule"
|
||||
placeholder="样式分隔符+图片处理样式名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="缩略图处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_qiniu_style_rule" />
|
||||
<a-input
|
||||
v-model="options.oss_qiniu_thumbnail_style_rule"
|
||||
placeholder="样式分隔符+图片处理样式名称,一般为后台展示所用"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div
|
||||
class="aliyunForm"
|
||||
v-show="aliyunFormHidden"
|
||||
v-show="aliyunFormVisible"
|
||||
>
|
||||
<a-form-item
|
||||
label="绑定域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.oss_aliyun_domain"
|
||||
placeholder="如不填写,路径根域名将为 Bucket + EndPoint"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Bucket:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_aliyun_bucket_name" />
|
||||
<a-input
|
||||
v-model="options.oss_aliyun_bucket_name"
|
||||
placeholder="存储空间名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="EndPoint(地域节点):"
|
||||
|
@ -379,65 +470,119 @@
|
|||
label="Access Secret:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_aliyun_access_secret" />
|
||||
<a-input
|
||||
type="password"
|
||||
v-model="options.oss_aliyun_access_secret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="图片处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.oss_aliyun_style_rule"
|
||||
placeholder="请到阿里云控制台的图片处理获取"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="缩略图处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_aliyun_style_rule" />
|
||||
<a-input
|
||||
v-model="options.oss_aliyun_thumbnail_style_rule"
|
||||
placeholder="请到阿里云控制台的图片处理获取,一般为后台展示所用"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div
|
||||
class="baiduyunForm"
|
||||
v-show="baiduyunFormHidden"
|
||||
v-show="baiduyunFormVisible"
|
||||
>
|
||||
<a-form-item
|
||||
label="绑定域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.bos_baiduyun_domain"
|
||||
placeholder="如不填写,路径根域名将为 Bucket + EndPoint"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Bucket:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_baiduyun_bucket_name" />
|
||||
<a-input
|
||||
v-model="options.bos_baiduyun_bucket_name"
|
||||
placeholder="存储空间名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="EndPoint(地域节点):"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_baiduyun_endpoint" />
|
||||
<a-input v-model="options.bos_baiduyun_endpoint" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Access Key:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_baiduyun_access_key" />
|
||||
<a-input v-model="options.bos_baiduyun_access_key" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Access Secret:"
|
||||
label="Secret Key:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_baiduyun_access_secret" />
|
||||
<a-input
|
||||
type="password"
|
||||
v-model="options.bos_baiduyun_secret_key"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="图片处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.bos_baiduyun_style_rule"
|
||||
placeholder="请到百度云控制台的图片处理获取"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="缩略图处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_baiduyun_style_rule" />
|
||||
<a-input
|
||||
v-model="options.bos_baiduyun_thumbnail_style_rule"
|
||||
placeholder="请到百度云控制台的图片处理获取,一般为后台展示所用"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div
|
||||
class="tencentyunForm"
|
||||
v-show="tencentyunFormHidden"
|
||||
v-show="tencentyunFormVisible"
|
||||
>
|
||||
<a-form-item
|
||||
label="绑定域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.cos_tencentyun_domain"
|
||||
placeholder="如不填写,路径根域名将为 Bucket + 区域地址"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Bucket:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_tencentyun_bucket_name" />
|
||||
<a-input
|
||||
v-model="options.cos_tencentyun_bucket_name"
|
||||
placeholder="存储桶名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="区域:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-select v-model="options.oss_tencentyun_region">
|
||||
<a-select v-model="options.cos_tencentyun_region">
|
||||
<a-select-option value="ap-beijing-1">北京一区</a-select-option>
|
||||
<a-select-option value="ap-beijing">北京</a-select-option>
|
||||
<a-select-option value="ap-shanghai">上海(华东)</a-select-option>
|
||||
|
@ -450,19 +595,16 @@
|
|||
label="Secret Id:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_tencentyun_access_key" />
|
||||
<a-input v-model="options.cos_tencentyun_secret_id" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Secret Key:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_tencentyun_access_secret" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="缩略图处理策略:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input v-model="options.oss_tencentyun_style_rule" />
|
||||
<a-input
|
||||
type="password"
|
||||
v-model="options.cos_tencentyun_secret_key"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item>
|
||||
|
@ -518,13 +660,11 @@
|
|||
label="邮箱密码:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-tooltip
|
||||
:trigger="['focus']"
|
||||
placement="right"
|
||||
title="部分邮箱可能是授权码"
|
||||
>
|
||||
<a-input v-model="options.email_password" />
|
||||
</a-tooltip>
|
||||
<a-input
|
||||
v-model="options.email_password"
|
||||
type="password"
|
||||
placeholder="部分邮箱可能是授权码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="发件人:"
|
||||
|
@ -580,7 +720,7 @@
|
|||
</a-tab-pane>
|
||||
<a-tab-pane key="api">
|
||||
<span slot="tab">
|
||||
<a-icon type="align-left" />API 设置
|
||||
<a-icon type="thunderbolt" />API 设置
|
||||
</span>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
|
@ -608,6 +748,15 @@
|
|||
<a-icon type="align-left" />其他设置
|
||||
</span>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
label="CDN 加速域名:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
v-model="options.blog_cdn_domain"
|
||||
placeholder="请确保已经正确配置好了 CDN"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="自定义 head:"
|
||||
:wrapper-col="wrapperCol"
|
||||
|
@ -616,6 +765,7 @@
|
|||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="options.blog_custom_head"
|
||||
placeholder="将放置于每个页面的<head></head>标签中"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
|
@ -626,8 +776,20 @@
|
|||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="options.blog_statistics_code"
|
||||
placeholder="第三方网站统计的代码,如:Google Analytics、百度统计、CNZZ 等"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item
|
||||
label="黑名单 IP:"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5 }"
|
||||
v-model="options.blog_ip_blacklist"
|
||||
placeholder="多个 IP 地址换行隔开"
|
||||
/>
|
||||
</a-form-item> -->
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
|
@ -655,10 +817,10 @@
|
|||
</template>
|
||||
<script>
|
||||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import { mapActions } from 'vuex'
|
||||
import optionApi from '@/api/option'
|
||||
import mailApi from '@/api/mail'
|
||||
import attachmentApi from '@/api/attachment'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -673,11 +835,12 @@ export default {
|
|||
sm: { span: 12 },
|
||||
xs: { span: 24 }
|
||||
},
|
||||
upyunFormHidden: false,
|
||||
qnyunFormHidden: false,
|
||||
aliyunFormHidden: false,
|
||||
baiduyunFormHidden: false,
|
||||
tencentyunFormHidden: false,
|
||||
smmsFormVisible: false,
|
||||
upyunFormVisible: false,
|
||||
qnyunFormVisible: false,
|
||||
aliyunFormVisible: false,
|
||||
baiduyunFormVisible: false,
|
||||
tencentyunFormVisible: false,
|
||||
logoDrawerVisible: false,
|
||||
faviconDrawerVisible: false,
|
||||
options: [],
|
||||
|
@ -685,18 +848,290 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadOptions()
|
||||
this.loadFormOptions()
|
||||
},
|
||||
destroyed: function() {
|
||||
if (this.faviconDrawerVisible) {
|
||||
this.faviconDrawerVisible = false
|
||||
}
|
||||
if (this.logoDrawerVisible) {
|
||||
this.logoDrawerVisible = false
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
if (this.faviconDrawerVisible) {
|
||||
this.faviconDrawerVisible = false
|
||||
}
|
||||
if (this.logoDrawerVisible) {
|
||||
this.logoDrawerVisible = false
|
||||
}
|
||||
next()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['loadUser']),
|
||||
loadOptions() {
|
||||
...mapActions(['loadUser', 'loadOptions']),
|
||||
loadFormOptions() {
|
||||
optionApi.listAll().then(response => {
|
||||
this.options = response.data.data
|
||||
this.handleAttachChange(this.options['attachment_type'])
|
||||
})
|
||||
},
|
||||
handleSaveOptions() {
|
||||
if (!this.options.blog_title) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '博客标题不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.options.blog_url) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '博客地址不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 新评论通知和回复通知验证
|
||||
if (this.options.comment_new_notice || this.options.comment_reply_notice) {
|
||||
if (!this.options.email_enabled) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '新评论通知或回复通知需要打开和配置 SMTP 服务!'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 附件配置验证
|
||||
switch (this.options.attachment_type) {
|
||||
case 'SMMS':
|
||||
if (!this.options.smms_api_secret_token) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Secret Token不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
case 'UPYUN':
|
||||
if (!this.options.oss_upyun_domain) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '域名不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_upyun_bucket) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '空间名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_upyun_operator) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '操作员名称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_upyun_password) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '操作员密码不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_upyun_source) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '文件目录不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
case 'QNYUN':
|
||||
if (!this.options.oss_qiniu_domain) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '域名不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_qiniu_access_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Access Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_qiniu_secret_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Secret Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_qiniu_bucket) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Bucket 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
case 'ALIYUN':
|
||||
if (!this.options.oss_aliyun_bucket_name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Bucket 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_aliyun_endpoint) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'EndPoint(地域节点) 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_aliyun_access_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Access Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.oss_aliyun_access_secret) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Access Secret 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
case 'BAIDUYUN':
|
||||
if (!this.options.bos_baiduyun_bucket_name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Bucket 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.bos_baiduyun_endpoint) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'EndPoint(地域节点) 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.bos_baiduyun_access_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Access Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.bos_baiduyun_secret_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Secret Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
case 'TENCENTYUN':
|
||||
if (!this.options.cos_tencentyun_bucket_name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Bucket 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.cos_tencentyun_region) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '区域不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.cos_tencentyun_secret_id) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Secret Id 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.cos_tencentyun_secret_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Secret Key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// SMTP 配置验证
|
||||
if (this.options.email_enabled) {
|
||||
if (!this.options.email_host) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'SMTP 地址不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.email_protocol) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '发送协议不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.email_ssl_port) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'SSL 端口不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.email_username) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '邮箱账号不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.email_password) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '邮箱密码不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.options.email_from_name) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '发件人不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// API 配置验证
|
||||
if (this.options.api_enabled) {
|
||||
if (!this.options.api_access_key) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: 'Access key 不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
optionApi.save(this.options).then(response => {
|
||||
this.loadFormOptions()
|
||||
this.loadOptions()
|
||||
this.loadUser()
|
||||
this.$message.success('保存成功!')
|
||||
|
@ -705,47 +1140,60 @@ export default {
|
|||
handleAttachChange(e) {
|
||||
switch (e) {
|
||||
case 'LOCAL':
|
||||
this.upyunFormVisible = false
|
||||
this.qnyunFormVisible = false
|
||||
this.aliyunFormVisible = false
|
||||
this.baiduyunFormVisible = false
|
||||
this.tencentyunFormVisible = false
|
||||
this.smmsFormVisible = false
|
||||
break
|
||||
case 'SMMS':
|
||||
this.upyunFormHidden = false
|
||||
this.qnyunFormHidden = false
|
||||
this.aliyunFormHidden = false
|
||||
this.baiduyunFormHidden = false
|
||||
this.tencentyunFormHidden = false
|
||||
this.smmsFormVisible = true
|
||||
this.upyunFormVisible = false
|
||||
this.qnyunFormVisible = false
|
||||
this.aliyunFormVisible = false
|
||||
this.baiduyunFormVisible = false
|
||||
this.tencentyunFormVisible = false
|
||||
break
|
||||
case 'UPYUN':
|
||||
this.upyunFormHidden = true
|
||||
this.qnyunFormHidden = false
|
||||
this.aliyunFormHidden = false
|
||||
this.baiduyunFormHidden = false
|
||||
this.tencentyunFormHidden = false
|
||||
this.smmsFormVisible = false
|
||||
this.upyunFormVisible = true
|
||||
this.qnyunFormVisible = false
|
||||
this.aliyunFormVisible = false
|
||||
this.baiduyunFormVisible = false
|
||||
this.tencentyunFormVisible = false
|
||||
break
|
||||
case 'QNYUN':
|
||||
this.qnyunFormHidden = true
|
||||
this.upyunFormHidden = false
|
||||
this.aliyunFormHidden = false
|
||||
this.baiduyunFormHidden = false
|
||||
this.tencentyunFormHidden = false
|
||||
this.smmsFormVisible = false
|
||||
this.qnyunFormVisible = true
|
||||
this.upyunFormVisible = false
|
||||
this.aliyunFormVisible = false
|
||||
this.baiduyunFormVisible = false
|
||||
this.tencentyunFormVisible = false
|
||||
break
|
||||
case 'ALIYUN':
|
||||
this.aliyunFormHidden = true
|
||||
this.qnyunFormHidden = false
|
||||
this.upyunFormHidden = false
|
||||
this.baiduyunFormHidden = false
|
||||
this.tencentyunFormHidden = false
|
||||
this.smmsFormVisible = false
|
||||
this.aliyunFormVisible = true
|
||||
this.qnyunFormVisible = false
|
||||
this.upyunFormVisible = false
|
||||
this.baiduyunFormVisible = false
|
||||
this.tencentyunFormVisible = false
|
||||
break
|
||||
case 'BAIDUYUN':
|
||||
this.aliyunFormHidden = false
|
||||
this.qnyunFormHidden = false
|
||||
this.upyunFormHidden = false
|
||||
this.baiduyunFormHidden = true
|
||||
this.tencentyunFormHidden = false
|
||||
this.smmsFormVisible = false
|
||||
this.aliyunFormVisible = false
|
||||
this.qnyunFormVisible = false
|
||||
this.upyunFormVisible = false
|
||||
this.baiduyunFormVisible = true
|
||||
this.tencentyunFormVisible = false
|
||||
break
|
||||
case 'TENCENTYUN':
|
||||
this.aliyunFormHidden = false
|
||||
this.qnyunFormHidden = false
|
||||
this.upyunFormHidden = false
|
||||
this.baiduyunFormHidden = false
|
||||
this.tencentyunFormHidden = true
|
||||
this.smmsFormVisible = false
|
||||
this.aliyunFormVisible = false
|
||||
this.qnyunFormVisible = false
|
||||
this.upyunFormVisible = false
|
||||
this.baiduyunFormVisible = false
|
||||
this.tencentyunFormVisible = true
|
||||
break
|
||||
}
|
||||
},
|
||||
|
@ -754,6 +1202,27 @@ export default {
|
|||
this.logoDrawerVisible = false
|
||||
},
|
||||
handleTestMailClick() {
|
||||
if (!this.mailParam.to) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '收件人不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.mailParam.subject) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '主题不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.mailParam.content) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '内容不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
mailApi.testMail(this.mailParam).then(response => {
|
||||
this.$message.info(response.data.message)
|
||||
})
|
||||
|
|
|
@ -27,20 +27,16 @@
|
|||
title="Markdown 文章导入"
|
||||
v-model="markdownUpload"
|
||||
:footer="null"
|
||||
destroyOnClose
|
||||
:afterClose="onUploadClose"
|
||||
>
|
||||
<upload
|
||||
name="files"
|
||||
multiple
|
||||
<FilePondUpload
|
||||
ref="upload"
|
||||
name="file"
|
||||
accept="text/markdown"
|
||||
label="拖拽或点击选择 Markdown 文件到此处"
|
||||
:uploadHandler="uploadHandler"
|
||||
@change="handleChange"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<a-icon type="inbox" />
|
||||
</p>
|
||||
<p class="ant-upload-text">拖拽或点击选择 Markdown 文件到此处</p>
|
||||
<p class="ant-upload-hint">支持多个文件同时上传</p>
|
||||
</upload>
|
||||
></FilePondUpload>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,10 +65,10 @@ export default {
|
|||
} else if (status === 'error') {
|
||||
this.$message.error(`${info.file.name} 导入失败!`)
|
||||
}
|
||||
},
|
||||
onUploadClose() {
|
||||
this.$refs.upload.handleClearFileList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="loginLogo animated fadeInUp">
|
||||
<div class="container-wrapper">
|
||||
<div class="halo-logo animated fadeInUp">
|
||||
<span>Halo</span>
|
||||
</div>
|
||||
<div class="loginBody animated">
|
||||
<div class="animated">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
@keyup.enter.native="handleLogin"
|
||||
|
@ -39,17 +39,27 @@
|
|||
/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.3s'}"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
:block="true"
|
||||
@click="handleLogin"
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.3s'}"
|
||||
>登录</a-button>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-row>
|
||||
<router-link :to="{ name:'ResetPassword' }">
|
||||
<a
|
||||
class="tip animated fadeInRight"
|
||||
v-if="resetPasswordButton"
|
||||
href="javascript:void(0);"
|
||||
>
|
||||
找回密码
|
||||
</a>
|
||||
</router-link>
|
||||
<a
|
||||
@click="handleApiModifyModalOpen"
|
||||
class="tip animated fadeInUp"
|
||||
|
@ -61,7 +71,7 @@
|
|||
|
||||
<a-modal
|
||||
title="API 设置"
|
||||
:visible="apiModifyVisiable"
|
||||
:visible="apiModifyVisible"
|
||||
@ok="handleApiModifyOk"
|
||||
@cancel="handleApiModifyCancel"
|
||||
>
|
||||
|
@ -90,16 +100,25 @@ export default {
|
|||
return {
|
||||
username: null,
|
||||
password: null,
|
||||
apiModifyVisiable: false,
|
||||
apiModifyVisible: false,
|
||||
defaultApiBefore: window.location.protocol + '//',
|
||||
apiUrl: window.location.host
|
||||
apiUrl: window.location.host,
|
||||
resetPasswordButton: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ defaultApiUrl: 'apiUrl' })
|
||||
},
|
||||
created() {
|
||||
const _this = this
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.keyCode === 72 && e.altKey && e.shiftKey) {
|
||||
_this.toggleHidden()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login', 'loadUser']),
|
||||
...mapActions(['login', 'loadUser', 'loadOptions']),
|
||||
...mapMutations({
|
||||
setApiUrl: 'SET_API_URL',
|
||||
restoreApiUrl: 'RESTORE_API_URL'
|
||||
|
@ -123,6 +142,7 @@ export default {
|
|||
loginSuccess() {
|
||||
// Cache the user info
|
||||
this.loadUser()
|
||||
this.loadOptions()
|
||||
if (this.$route.query.redirect) {
|
||||
this.$router.replace(this.$route.query.redirect)
|
||||
} else {
|
||||
|
@ -131,18 +151,21 @@ export default {
|
|||
},
|
||||
handleApiModifyModalOpen() {
|
||||
this.apiUrl = this.defaultApiUrl
|
||||
this.apiModifyVisiable = true
|
||||
this.apiModifyVisible = true
|
||||
},
|
||||
handleApiModifyOk() {
|
||||
this.setApiUrl(this.apiUrl)
|
||||
this.apiModifyVisiable = false
|
||||
this.apiModifyVisible = false
|
||||
},
|
||||
handleApiModifyCancel() {
|
||||
this.apiModifyVisiable = false
|
||||
this.apiModifyVisible = false
|
||||
},
|
||||
handleApiUrlRestore() {
|
||||
this.restoreApiUrl()
|
||||
this.apiUrl = this.defaultApiUrl
|
||||
},
|
||||
toggleHidden() {
|
||||
this.resetPasswordButton = !this.resetPasswordButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,40 +175,41 @@ body {
|
|||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: #f7f7f7;
|
||||
|
||||
.container-wrapper {
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
margin: -160px 0 0 -160px;
|
||||
width: 320px;
|
||||
padding: 16px 32px 32px 32px;
|
||||
padding: 18px 28px 28px 28px;
|
||||
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
|
||||
.tip {
|
||||
cursor: pointer;
|
||||
margin-top: .5rem;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.loginLogo {
|
||||
|
||||
.halo-logo {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.loginLogo span {
|
||||
span {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 36px;
|
||||
font-size: 38px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
color: #1790fe;
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
37.219838% 34.532506%,
|
||||
36.425669% 93.178216%,
|
||||
from(#36c8f5),
|
||||
to(#1790fe),
|
||||
color-stop(0.37, #1790fe)
|
||||
);
|
||||
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
small {
|
||||
margin-left: 5px;
|
||||
font-size: 35%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
:md="24"
|
||||
:style="{ 'padding-bottom': '12px' }"
|
||||
>
|
||||
<a-card :bordered="false">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:bodyStyle="{ padding: '16px' }"
|
||||
>
|
||||
<div class="profile-center-avatarHolder">
|
||||
<a-tooltip
|
||||
placement="right"
|
||||
|
@ -149,8 +152,7 @@
|
|||
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
|
||||
import userApi from '@/api/user'
|
||||
import adminApi from '@/api/admin'
|
||||
import optionApi from '@/api/option'
|
||||
import { mapMutations } from 'vuex'
|
||||
import { mapMutations, mapGetters } from 'vuex'
|
||||
import MD5 from 'md5.js'
|
||||
|
||||
export default {
|
||||
|
@ -168,20 +170,18 @@ export default {
|
|||
newPassword: null,
|
||||
confirmPassword: null
|
||||
},
|
||||
attachment: {},
|
||||
options: [],
|
||||
keys: ['blog_url']
|
||||
attachment: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
passwordUpdateButtonDisabled() {
|
||||
return !(this.passwordParam.oldPassword && this.passwordParam.newPassword)
|
||||
}
|
||||
},
|
||||
...mapGetters(['options'])
|
||||
},
|
||||
created() {
|
||||
this.loadUser()
|
||||
this.getCounts()
|
||||
this.loadOptions()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({ setUser: 'SET_USER' }),
|
||||
|
@ -191,11 +191,6 @@ export default {
|
|||
this.profileLoading = false
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
optionApi.listAll(this.keys).then(response => {
|
||||
this.options = response.data.data
|
||||
})
|
||||
},
|
||||
getCounts() {
|
||||
adminApi.counts().then(response => {
|
||||
this.counts = response.data.data
|
||||
|
@ -208,9 +203,35 @@ export default {
|
|||
this.$message.error('确认密码和新密码不匹配!')
|
||||
return
|
||||
}
|
||||
userApi.updatePassword(this.passwordParam.oldPassword, this.passwordParam.newPassword).then(response => {})
|
||||
userApi.updatePassword(this.passwordParam.oldPassword, this.passwordParam.newPassword).then(response => {
|
||||
this.$message.success('密码修改成功!')
|
||||
this.passwordParam.oldPassword = null
|
||||
this.passwordParam.newPassword = null
|
||||
this.passwordParam.confirmPassword = null
|
||||
})
|
||||
},
|
||||
handleUpdateProfile() {
|
||||
if (!this.user.username) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '用户名不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.user.nickname) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '用户昵称不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.user.email) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '邮箱不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
userApi.updateProfile(this.user).then(response => {
|
||||
this.user = response.data.data
|
||||
this.setUser(Object.assign({}, this.user))
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
<template>
|
||||
<div class="container-wrapper">
|
||||
<div class="halo-logo animated fadeInUp">
|
||||
<span>Halo<small>重置密码</small></span>
|
||||
</div>
|
||||
<div class="animated">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.1s'}"
|
||||
>
|
||||
<a-input
|
||||
placeholder="用户名"
|
||||
v-model="resetParam.username"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
type="user"
|
||||
style="color: rgba(0,0,0,.25)"
|
||||
/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.2s'}"
|
||||
>
|
||||
<a-input
|
||||
placeholder="邮箱"
|
||||
v-model="resetParam.email"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
type="mail"
|
||||
style="color: rgba(0,0,0,.25)"
|
||||
/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.3s'}"
|
||||
>
|
||||
<a-input
|
||||
v-model="resetParam.code"
|
||||
type="password"
|
||||
placeholder="验证码"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
type="safety-certificate"
|
||||
style="color: rgba(0,0,0,.25)"
|
||||
/>
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
slot="addonAfter"
|
||||
@click="handleSendCode"
|
||||
>
|
||||
获取
|
||||
</a>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.4s'}"
|
||||
>
|
||||
<a-input
|
||||
v-model="resetParam.password"
|
||||
type="password"
|
||||
placeholder="新密码"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
type="lock"
|
||||
style="color: rgba(0,0,0,.25)"
|
||||
/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.5s'}"
|
||||
>
|
||||
<a-input
|
||||
v-model="resetParam.confirmPassword"
|
||||
type="password"
|
||||
placeholder="确认密码"
|
||||
>
|
||||
<a-icon
|
||||
slot="prefix"
|
||||
type="lock"
|
||||
style="color: rgba(0,0,0,.25)"
|
||||
/>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
class="animated fadeInUp"
|
||||
:style="{'animation-delay': '0.6s'}"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
:block="true"
|
||||
@click="handleResetPassword"
|
||||
>重置密码</a-button>
|
||||
</a-form-item>
|
||||
|
||||
<a-row>
|
||||
<router-link :to="{ name:'Login' }">
|
||||
<a
|
||||
class="tip animated fadeInUp"
|
||||
:style="{'animation-delay': '0.7s'}"
|
||||
>
|
||||
返回登陆
|
||||
</a>
|
||||
</router-link>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import adminApi from '@/api/admin'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
resetParam: {
|
||||
username: '',
|
||||
email: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSendCode() {
|
||||
if (!this.resetParam.username) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '用户名不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.resetParam.email) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '邮箱不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
adminApi.sendResetCode(this.resetParam).then(response => {
|
||||
this.$message.info('邮件发送成功,五分钟内有效')
|
||||
})
|
||||
},
|
||||
handleResetPassword() {
|
||||
if (!this.resetParam.username) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '用户名不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.resetParam.email) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '邮箱不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.resetParam.code) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '验证码不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.resetParam.password) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '新密码不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.resetParam.confirmPassword) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '确认密码不能为空!'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (this.resetParam.confirmPassword !== this.resetParam.password) {
|
||||
this.$notification['error']({
|
||||
message: '提示',
|
||||
description: '确认密码和新密码不匹配!'
|
||||
})
|
||||
return
|
||||
}
|
||||
adminApi.resetPassword(this.resetParam).then(response => {
|
||||
this.$message.info('密码重置成功!')
|
||||
this.$router.push({ name: 'Login' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
body {
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container-wrapper {
|
||||
background: #ffffff;
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
margin: -160px 0 0 -160px;
|
||||
width: 320px;
|
||||
padding: 18px 28px 28px 28px;
|
||||
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.halo-logo {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
span {
|
||||
vertical-align: text-bottom;
|
||||
font-size: 38px;
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
color: #1790fe;
|
||||
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
small {
|
||||
margin-left: 5px;
|
||||
font-size: 35%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
cursor: pointer;
|
||||
margin-left: 0.5rem;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,30 +1,14 @@
|
|||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
// const GenerateAssetPlugin = require('generate-asset-webpack-plugin')
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
// var createServerConfig = function(compilation) {
|
||||
// const configJson = {
|
||||
// apiUrl: 'http://localhost:8090'
|
||||
// }
|
||||
// return JSON.stringify(configJson)
|
||||
// }
|
||||
|
||||
// vue.config.js
|
||||
module.exports = {
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
|
||||
// new GenerateAssetPlugin({
|
||||
// filename: 'config.json',
|
||||
// fn: (compilation, cb) => {
|
||||
// cb(null, createServerConfig(compilation))
|
||||
// },
|
||||
// extraFiles: []
|
||||
// })
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -58,13 +42,6 @@ module.exports = {
|
|||
css: {
|
||||
loaderOptions: {
|
||||
less: {
|
||||
modifyVars: {
|
||||
/*
|
||||
'primary-color': '#F5222D',
|
||||
'link-color': '#F5222D',
|
||||
'border-radius-base': '4px',
|
||||
*/
|
||||
},
|
||||
javascriptEnabled: true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue