Merge pull request #62 from halo-dev/dev

sync
pull/64/head
Ryan Wang 2020-02-16 15:49:21 +08:00 committed by GitHub
commit ea6d157df3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1020 additions and 296 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
NODE_ENV=production
PUBLIC_PATH=https://cdn.jsdelivr.net/npm/halo-admin@1.2.0/dist/

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
NODE_ENV=development
PUBLIC_PATH=/

View File

@ -124,4 +124,23 @@ postApi.postStatus = {
text: '私密' text: '私密'
} }
} }
postApi.permalinkType = {
DEFAULT: {
type: 'DEFAULT',
text: '默认'
},
DATE: {
type: 'DATE',
text: '年月型'
},
DAY: {
type: 'DAY',
text: '年月日型'
},
ID: {
type: 'ID',
text: 'ID 型'
}
}
export default postApi export default postApi

39
src/api/staticPage.js Normal file
View File

@ -0,0 +1,39 @@
import service from '@/utils/service'
const baseUrl = '/api/admin/static_page'
const staticPageApi = {}
staticPageApi.list = () => {
return service({
url: baseUrl,
method: 'get'
})
}
staticPageApi.generate = () => {
return service({
url: `${baseUrl}/generate`,
method: 'get'
})
}
staticPageApi.deploy = () => {
return service({
url: `${baseUrl}/deploy`,
method: 'get'
})
}
staticPageApi.deployType = {
GIT: {
type: 'GIT',
text: 'Git'
},
NETLIFY: {
type: 'NETLIFY',
text: 'Netlify'
}
}
export default staticPageApi

View File

@ -79,7 +79,7 @@ export default {
const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])] const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])]
const itemArr = [] const itemArr = []
const pIndex_ = pIndex + '_' + index const pIndex_ = pIndex + '_' + index
console.log('menu', menu) this.$log.debug('menu', menu)
if (!menu.hideChildrenInMenu) { if (!menu.hideChildrenInMenu) {
menu.children.forEach(function(item, i) { menu.children.forEach(function(item, i) {
itemArr.push(this2_.renderItem(h, item, pIndex_, i)) itemArr.push(this2_.renderItem(h, item, pIndex_, i))

View File

@ -120,7 +120,7 @@ export default {
}, },
methods: { methods: {
handleFilePondInit() { handleFilePondInit() {
console.log('FilePond has initialized') this.$log.debug('FilePond has initialized')
}, },
handleClearFileList() { handleClearFileList() {
this.$refs.pond.removeFiles() this.$refs.pond.removeFiles()

View File

@ -911,4 +911,8 @@ body {
img { img {
width: 100%; width: 100%;
} }
}
.ant-input-group-addon {
line-height: initial !important;
} }

View File

@ -185,6 +185,13 @@ export const asyncRouterMap = [
component: () => import('@/views/system/ToolList'), component: () => import('@/views/system/ToolList'),
meta: { title: '小工具', hiddenHeaderContent: false } meta: { title: '小工具', hiddenHeaderContent: false }
}, },
{
path: '/system/tools/staticpages',
name: 'StaticPagesManage',
hidden: true,
component: () => import('@/views/system/staticpages/StaticPagesManage'),
meta: { title: '静态部署', hiddenHeaderContent: false }
},
{ {
path: '/system/about', path: '/system/about',
name: 'About', name: 'About',

View File

@ -8,7 +8,11 @@ const keys = [
'developer_mode', 'developer_mode',
'attachment_upload_image_preview_enable', 'attachment_upload_image_preview_enable',
'attachment_upload_max_parallel_uploads', 'attachment_upload_max_parallel_uploads',
'attachment_upload_max_files' 'attachment_upload_max_files',
'sheet_prefix',
'post_permalink_type',
'archives_prefix',
'path_suffix'
] ]
const option = { const option = {
state: { state: {

View File

@ -22,6 +22,14 @@ Vue.filter('moment', function(dataStr, pattern = 'YYYY-MM-DD HH:mm') {
return moment(dataStr).format(pattern) return moment(dataStr).format(pattern)
}) })
Vue.filter('moment_post_date', function(dataStr, pattern = '/YYYY/M/') {
return moment(dataStr).format(pattern)
})
Vue.filter('moment_post_day', function(dataStr, pattern = '/YYYY/M/D/') {
return moment(dataStr).format(pattern)
})
Vue.filter('timeAgo', timeAgo) Vue.filter('timeAgo', timeAgo)
Vue.filter('fileSizeFormat', function(value) { Vue.filter('fileSizeFormat', function(value) {

View File

@ -2,7 +2,7 @@ export function actionToObject(json) {
try { try {
return JSON.parse(json) return JSON.parse(json)
} catch (e) { } catch (e) {
console.log('err', e.message) this.$log.debug('err', e.message)
} }
return [] return []
} }

View File

@ -269,11 +269,11 @@ export default {
const text = `${encodeURI(this.attachment.path)}` const text = `${encodeURI(this.attachment.path)}`
this.$copyText(text) this.$copyText(text)
.then(message => { .then(message => {
console.log('copy', message) this.$log.debug('copy', message)
this.$message.success('复制成功!') this.$message.success('复制成功!')
}) })
.catch(err => { .catch(err => {
console.log('copy.err', err) this.$log.debug('copy.err', err)
this.$message.error('复制失败!') this.$message.error('复制失败!')
}) })
}, },
@ -281,11 +281,11 @@ export default {
const text = `![${this.attachment.name}](${encodeURI(this.attachment.path)})` const text = `![${this.attachment.name}](${encodeURI(this.attachment.path)})`
this.$copyText(text) this.$copyText(text)
.then(message => { .then(message => {
console.log('copy', message) this.$log.debug('copy', message)
this.$message.success('复制成功!') this.$message.success('复制成功!')
}) })
.catch(err => { .catch(err => {
console.log('copy.err', err) this.$log.debug('copy.err', err)
this.$message.error('复制失败!') this.$message.error('复制失败!')
}) })
}, },

View File

@ -56,13 +56,13 @@
<a <a
slot="description" slot="description"
target="_blank" target="_blank"
:href="options.blog_url+'/archives/'+comment.post.url" :href="comment.post.fullPath"
v-if="this.type=='posts'" v-if="this.type=='posts'"
>{{ comment.post.title }}</a> >{{ comment.post.title }}</a>
<a <a
slot="description" slot="description"
target="_blank" target="_blank"
:href="options.blog_url+'/s/'+comment.sheet.url" :href="comment.sheet.fullPath"
v-else-if="this.type=='sheets'" v-else-if="this.type=='sheets'"
>{{ comment.sheet.title }}</a> >{{ comment.sheet.title }}</a>
<span <span
@ -125,7 +125,6 @@
</template> </template>
<script> <script>
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import { mapGetters } from 'vuex'
import commentApi from '@/api/comment' import commentApi from '@/api/comment'
export default { export default {
name: 'CommentDetail', name: 'CommentDetail',
@ -136,7 +135,6 @@ export default {
detailLoading: true, detailLoading: true,
editable: false, editable: false,
commentStatus: commentApi.commentStatus, commentStatus: commentApi.commentStatus,
options: [],
keys: ['blog_url'] keys: ['blog_url']
} }
}, },
@ -163,9 +161,6 @@ export default {
} }
} }
}, },
computed: {
...mapGetters(['options'])
},
watch: { watch: {
visible: function(newValue, oldValue) { visible: function(newValue, oldValue) {
this.$log.debug('old value', oldValue) this.$log.debug('old value', oldValue)

View File

@ -187,12 +187,12 @@
发表在 发表在
<a <a
v-if="type==='posts'" v-if="type==='posts'"
:href="options.blog_url+'/archives/'+item.post.url" :href="item.post.fullPath"
target="_blank" target="_blank"
>{{ item.post.title }}</a> >{{ item.post.title }}</a>
<a <a
v-if="type === 'sheets'" v-if="type === 'sheets'"
:href="options.blog_url+'/s/'+item.sheet.url" :href="item.sheet.fullPath"
target="_blank" target="_blank"
>{{ item.sheet.title }}</a> >{{ item.sheet.title }}</a>
</template> </template>
@ -282,14 +282,14 @@
v-if="type==='posts'" v-if="type==='posts'"
slot="post" slot="post"
slot-scope="post" slot-scope="post"
:href="options.blog_url+'/archives/'+post.url" :href="post.fullPath"
target="_blank" target="_blank"
>{{ post.title }}</a> >{{ post.title }}</a>
<a <a
v-if="type === 'sheets'" v-if="type === 'sheets'"
slot="sheet" slot="sheet"
slot-scope="sheet" slot-scope="sheet"
:href="options.blog_url+'/s/'+sheet.url" :href="sheet.fullPath"
target="_blank" target="_blank"
>{{ sheet.title }}</a> >{{ sheet.title }}</a>
<span <span
@ -429,7 +429,6 @@
</template> </template>
<script> <script>
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import { mapGetters } from 'vuex'
import CommentDetail from './CommentDetail' import CommentDetail from './CommentDetail'
import marked from 'marked' import marked from 'marked'
import commentApi from '@/api/comment' import commentApi from '@/api/comment'
@ -561,8 +560,7 @@ export default {
comment.content = marked(comment.content) comment.content = marked(comment.content)
return comment return comment
}) })
}, }
...mapGetters(['options'])
}, },
methods: { methods: {
loadComments() { loadComments() {

View File

@ -124,7 +124,7 @@
<a <a
v-if="item.status=='PUBLISHED' || item.status == 'INTIMATE'" v-if="item.status=='PUBLISHED' || item.status == 'INTIMATE'"
slot="title" slot="title"
:href="options.blog_url+'/archives/'+item.url" :href="item.fullPath"
target="_blank" target="_blank"
>{{ item.title }}</a> >{{ item.title }}</a>
<a <a
@ -322,7 +322,6 @@
<script> <script>
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import { mapGetters } from 'vuex'
import { PageView } from '@/layouts' import { PageView } from '@/layouts'
import AnalysisCard from './components/AnalysisCard' import AnalysisCard from './components/AnalysisCard'
import RecentCommentTab from './components/RecentCommentTab' import RecentCommentTab from './components/RecentCommentTab'
@ -396,8 +395,7 @@ export default {
log.type = this.logType[log.type].text log.type = this.logType[log.type].text
return log return log
}) })
}, }
...mapGetters(['options'])
}, },
destroyed: function() { destroyed: function() {
if (this.logDrawerVisible) { if (this.logDrawerVisible) {

View File

@ -19,7 +19,7 @@
target="_blank" target="_blank"
>{{ item.author }}</a> 发表在 <a >{{ item.author }}</a> 发表在 <a
v-if="item.post.status=='PUBLISHED' || item.post.status=='INTIMATE'" v-if="item.post.status=='PUBLISHED' || item.post.status=='INTIMATE'"
:href="options.blog_url+'/archives/'+item.post.url" :href="item.post.fullPath"
target="_blank" target="_blank"
>{{ item.post.title }}</a><a >{{ item.post.title }}</a><a
v-else-if="item.post.status=='DRAFT'" v-else-if="item.post.status=='DRAFT'"
@ -40,7 +40,7 @@
target="_blank" target="_blank"
>{{ item.author }}</a> 发表在 <a >{{ item.author }}</a> 发表在 <a
v-if="item.sheet.status=='PUBLISHED'" v-if="item.sheet.status=='PUBLISHED'"
:href="options.blog_url+'/s/'+item.sheet.url" :href="item.sheet.fullPath"
target="_blank" target="_blank"
>{{ item.sheet.title }}</a><a >{{ item.sheet.title }}</a><a
v-else-if="item.sheet.status=='DRAFT'" v-else-if="item.sheet.status=='DRAFT'"
@ -71,7 +71,6 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import commentApi from '@/api/comment' import commentApi from '@/api/comment'
import postApi from '@/api/post' import postApi from '@/api/post'
import sheetApi from '@/api/sheet' import sheetApi from '@/api/sheet'
@ -101,8 +100,7 @@ export default {
comment.content = marked(comment.content) comment.content = marked(comment.content)
return comment return comment
}) })
}, }
...mapGetters(['options'])
}, },
created() { created() {
this.loadComments() this.loadComments()

View File

@ -32,6 +32,20 @@
v-model="categoryToCreate.parentId" v-model="categoryToCreate.parentId"
/> />
</a-form-item> </a-form-item>
<a-form-item
label="封面图"
help="* 在分类页面可展示,需要主题支持"
>
<a-input v-model="categoryToCreate.thumbnail">
<a
href="javascript:void(0);"
slot="addonAfter"
@click="()=>this.thumbnailDrawerVisible = true"
>
<a-icon type="picture" />
</a>
</a-input>
</a-form-item>
<a-form-item <a-form-item
label="描述:" label="描述:"
help="* 分类描述,部分主题可显示" help="* 分类描述,部分主题可显示"
@ -206,12 +220,19 @@
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
<AttachmentSelectDrawer
v-model="thumbnailDrawerVisible"
@listenToSelect="handleSelectThumbnail"
title="选择封面图"
/>
</div> </div>
</template> </template>
<script> <script>
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import CategorySelectTree from './components/CategorySelectTree' import CategorySelectTree from './components/CategorySelectTree'
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
import categoryApi from '@/api/category' import categoryApi from '@/api/category'
import menuApi from '@/api/menu' import menuApi from '@/api/menu'
@ -239,13 +260,14 @@ const columns = [
} }
] ]
export default { export default {
components: { CategorySelectTree }, components: { CategorySelectTree, AttachmentSelectDrawer },
mixins: [mixin, mixinDevice], mixins: [mixin, mixinDevice],
data() { data() {
return { return {
formType: 'create', formType: 'create',
categories: [], categories: [],
categoryToCreate: {}, categoryToCreate: {},
thumbnailDrawerVisible: false,
menu: {}, menu: {},
loading: false, loading: false,
columns columns
@ -311,11 +333,15 @@ export default {
}, },
handleCategoryToMenu(category) { handleCategoryToMenu(category) {
this.menu['name'] = category.name this.menu['name'] = category.name
this.menu['url'] = `/categories/${category.slugName}` this.menu['url'] = `${category.fullPath}`
menuApi.create(this.menu).then(response => { menuApi.create(this.menu).then(response => {
this.$message.success('添加到菜单成功!') this.$message.success('添加到菜单成功!')
this.menu = {} this.menu = {}
}) })
},
handleSelectThumbnail(data) {
this.$set(this.categoryToCreate, 'thumbnail', encodeURI(data.path))
this.thumbnailDrawerVisible = false
} }
} }
} }

View File

@ -237,7 +237,7 @@
/> />
<a <a
v-if="item.status=='PUBLISHED' || item.status == 'INTIMATE'" v-if="item.status=='PUBLISHED' || item.status == 'INTIMATE'"
:href="options.blog_url+'/archives/'+item.url" :href="item.fullPath"
target="_blank" target="_blank"
style="text-decoration: none;" style="text-decoration: none;"
> >
@ -318,7 +318,7 @@
/> />
<a <a
v-if="record.status=='PUBLISHED' || record.status == 'INTIMATE'" v-if="record.status=='PUBLISHED' || record.status == 'INTIMATE'"
:href="options.blog_url+'/archives/'+record.url" :href="record.fullPath"
target="_blank" target="_blank"
style="text-decoration: none;" style="text-decoration: none;"
> >
@ -518,7 +518,6 @@ import TargetCommentDrawer from '../comment/components/TargetCommentDrawer'
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer' import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
import TagSelect from './components/TagSelect' import TagSelect from './components/TagSelect'
import CategoryTree from './components/CategoryTree' import CategoryTree from './components/CategoryTree'
import { mapGetters } from 'vuex'
import categoryApi from '@/api/category' import categoryApi from '@/api/category'
import postApi from '@/api/post' import postApi from '@/api/post'
const columns = [ const columns = [
@ -620,8 +619,7 @@ export default {
post.statusProperty = this.postStatus[post.status] post.statusProperty = this.postStatus[post.status]
return post return post
}) })
}, }
...mapGetters(['options'])
}, },
created() { created() {
this.loadPosts() this.loadPosts()

View File

@ -9,7 +9,10 @@
:xs="24" :xs="24"
:style="{ 'padding-bottom': '12px' }" :style="{ 'padding-bottom': '12px' }"
> >
<a-card :title="title" :bodyStyle="{ padding: '16px' }"> <a-card
:title="title"
:bodyStyle="{ padding: '16px' }"
>
<a-form layout="horizontal"> <a-form layout="horizontal">
<a-form-item <a-form-item
label="名称:" label="名称:"
@ -23,6 +26,20 @@
> >
<a-input v-model="tagToCreate.slugName" /> <a-input v-model="tagToCreate.slugName" />
</a-form-item> </a-form-item>
<a-form-item
label="封面图"
help="* 在标签页面可展示,需要主题支持"
>
<a-input v-model="tagToCreate.thumbnail">
<a
href="javascript:void(0);"
slot="addonAfter"
@click="()=>this.thumbnailDrawerVisible = true"
>
<a-icon type="picture" />
</a>
</a-input>
</a-form-item>
<a-form-item> <a-form-item>
<a-button <a-button
type="primary" type="primary"
@ -64,8 +81,11 @@
:xs="24" :xs="24"
:style="{ 'padding-bottom': '12px' }" :style="{ 'padding-bottom': '12px' }"
> >
<a-card title="所有标签" :bodyStyle="{ padding: '16px' }"> <a-card
<a-empty v-if="tags.length==0"/> title="所有标签"
:bodyStyle="{ padding: '16px' }"
>
<a-empty v-if="tags.length==0" />
<a-tooltip <a-tooltip
placement="topLeft" placement="topLeft"
v-for="tag in tags" v-for="tag in tags"
@ -84,18 +104,27 @@
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
<AttachmentSelectDrawer
v-model="thumbnailDrawerVisible"
@listenToSelect="handleSelectThumbnail"
title="选择封面图"
/>
</div> </div>
</template> </template>
<script> <script>
import tagApi from '@/api/tag' import tagApi from '@/api/tag'
import AttachmentSelectDrawer from '../attachment/components/AttachmentSelectDrawer'
export default { export default {
components: { AttachmentSelectDrawer },
data() { data() {
return { return {
formType: 'create', formType: 'create',
tags: [], tags: [],
tagToCreate: {} tagToCreate: {},
thumbnailDrawerVisible: false
} }
}, },
computed: { computed: {
@ -155,6 +184,10 @@ export default {
}) })
} }
this.handleAddTag() this.handleAddTag()
},
handleSelectThumbnail(data) {
this.$set(this.tagToCreate, 'thumbnail', encodeURI(data.path))
this.thumbnailDrawerVisible = false
} }
} }
} }

View File

@ -24,10 +24,13 @@
> >
<a-input v-model="selectedPost.title" /> <a-input v-model="selectedPost.title" />
</a-form-item> </a-form-item>
<a-form-item <a-form-item label="文章路径:">
label="文章路径:" <template slot="help">
:help="options.blog_url+'/archives/' + (selectedPost.url ? selectedPost.url : '{auto_generate}')" <span v-if="options.post_permalink_type === 'DEFAULT'">{{ options.blog_url }}/{{ options.archives_prefix }}/{{ selectedPost.url?selectedPost.url:'${url}' }}{{ options.path_suffix }}</span>
> <span v-else-if="options.post_permalink_type === 'DATE'">{{ options.blog_url }}{{ selectedPost.createTime?selectedPost.createTime:new Date() | moment_post_date }}{{ selectedPost.url?selectedPost.url:'${url}' }}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'DAY'">{{ options.blog_url }}{{ selectedPost.createTime?selectedPost.createTime:new Date() | moment_post_day }}{{ selectedPost.url?selectedPost.url:'${url}' }}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'ID'">{{ options.blog_url }}/?p={{ selectedPost.id?selectedPost.id:'${id}' }}</span>
</template>
<a-input v-model="selectedPost.url" /> <a-input v-model="selectedPost.url" />
</a-form-item> </a-form-item>
<a-form-item label="访问密码:"> <a-form-item label="访问密码:">
@ -164,7 +167,7 @@
<a-divider /> <a-divider />
<div :style="{ marginBottom: '16px' }"> <div :style="{ marginBottom: '16px' }">
<h3 class="post-setting-drawer-title">缩略</h3> <h3 class="post-setting-drawer-title">封面</h3>
<div class="post-setting-drawer-item"> <div class="post-setting-drawer-item">
<div class="post-thumb"> <div class="post-thumb">
<img <img
@ -177,7 +180,7 @@
<a-form-item> <a-form-item>
<a-input <a-input
v-model="selectedPost.thumbnail" v-model="selectedPost.thumbnail"
placeholder="点击缩略图选择图片,或者输入外部链接" placeholder="点击封面图选择图片,或者输入外部链接"
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-form> </a-form>

View File

@ -62,12 +62,12 @@
style="max-width: 300px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;" style="max-width: 300px;display: block;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
> >
<a <a
:href="options.blog_url+item.url" :href="item.url"
target="_blank" target="_blank"
v-if="item.status" v-if="item.status"
>{{ item.title }}</a> >{{ item.title }}</a>
<a <a
:href="options.blog_url+item.url" :href="item.url"
target="_blank" target="_blank"
disabled disabled
v-else v-else
@ -124,12 +124,12 @@
</router-link> </router-link>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a <a
:href="options.blog_url+record.url" :href="record.url"
target="_blank" target="_blank"
v-if="record.status" v-if="record.status"
>访问</a> >访问</a>
<a <a
:href="options.blog_url+record.url" :href="record.url"
target="_blank" target="_blank"
disabled disabled
v-else v-else
@ -247,7 +247,7 @@
> >
<a <a
v-if="item.status=='PUBLISHED'" v-if="item.status=='PUBLISHED'"
:href="options.blog_url+'/archives/'+item.url" :href="item.fullPath"
target="_blank" target="_blank"
style="text-decoration: none;" style="text-decoration: none;"
> >
@ -298,7 +298,7 @@
> >
<a <a
v-if="record.status=='PUBLISHED'" v-if="record.status=='PUBLISHED'"
:href="options.blog_url+'/s/'+record.url" :href="record.fullPath"
target="_blank" target="_blank"
style="text-decoration: none;" style="text-decoration: none;"
> >
@ -497,7 +497,7 @@ const internalColumns = [
dataIndex: 'title' dataIndex: 'title'
}, },
{ {
title: '访问路径', title: '访问地址',
dataIndex: 'url' dataIndex: 'url'
}, },
{ {
@ -637,7 +637,7 @@ export default {
}, },
handleSheetToMenu(sheet) { handleSheetToMenu(sheet) {
this.menu['name'] = sheet.title this.menu['name'] = sheet.title
this.menu['url'] = `/s/${sheet.url}` this.menu['url'] = `${sheet.fullPath}`
menuApi.create(this.menu).then(response => { menuApi.create(this.menu).then(response => {
this.$message.success('添加到菜单成功!') this.$message.success('添加到菜单成功!')
this.menu = {} this.menu = {}

View File

@ -26,7 +26,7 @@
</a-form-item> </a-form-item>
<a-form-item <a-form-item
label="页面路径:" label="页面路径:"
:help="options.blog_url+'/s/'+ (selectedSheet.url ? selectedSheet.url : '{auto_generate}')" :help="options.blog_url+'/'+options.sheet_prefix+'/'+ (selectedSheet.url ? selectedSheet.url : '{auto_generate}')"
> >
<a-input v-model="selectedSheet.url" /> <a-input v-model="selectedSheet.url" />
</a-form-item> </a-form-item>
@ -85,7 +85,7 @@
<a-divider /> <a-divider />
<div :style="{ marginBottom: '16px' }"> <div :style="{ marginBottom: '16px' }">
<h3 class="post-setting-drawer-title">缩略</h3> <h3 class="post-setting-drawer-title">封面</h3>
<div class="post-setting-drawer-item"> <div class="post-setting-drawer-item">
<div class="sheet-thumb"> <div class="sheet-thumb">
<img <img
@ -98,7 +98,7 @@
<a-form-item> <a-form-item>
<a-input <a-input
v-model="selectedSheet.thumbnail" v-model="selectedSheet.thumbnail"
placeholder="点击缩略图选择图片,或者输入外部链接" placeholder="点击封面图选择图片,或者输入外部链接"
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-form> </a-form>

View File

@ -126,7 +126,10 @@
<a-list-item-meta> <a-list-item-meta>
<template slot="description"> <template slot="description">
<p v-html="item.content" class="journal-list-content"></p> <p
v-html="item.content"
class="journal-list-content"
></p>
</template> </template>
<span slot="title">{{ item.createTime | moment }}</span> <span slot="title">{{ item.createTime | moment }}</span>
<a-avatar <a-avatar
@ -154,6 +157,40 @@
</a-col> </a-col>
</a-row> </a-row>
<div style="position: fixed;bottom: 30px;right: 30px;">
<a-button
type="primary"
shape="circle"
icon="setting"
size="large"
@click="()=>this.optionFormVisible=true"
></a-button>
</div>
<a-modal
v-model="optionFormVisible"
title="页面设置"
:afterClose="onOptionFormClose"
>
<template slot="footer">
<a-button
key="submit"
type="primary"
@click="handleSaveOptions()"
>保存</a-button>
</template>
<a-form layout="vertical">
<a-form-item label="页面标题:" help="* 需要主题进行适配">
<a-input v-model="options.journals_title" />
</a-form-item>
<a-form-item label="每页显示条数:">
<a-input
type="number"
v-model="options.journals_page_size"
/>
</a-form-item>
</a-form>
</a-modal>
<!-- 编辑日志弹窗 --> <!-- 编辑日志弹窗 -->
<a-modal v-model="visible"> <a-modal v-model="visible">
<template slot="title"> <template slot="title">
@ -211,9 +248,10 @@
import TargetCommentDrawer from '../../comment/components/TargetCommentDrawer' import TargetCommentDrawer from '../../comment/components/TargetCommentDrawer'
import AttachmentDrawer from '../../attachment/components/AttachmentDrawer' import AttachmentDrawer from '../../attachment/components/AttachmentDrawer'
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import { mapGetters } from 'vuex' import { mapGetters, mapActions } from 'vuex'
import journalApi from '@/api/journal' import journalApi from '@/api/journal'
import journalCommentApi from '@/api/journalComment' import journalCommentApi from '@/api/journalComment'
import optionApi from '@/api/option'
export default { export default {
mixins: [mixin, mixinDevice], mixins: [mixin, mixinDevice],
components: { TargetCommentDrawer, AttachmentDrawer }, components: { TargetCommentDrawer, AttachmentDrawer },
@ -225,6 +263,7 @@ export default {
visible: false, visible: false,
journalCommentVisible: false, journalCommentVisible: false,
attachmentDrawerVisible: false, attachmentDrawerVisible: false,
optionFormVisible: false,
pagination: { pagination: {
page: 1, page: 1,
size: 10, size: 10,
@ -241,16 +280,19 @@ export default {
comments: [], comments: [],
journal: {}, journal: {},
isPublic: true, isPublic: true,
replyComment: {} replyComment: {},
options: []
} }
}, },
created() { created() {
this.loadJournals() this.loadJournals()
this.loadFormOptions()
}, },
computed: { computed: {
...mapGetters(['user']) ...mapGetters(['user'])
}, },
methods: { methods: {
...mapActions(['loadOptions']),
loadJournals() { loadJournals() {
this.listLoading = true this.listLoading = true
this.queryParam.page = this.pagination.page - 1 this.queryParam.page = this.pagination.page - 1
@ -262,6 +304,11 @@ export default {
this.listLoading = false this.listLoading = false
}) })
}, },
loadFormOptions() {
optionApi.listAll().then(response => {
this.options = response.data.data
})
},
handleQuery() { handleQuery() {
this.handlePaginationChange(1, this.pagination.size) this.handlePaginationChange(1, this.pagination.size)
}, },
@ -332,6 +379,17 @@ export default {
this.queryParam.keyword = null this.queryParam.keyword = null
this.queryParam.type = null this.queryParam.type = null
this.handlePaginationChange(1, this.pagination.size) this.handlePaginationChange(1, this.pagination.size)
},
handleSaveOptions() {
optionApi.save(this.options).then(response => {
this.loadFormOptions()
this.loadOptions()
this.$message.success('保存成功!')
this.optionFormVisible = false
})
},
onOptionFormClose() {
this.optionFormVisible = false
} }
} }
} }

View File

@ -195,11 +195,40 @@
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
<div style="position: fixed;bottom: 30px;right: 30px;">
<a-button
type="primary"
shape="circle"
icon="setting"
size="large"
@click="()=>this.optionFormVisible=true"
></a-button>
</div>
<a-modal
v-model="optionFormVisible"
title="页面设置"
:afterClose="onOptionFormClose"
>
<template slot="footer">
<a-button
key="submit"
type="primary"
@click="handleSaveOptions()"
>保存</a-button>
</template>
<a-form layout="vertical">
<a-form-item label="页面标题:" help="* 需要主题进行适配">
<a-input v-model="options.links_title" />
</a-form-item>
</a-form>
</a-modal>
</div> </div>
</template> </template>
<script> <script>
import { mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import optionApi from '@/api/option'
import linkApi from '@/api/link' import linkApi from '@/api/link'
const columns = [ const columns = [
{ {
@ -231,12 +260,14 @@ export default {
data() { data() {
return { return {
formType: 'create', formType: 'create',
optionFormVisible: false,
data: [], data: [],
loading: false, loading: false,
columns, columns,
links: [], links: [],
link: {}, link: {},
teams: [] teams: [],
options: []
} }
}, },
computed: { computed: {
@ -250,8 +281,10 @@ export default {
created() { created() {
this.loadLinks() this.loadLinks()
this.loadTeams() this.loadTeams()
this.loadFormOptions()
}, },
methods: { methods: {
...mapActions(['loadOptions']),
loadLinks() { loadLinks() {
this.loading = true this.loading = true
linkApi.listAll().then(response => { linkApi.listAll().then(response => {
@ -264,6 +297,11 @@ export default {
this.teams = response.data.data this.teams = response.data.data
}) })
}, },
loadFormOptions() {
optionApi.listAll().then(response => {
this.options = response.data.data
})
},
handleSaveClick() { handleSaveClick() {
this.createOrUpdateLink() this.createOrUpdateLink()
}, },
@ -318,6 +356,17 @@ export default {
}) })
} }
this.handleAddLink() this.handleAddLink()
},
handleSaveOptions() {
optionApi.save(this.options).then(response => {
this.loadFormOptions()
this.loadOptions()
this.$message.success('保存成功!')
this.optionFormVisible = false
})
},
onOptionFormClose() {
this.optionFormVisible = false
} }
} }
} }

View File

@ -59,7 +59,10 @@
</a-row> </a-row>
</a-form> </a-form>
</div> </div>
<div class="table-operator" style="margin-bottom: 0;"> <div
class="table-operator"
style="margin-bottom: 0;"
>
<a-button <a-button
type="primary" type="primary"
icon="plus" icon="plus"
@ -85,7 +88,10 @@
@click="showDrawer(item)" @click="showDrawer(item)"
> >
<div class="photo-thumb"> <div class="photo-thumb">
<img :src="item.thumbnail" loading="lazy"> <img
:src="item.thumbnail"
loading="lazy"
>
</div> </div>
<a-card-meta style="padding: 0.8rem;"> <a-card-meta style="padding: 0.8rem;">
<ellipsis <ellipsis
@ -110,6 +116,39 @@
@showSizeChange="handlePaginationChange" @showSizeChange="handlePaginationChange"
/> />
</div> </div>
<div style="position: fixed;bottom: 30px;right: 30px;">
<a-button
type="primary"
shape="circle"
icon="setting"
size="large"
@click="()=>this.optionFormVisible=true"
></a-button>
</div>
<a-modal
v-model="optionFormVisible"
title="页面设置"
:afterClose="onOptionFormClose"
>
<template slot="footer">
<a-button
key="submit"
type="primary"
@click="handleSaveOptions()"
>保存</a-button>
</template>
<a-form layout="vertical">
<a-form-item label="页面标题:" help="* 需要主题进行适配">
<a-input v-model="options.photos_title" />
</a-form-item>
<a-form-item label="每页显示条数:">
<a-input
type="number"
v-model="options.photos_page_size"
/>
</a-form-item>
</a-form>
</a-modal>
<a-drawer <a-drawer
title="图片详情" title="图片详情"
:width="isMobile()?'100%':'460'" :width="isMobile()?'100%':'460'"
@ -137,7 +176,7 @@
</div> </div>
</a-skeleton> </a-skeleton>
</a-col> </a-col>
<a-divider style="margin: 24px 0 12px 0;"/> <a-divider style="margin: 24px 0 12px 0;" />
<a-col :span="24"> <a-col :span="24">
<a-skeleton <a-skeleton
@ -289,9 +328,11 @@
</template> </template>
<script> <script>
import { mapActions } from 'vuex'
import { mixin, mixinDevice } from '@/utils/mixin.js' import { mixin, mixinDevice } from '@/utils/mixin.js'
import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer' import AttachmentSelectDrawer from '../../attachment/components/AttachmentSelectDrawer'
import photoApi from '@/api/photo' import photoApi from '@/api/photo'
import optionApi from '@/api/option'
export default { export default {
components: { components: {
@ -304,6 +345,7 @@ export default {
drawerLoading: false, drawerLoading: false,
listLoading: true, listLoading: true,
thumDrawerVisible: false, thumDrawerVisible: false,
optionFormVisible: false,
photo: {}, photo: {},
photos: [], photos: [],
teams: [], teams: [],
@ -319,14 +361,17 @@ export default {
sort: null, sort: null,
keyword: null, keyword: null,
team: null team: null
} },
options: []
} }
}, },
created() { created() {
this.loadPhotos() this.loadPhotos()
this.loadTeams() this.loadTeams()
this.loadFormOptions()
}, },
methods: { methods: {
...mapActions(['loadOptions']),
loadPhotos() { loadPhotos() {
this.listLoading = true this.listLoading = true
this.queryParam.page = this.pagination.page - 1 this.queryParam.page = this.pagination.page - 1
@ -338,6 +383,11 @@ export default {
this.listLoading = false this.listLoading = false
}) })
}, },
loadFormOptions() {
optionApi.listAll().then(response => {
this.options = response.data.data
})
},
handleQuery() { handleQuery() {
this.handlePaginationChange(1, this.pagination.size) this.handlePaginationChange(1, this.pagination.size)
}, },
@ -406,6 +456,17 @@ export default {
this.drawerVisible = false this.drawerVisible = false
this.photo = {} this.photo = {}
this.editable = false this.editable = false
},
handleSaveOptions() {
optionApi.save(this.options).then(response => {
this.loadFormOptions()
this.loadOptions()
this.$message.success('保存成功!')
this.optionFormVisible = false
})
},
onOptionFormClose() {
this.optionFormVisible = false
} }
} }
} }

View File

@ -20,7 +20,7 @@
<a-icon type="copy" /> <a-icon type="copy" />
</a> </a>
</template> </template>
<a-popconfirm <!-- <a-popconfirm
slot="extra" slot="extra"
placement="left" placement="left"
okText="确定" okText="确定"
@ -41,7 +41,7 @@
icon="cloud-download" icon="cloud-download"
> >
</a-button> </a-button>
</a-popconfirm> </a-popconfirm> -->
<ul style="margin: 0;padding: 0;list-style: none;"> <ul style="margin: 0;padding: 0;list-style: none;">
<li>Server 版本{{ environments.version }}</li> <li>Server 版本{{ environments.version }}</li>
@ -55,10 +55,10 @@
href="https://github.com/halo-dev" href="https://github.com/halo-dev"
target="_blank" target="_blank"
style="margin-right: 10px;" style="margin-right: 10px;"
>开源地址 >开源组织
<a-icon type="link" /></a> <a-icon type="link" /></a>
<a <a
href="https://halo.run/guide" href="https://halo.run"
target="_blank" target="_blank"
style="margin-right: 10px;" style="margin-right: 10px;"
>用户文档 >用户文档
@ -75,39 +75,26 @@
title="开发者" title="开发者"
:bordered="false" :bordered="false"
:bodyStyle="{ padding: '16px' }" :bodyStyle="{ padding: '16px' }"
:loading="contributorsLoading"
> >
<a <a
:href="item.github" :href="item.html_url"
v-for="(item,index) in developers" v-for="(item,index) in contributors"
:key="index" :key="index"
target="_blank" target="_blank"
> >
<a-tooltip <a-tooltip
placement="top" placement="top"
:title="item.name" :title="item.login"
> >
<a-avatar <a-avatar
size="large" size="large"
:src="item.avatar" :src="item.avatar_url"
:style="{ marginRight: '10px' }" :style="{ marginRight: '10px',marginBottom: '10px'}"
/> />
</a-tooltip> </a-tooltip>
</a> </a>
</a-card> </a-card>
<a-card
title="时间轴"
:bordered="false"
:bodyStyle="{ padding: '16px' }"
>
<a-timeline>
<a-timeline-item>...</a-timeline-item>
<a-timeline-item
v-for="(item, index) in steps"
:key="index"
>{{ item.date }} {{ item.content }}</a-timeline-item>
</a-timeline>
</a-card>
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
@ -122,104 +109,37 @@ export default {
return { return {
adminVersion: this.VERSION, adminVersion: this.VERSION,
environments: {}, environments: {},
developers: [ contributors: [
{ {
name: 'Ryan Wang', login: '',
avatar: '//cn.gravatar.com/avatar/7cc7f29278071bd4dce995612d428834?s=256&d=mm', id: 0,
website: 'https://ryanc.cc', node_id: '',
github: 'https://github.com/ruibaby' avatar_url: '',
}, gravatar_id: '',
{ url: '',
name: 'John Niang', html_url: '',
avatar: '//cn.gravatar.com/avatar/1dcf60ef27363dae539385d5bae9b2bd?s=256&d=mm', followers_url: '',
website: 'https://johnniang.me', following_url: '',
github: 'https://github.com/johnniang' gists_url: '',
}, starred_url: '',
{ subscriptions_url: '',
name: 'Aquan', organizations_url: '',
avatar: '//cn.gravatar.com/avatar/3958035fa354403fa9ca3fca36b08068?s=256&d=mm', repos_url: '',
website: 'https://blog.eunji.cn', events_url: '',
github: 'https://github.com/aquanlerou' received_events_url: '',
}, type: '',
{ site_admin: false,
name: 'appdev', contributions: 0
avatar: '//cn.gravatar.com/avatar/08cf681fb7c6ad1b4fe70a8269c2103c?s=256&d=mm',
website: 'https://www.apkdv.com',
github: 'https://github.com/appdev'
},
{
name: 'guqing',
avatar: '//cn.gravatar.com/avatar/ad062ba572c8b006bfd2cbfc43fdee5e?s=256&d=mm',
website: 'http://www.guqing.xyz',
github: 'https://github.com/guqing'
} }
], ],
steps: [ contributorsLoading: true
{
date: '2019-09-11',
content: 'Halo v1.1.0 发布'
},
{
date: '2019-07-09',
content: 'Halo v1.0.3 发布'
},
{
date: '2019-07-08',
content: 'Star 数达到 6500'
},
{
date: '2019-06-01',
content: '1.0 正式版发布'
},
{
date: '2019-05-03',
content: 'Star 数达到 3300'
},
{
date: '2019-01-30',
content: 'John Niang 加入开发'
},
{
date: '2018-10-18',
content: '构建镜像到 Docker hub'
},
{
date: '2018-09-22',
content: 'Star 数达到 800'
},
{
date: '2018-05-02',
content: '第一条 Issue'
},
{
date: '2018-05-01',
content: 'Star 数达到 100'
},
{
date: '2018-04-29',
content: '第一个 Pull request'
},
{
date: '2018-04-28',
content: '正式开源'
},
{
date: '2018-03-21',
content: '确定命名为 Halo并上传到 Github'
}
],
updating: false
} }
}, },
created() { created() {
this.getEnvironments() this.getEnvironments()
this.fetchContributors()
this.checkServerUpdate() this.checkServerUpdate()
this.checkAdminUpdate() // this.checkAdminUpdate()
},
computed: {
updateText() {
return this.updating ? '更新中...' : '更新'
}
}, },
methods: { methods: {
getEnvironments() { getEnvironments() {
@ -227,36 +147,49 @@ export default {
this.environments = response.data.data this.environments = response.data.data
}) })
}, },
confirmUpdate() { // confirmUpdate() {
this.updating = true // this.updating = true
adminApi // adminApi
.updateAdminAssets() // .updateAdminAssets()
.then(response => { // .then(response => {
this.$notification.success({ // this.$notification.success({
message: '更新成功', // message: '',
description: '请刷新后体验最新版本!' // description: ''
}) // })
}) // })
.finally(() => { // .finally(() => {
this.updating = false // this.updating = false
}) // })
}, // },
handleCopyEnvironments() { handleCopyEnvironments() {
const text = `Server 版本:${this.environments.version} const text = `Server 版本:${this.environments.version}
Admin 版本${this.adminVersion} Admin 版本${this.adminVersion}
数据库${this.environments.database} 数据库${this.environments.database}
运行模式${this.environments.mode} 运行模式${this.environments.mode}
UA 信息${navigator.userAgent}` User Agent${navigator.userAgent}`
this.$copyText(text) this.$copyText(text)
.then(message => { .then(message => {
console.log('copy', message) this.$log.debug('copy', message)
this.$message.success('复制成功!') this.$message.success('复制成功!')
}) })
.catch(err => { .catch(err => {
console.log('copy.err', err) this.$log.debug('copy.err', err)
this.$message.error('复制失败!') this.$message.error('复制失败!')
}) })
}, },
async fetchContributors() {
this.contributorsLoading = true
const _this = this
axios
.get('https://api.github.com/repos/halo-dev/halo/contributors')
.then(response => {
_this.contributors = response.data
this.contributorsLoading = false
})
.catch(function(error) {
console.error('Fetch contributors error', error)
})
},
async checkServerUpdate() { async checkServerUpdate() {
const _this = this const _this = this
@ -300,48 +233,48 @@ UA 信息:${navigator.userAgent}`
console.error('Check update fail', error) console.error('Check update fail', error)
}) })
}, },
async checkAdminUpdate() { // async checkAdminUpdate() {
const _this = this // const _this = this
axios // axios
.get('https://api.github.com/repos/halo-dev/halo-admin/releases/latest') // .get('https://api.github.com/repos/halo-dev/halo-admin/releases/latest')
.then(response => { // .then(response => {
const data = response.data // const data = response.data
if (data.draft || data.prerelease) { // if (data.draft || data.prerelease) {
return // return
} // }
const current = _this.calculateIntValue(_this.adminVersion) // const current = _this.calculateIntValue(_this.adminVersion)
const latest = _this.calculateIntValue(data.name) // const latest = _this.calculateIntValue(data.name)
if (current >= latest) { // if (current >= latest) {
return // return
} // }
const title = '新版本提醒' // const title = ''
const content = '检测到 Admin 新版本:' + data.name + ',点击下方按钮可直接更新为最新版本。' // const content = ' Admin ' + data.name + ''
this.$notification.open({ // this.$notification.open({
message: title, // message: title,
description: content, // description: content,
icon: <a-icon type="smile" style="color: #108ee9" />, // icon: <a-icon type="smile" style="color: #108ee9" />,
btn: h => { // btn: h => {
return h( // return h(
'a-button', // 'a-button',
{ // {
props: { // props: {
type: 'primary', // type: 'primary',
size: 'small' // size: 'small'
}, // },
on: { // on: {
click: () => _this.confirmUpdate() // click: () => _this.confirmUpdate()
} // }
}, // },
'点击更新' // ''
) // )
} // }
}) // })
}) // })
.catch(function(error) { // .catch(function(error) {
console.error('Check update fail', error) // console.error('Check update fail', error)
}) // })
}, // },
calculateIntValue(version) { calculateIntValue(version) {
version = version.replace(/v/g, '') version = version.replace(/v/g, '')
const ss = version.split('.') const ss = version.split('.')

View File

@ -247,8 +247,8 @@ export default {
handleNextStep(e) { handleNextStep(e) {
e.preventDefault() e.preventDefault()
this.bloggerForm.validateFields((error, values) => { this.bloggerForm.validateFields((error, values) => {
console.log('error', error) this.$log.debug('error', error)
console.log('Received values of form: ', values) this.$log.debug('Received values of form: ', values)
if (error != null) { if (error != null) {
} else { } else {
this.stepCurrent++ this.stepCurrent++

View File

@ -3,7 +3,11 @@
<a-row> <a-row>
<a-col :span="24"> <a-col :span="24">
<div class="card-container"> <div class="card-container">
<a-tabs type="card"> <a-tabs
type="card"
class="general"
v-if="!advancedOptions"
>
<a-tab-pane key="general"> <a-tab-pane key="general">
<span slot="tab"> <span slot="tab">
<a-icon type="tool" />常规设置 <a-icon type="tool" />常规设置
@ -70,7 +74,7 @@
<a-form-item label="屏蔽搜索引擎:"> <a-form-item label="屏蔽搜索引擎:">
<a-switch v-model="options.seo_spider_disabled" /> <a-switch v-model="options.seo_spider_disabled" />
</a-form-item> </a-form-item>
<a-form-item label="关键词: "> <a-form-item label="关键词:">
<a-input <a-input
v-model="options.seo_keywords" v-model="options.seo_keywords"
placeholder="多个关键词以英文状态下的逗号隔开" placeholder="多个关键词以英文状态下的逗号隔开"
@ -247,6 +251,7 @@
<a-input-password <a-input-password
v-model="options.smms_api_secret_token" v-model="options.smms_api_secret_token"
placeholder="需要到 sm.ms 官网注册后获取" placeholder="需要到 sm.ms 官网注册后获取"
autocomplete="new-password"
/> />
</a-form-item> </a-form-item>
</div> </div>
@ -273,7 +278,10 @@
<a-input v-model="options.oss_upyun_operator" /> <a-input v-model="options.oss_upyun_operator" />
</a-form-item> </a-form-item>
<a-form-item label="操作员密码:"> <a-form-item label="操作员密码:">
<a-input-password v-model="options.oss_upyun_password" /> <a-input-password
v-model="options.oss_upyun_password"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="文件目录:"> <a-form-item label="文件目录:">
<a-input v-model="options.oss_upyun_source" /> <a-input v-model="options.oss_upyun_source" />
@ -315,10 +323,16 @@
/> />
</a-form-item> </a-form-item>
<a-form-item label="Access Key"> <a-form-item label="Access Key">
<a-input-password v-model="options.oss_qiniu_access_key" /> <a-input-password
v-model="options.oss_qiniu_access_key"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="Secret Key"> <a-form-item label="Secret Key">
<a-input-password v-model="options.oss_qiniu_secret_key" /> <a-input-password
v-model="options.oss_qiniu_secret_key"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="文件目录:"> <a-form-item label="文件目录:">
<a-input <a-input
@ -371,10 +385,16 @@
<a-input v-model="options.oss_ali_endpoint" /> <a-input v-model="options.oss_ali_endpoint" />
</a-form-item> </a-form-item>
<a-form-item label="Access Key"> <a-form-item label="Access Key">
<a-input-password v-model="options.oss_ali_access_key" /> <a-input-password
v-model="options.oss_ali_access_key"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="Access Secret"> <a-form-item label="Access Secret">
<a-input-password v-model="options.oss_ali_access_secret" /> <a-input-password
v-model="options.oss_ali_access_secret"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="文件目录:"> <a-form-item label="文件目录:">
<a-input <a-input
@ -421,10 +441,16 @@
<a-input v-model="options.bos_baidu_endpoint" /> <a-input v-model="options.bos_baidu_endpoint" />
</a-form-item> </a-form-item>
<a-form-item label="Access Key"> <a-form-item label="Access Key">
<a-input-password v-model="options.bos_baidu_access_key" /> <a-input-password
v-model="options.bos_baidu_access_key"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="Secret Key"> <a-form-item label="Secret Key">
<a-input-password v-model="options.bos_baidu_secret_key" /> <a-input-password
v-model="options.bos_baidu_secret_key"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="图片处理策略:"> <a-form-item label="图片处理策略:">
<a-input <a-input
@ -469,10 +495,16 @@
/> />
</a-form-item> </a-form-item>
<a-form-item label="Secret Id"> <a-form-item label="Secret Id">
<a-input-password v-model="options.cos_tencent_secret_id" /> <a-input-password
v-model="options.cos_tencent_secret_id"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="Secret Key"> <a-form-item label="Secret Key">
<a-input-password v-model="options.cos_tencent_secret_key" /> <a-input-password
v-model="options.cos_tencent_secret_key"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="文件目录:"> <a-form-item label="文件目录:">
<a-input <a-input
@ -534,6 +566,7 @@
<a-input-password <a-input-password
v-model="options.email_password" v-model="options.email_password"
placeholder="部分邮箱可能是授权码" placeholder="部分邮箱可能是授权码"
autocomplete="new-password"
/> />
</a-form-item> </a-form-item>
<a-form-item label="发件人:"> <a-form-item label="发件人:">
@ -579,28 +612,6 @@
</a-tabs> </a-tabs>
</div> </div>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="api">
<span slot="tab">
<a-icon type="thunderbolt" />API 设置
</span>
<a-form
layout="vertical"
:wrapperCol="wrapperCol"
>
<a-form-item label="API 服务:">
<a-switch v-model="options.api_enabled" />
</a-form-item>
<a-form-item label="Access key">
<a-input-password v-model="options.api_access_key" />
</a-form-item>
<a-form-item>
<a-button
type="primary"
@click="handleSaveOptions"
>保存</a-button>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="other"> <a-tab-pane key="other">
<span slot="tab"> <span slot="tab">
<a-icon type="align-left" />其他设置 <a-icon type="align-left" />其他设置
@ -653,10 +664,158 @@
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
<a-tabs
type="card"
class="advanced"
v-else
>
<a-tab-pane key="permalink">
<span slot="tab">
<a-icon type="link" />固定链接
</span>
<a-form
layout="vertical"
:wrapperCol="wrapperCol"
>
<a-form-item label="文章固定链接类型:">
<template slot="help">
<span v-if="options.post_permalink_type === 'DEFAULT'">{{ options.blog_url }}/{{ options.archives_prefix }}/${url}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'DATE'">{{ options.blog_url }}{{ new Date() | moment_post_date }}${url}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'DAY'">{{ options.blog_url }}{{ new Date() | moment_post_day }}${url}{{ options.path_suffix }}</span>
<span v-else-if="options.post_permalink_type === 'ID'">{{ options.blog_url }}/?p=${id}</span>
</template>
<a-select v-model="options.post_permalink_type">
<a-select-option
v-for="item in Object.keys(permalinkType)"
:key="item"
:value="item"
>{{ permalinkType[item].text }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="自定义页面前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.sheet_prefix }}/${url}{{ options.path_suffix }}</span>
</template>
<a-input v-model="options.sheet_prefix" />
</a-form-item>
<a-form-item label="友情链接页面前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.links_prefix }}</span>
</template>
<a-input v-model="options.links_prefix" />
</a-form-item>
<a-form-item label="图库页面前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.photos_prefix }}</span>
</template>
<a-input v-model="options.photos_prefix" />
</a-form-item>
<a-form-item label="日志页面前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.journals_prefix }}</span>
</template>
<a-input v-model="options.journals_prefix" />
</a-form-item>
<a-form-item label="归档前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.archives_prefix }}{{ options.path_suffix }}</span>
</template>
<a-input v-model="options.archives_prefix" />
</a-form-item>
<a-form-item label="分类前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.categories_prefix }}/${slugName}{{ options.path_suffix }}</span>
</template>
<a-input v-model="options.categories_prefix" />
</a-form-item>
<a-form-item label="标签前缀:">
<template slot="help">
<span>{{ options.blog_url }}/{{ options.tags_prefix }}/${slugName}{{ options.path_suffix }}</span>
</template>
<a-input v-model="options.tags_prefix" />
</a-form-item>
<a-form-item label="路径后缀:">
<template slot="help">
<span>仅对内建路径有效</span>
</template>
<a-input v-model="options.path_suffix" />
</a-form-item>
<a-form-item>
<a-button
type="primary"
@click="handleSaveOptions"
>保存</a-button>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="api">
<span slot="tab">
<a-icon type="api" />API 设置
</span>
<a-form
layout="vertical"
:wrapperCol="wrapperCol"
>
<a-form-item label="API 服务:">
<a-switch v-model="options.api_enabled" />
</a-form-item>
<a-form-item label="Access key">
<a-input-password
v-model="options.api_access_key"
autocomplete="new-password"
/>
</a-form-item>
<a-form-item>
<a-button
type="primary"
@click="handleSaveOptions"
>保存</a-button>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="advanced-other">
<span slot="tab">
<a-icon type="align-left" />其他设置
</span>
<a-form
layout="vertical"
:wrapperCol="wrapperCol"
>
<a-form-item
label="全局绝对路径:"
help="* 对网站上面的所有页面路径、本地附件路径、以及主题中的静态资源路径有效。"
>
<a-switch v-model="options.global_absolute_path_enabled" />
</a-form-item>
<a-form-item>
<a-button
type="primary"
@click="handleSaveOptions"
>保存</a-button>
</a-form-item>
</a-form>
</a-tab-pane>
</a-tabs>
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
<div style="position: fixed;bottom: 30px;right: 30px;">
<a-tooltip placement="top">
<template slot="title">
<span>{{ advancedOptions?'基础选项':'高级选项' }}</span>
</template>
<a-button
type="primary"
shape="circle"
:icon="`${advancedOptions?'setting':'thunderbolt'}`"
size="large"
@click="handleAdvancedOptions()"
></a-button>
</a-tooltip>
</div>
<AttachmentSelectDrawer <AttachmentSelectDrawer
v-model="logoDrawerVisible" v-model="logoDrawerVisible"
@listenToSelect="handleSelectLogo" @listenToSelect="handleSelectLogo"
@ -675,6 +834,7 @@ import { mapActions } from 'vuex'
import optionApi from '@/api/option' import optionApi from '@/api/option'
import mailApi from '@/api/mail' import mailApi from '@/api/mail'
import attachmentApi from '@/api/attachment' import attachmentApi from '@/api/attachment'
import postApi from '@/api/post'
export default { export default {
components: { components: {
@ -683,6 +843,7 @@ export default {
data() { data() {
return { return {
attachmentType: attachmentApi.type, attachmentType: attachmentApi.type,
permalinkType: postApi.permalinkType,
wrapperCol: { wrapperCol: {
xl: { span: 8 }, xl: { span: 8 },
lg: { span: 8 }, lg: { span: 8 },
@ -693,6 +854,7 @@ export default {
faviconDrawerVisible: false, faviconDrawerVisible: false,
options: [], options: [],
mailParam: {}, mailParam: {},
advancedOptions: false,
tencentCosRegions: [ tencentCosRegions: [
{ {
text: '北京一区', text: '北京一区',
@ -1069,6 +1231,9 @@ export default {
handleSelectFavicon(data) { handleSelectFavicon(data) {
this.options.blog_favicon = encodeURI(data.path) this.options.blog_favicon = encodeURI(data.path)
this.faviconDrawerVisible = false this.faviconDrawerVisible = false
},
handleAdvancedOptions() {
this.advancedOptions = !this.advancedOptions
} }
} }
} }

View File

@ -18,7 +18,7 @@
<div slot="title"> <div slot="title">
<a-icon type="experiment" /> 开发者选项 <a-icon type="experiment" /> 开发者选项
</div> </div>
<p>点击进入开发者选项页面</p> <p style="min-height: 50px;">点击进入开发者选项页面</p>
<a-button <a-button
type="primary" type="primary"
style="float:right" style="float:right"
@ -26,6 +26,29 @@
>进入</a-button> >进入</a-button>
</a-card> </a-card>
</a-col> </a-col>
<a-col
:xl="6"
:lg="6"
:md="12"
:sm="24"
:xs="24"
:style="{ marginBottom: '12px' }"
>
<a-card
:bordered="false"
:bodyStyle="{ padding: '16px' }"
>
<div slot="title">
<a-icon type="html5" /> 静态部署
</div>
<p style="min-height: 50px;">生成静态页面并部署到 Github Pages 之类的托管平台</p>
<a-button
type="primary"
style="float:right"
@click="handleToStaticPagesManage"
>管理</a-button>
</a-card>
</a-col>
<a-col <a-col
:xl="6" :xl="6"
:lg="6" :lg="6"
@ -41,7 +64,7 @@
<div slot="title"> <div slot="title">
<a-icon type="hdd" /> 博客备份 <a-icon type="hdd" /> 博客备份
</div> </div>
<p>支持备份全站数据</p> <p style="min-height: 50px;">备份全站数据支持下载到本地</p>
<a-button <a-button
type="primary" type="primary"
style="float:right" style="float:right"
@ -64,7 +87,7 @@
<div slot="title"> <div slot="title">
<a-icon type="file-markdown" /> Markdown 文章导入 <a-icon type="file-markdown" /> Markdown 文章导入
</div> </div>
<p>支持 Hexo/Jekyll 文章导入并解析元数据</p> <p style="min-height: 50px;">支持 Hexo/Jekyll 文章导入并解析元数据</p>
<a-button <a-button
type="primary" type="primary"
style="float:right" style="float:right"
@ -117,7 +140,7 @@ export default {
handleChange(info) { handleChange(info) {
const status = info.file.status const status = info.file.status
if (status !== 'uploading') { if (status !== 'uploading') {
console.log(info.file, info.fileList) this.$log.debug(info.file, info.fileList)
} }
if (status === 'done') { if (status === 'done') {
this.$message.success(`${info.file.name} 导入成功!`) this.$message.success(`${info.file.name} 导入成功!`)
@ -128,6 +151,9 @@ export default {
handleToDeveloperOptions() { handleToDeveloperOptions() {
this.$router.push({ name: 'DeveloperOptions' }) this.$router.push({ name: 'DeveloperOptions' })
}, },
handleToStaticPagesManage() {
this.$router.push({ name: 'StaticPagesManage' })
},
onUploadClose() { onUploadClose() {
this.$refs.upload.handleClearFileList() this.$refs.upload.handleClearFileList()
} }

View File

@ -1,5 +1,8 @@
<template> <template>
<a-form layout="vertical"> <a-form
layout="vertical"
:wrapperCol="wrapperCol"
>
<a-form-item label="开发者选项:"> <a-form-item label="开发者选项:">
<a-switch v-model="options.developer_mode" /> <a-switch v-model="options.developer_mode" />
</a-form-item> </a-form-item>
@ -18,7 +21,13 @@ export default {
name: 'SettingsForm', name: 'SettingsForm',
data() { data() {
return { return {
options: [] options: [],
wrapperCol: {
xl: { span: 8 },
lg: { span: 8 },
sm: { span: 12 },
xs: { span: 24 }
}
} }
}, },
created() { created() {

View File

@ -27,7 +27,7 @@
</div> </div>
<div style="margin-top:15px"> <div style="margin-top:15px">
<a-table <a-table
:rowKey="record => record.name" :rowKey="record => record.id"
:columns="columns" :columns="columns"
:dataSource="sortedStatics" :dataSource="sortedStatics"
:pagination="false" :pagination="false"

View File

@ -0,0 +1,43 @@
<template>
<div>
<a-row>
<a-col :span="24">
<div class="card-container">
<a-tabs type="card">
<a-tab-pane key="environment">
<span slot="tab">
<a-icon type="folder" />文件列表
</span>
<StaticPagesList />
</a-tab-pane>
<a-tab-pane key="runtimeLogs">
<span slot="tab">
<a-icon type="appstore" />部署平台
</span>
<DeploySettingsForm />
</a-tab-pane>
<a-tab-pane key="optionsList">
<span slot="tab">
<a-icon type="setting" />配置
</span>
<SettingsForm />
</a-tab-pane>
</a-tabs>
</div>
</a-col>
</a-row>
</div>
</template>
<script>
import StaticPagesList from './tabs/StaticPagesList'
import DeploySettingsForm from './tabs/DeploySettingsForm'
import SettingsForm from './tabs/SettingsForm'
export default {
name: 'StaticPagesManage',
components: {
StaticPagesList,
DeploySettingsForm,
SettingsForm
}
}
</script>

View File

@ -0,0 +1,106 @@
<template>
<a-form
layout="vertical"
:wrapperCol="wrapperCol"
>
<a-form-item label="部署平台:">
<a-select v-model="options.static_deploy_type">
<a-select-option
v-for="item in Object.keys(deployType)"
:key="item"
:value="item"
>{{ deployType[item].text }}</a-select-option>
</a-select>
</a-form-item>
<div
id="gitForm"
v-show="options.static_deploy_type === 'GIT'"
>
<a-form-item label="域名:">
<a-input v-model="options.git_static_deploy_domain" />
</a-form-item>
<a-form-item label="仓库:">
<a-input v-model="options.git_static_deploy_repository" />
</a-form-item>
<a-form-item label="分支:">
<a-input v-model="options.git_static_deploy_branch" />
</a-form-item>
<a-form-item label="仓库用户名:">
<a-input v-model="options.git_static_deploy_username" />
</a-form-item>
<a-form-item label="邮箱:">
<a-input v-model="options.git_static_deploy_email" />
</a-form-item>
<a-form-item label="Token">
<a-input-password
v-model="options.git_static_deploy_token"
autocomplete="new-password"
/>
</a-form-item>
<a-form-item label="CNAME">
<a-input v-model="options.git_static_deploy_cname" />
</a-form-item>
</div>
<div
id="netlifyForm"
v-show="options.static_deploy_type === 'NETLIFY'"
>
<a-form-item label="域名:">
<a-input v-model="options.netlify_static_deploy_domain" />
</a-form-item>
<a-form-item label="Site ID">
<a-input v-model="options.netlify_static_deploy_site_id" />
</a-form-item>
<a-form-item label="Token">
<a-input-password
v-model="options.netlify_static_deploy_token"
autocomplete="new-password"
/>
</a-form-item>
</div>
<a-form-item>
<a-button
type="primary"
@click="handleSaveOptions"
>保存</a-button>
</a-form-item>
</a-form>
</template>
<script>
import staticPageApi from '@/api/staticPage'
import optionApi from '@/api/option'
import { mapActions } from 'vuex'
export default {
name: 'DeploySettingsForm',
data() {
return {
deployType: staticPageApi.deployType,
wrapperCol: {
xl: { span: 8 },
lg: { span: 8 },
sm: { span: 12 },
xs: { span: 24 }
},
options: []
}
},
mounted() {
this.loadFormOptions()
},
methods: {
...mapActions(['loadOptions']),
loadFormOptions() {
optionApi.listAll().then(response => {
this.options = response.data.data
})
},
handleSaveOptions() {
optionApi.save(this.options).then(response => {
this.loadFormOptions()
this.loadOptions()
this.$message.success('保存成功!')
})
}
}
}
</script>

View File

@ -0,0 +1,10 @@
<template>
<div>
SettingsForm
</div>
</template>
<script>
export default {
name: 'SettingsForm'
}
</script>

View File

@ -0,0 +1,113 @@
<template>
<div class="option-tab-wrapper">
<a-card
:bordered="false"
:bodyStyle="{ padding: 0 }"
>
<div class="table-operator">
<a-button
type="primary"
icon="reload"
@click="handleGenerate"
>生成</a-button>
<a-button
icon="cloud-upload"
@click="handleDeploy"
:loading="deployLoading"
:disabled="deployLoading"
>
部署
</a-button>
<a-button
icon="sync"
@click="loadStaticPageList"
:loading="loading"
:disabled="loading"
>
刷新
</a-button>
</div>
<div style="margin-top:15px">
<a-table
:rowKey="record => record.id"
:columns="columns"
:dataSource="staticPages"
:pagination="false"
size="middle"
:loading="loading"
>
<span
slot="name"
slot-scope="name"
>
<ellipsis
:length="64"
tooltip
>
{{ name }}
</ellipsis>
</span>
</a-table>
</div>
</a-card>
</div>
</template>
<script>
import staticPageApi from '@/api/staticPage'
const columns = [
{
title: '文件名',
dataIndex: 'name',
scopedSlots: { customRender: 'name' }
}
]
export default {
name: 'StaticPagesList',
data() {
return {
columns: columns,
staticPages: [],
loading: false,
deployLoading: false
}
},
created() {
this.loadStaticPageList()
},
methods: {
loadStaticPageList() {
this.loading = true
staticPageApi.list().then(response => {
this.staticPages = response.data.data
this.loading = false
})
},
handleGenerate() {
this.loading = true
const hide = this.$message.loading('生成中...', 0)
staticPageApi
.generate()
.then(response => {
this.$message.success('生成成功!')
})
.finally(response => {
this.loadStaticPageList()
hide()
})
},
handleDeploy() {
this.deployLoading = true
const hide = this.$message.loading('部署中...', 0)
staticPageApi
.deploy()
.then(response => {
this.$message.success('部署成功!')
})
.finally(response => {
this.deployLoading = false
hide()
})
}
}
}
</script>

View File

@ -47,6 +47,7 @@
:style="{'animation-delay': '0.3s'}" :style="{'animation-delay': '0.3s'}"
> >
<a-button <a-button
:loading="landing"
type="primary" type="primary"
:block="true" :block="true"
@click="handleLogin" @click="handleLogin"
@ -139,7 +140,8 @@ export default {
apiModifyVisible: false, apiModifyVisible: false,
defaultApiBefore: window.location.protocol + '//', defaultApiBefore: window.location.protocol + '//',
apiUrl: window.location.host, apiUrl: window.location.host,
resetPasswordButton: false resetPasswordButton: false,
landing: false
} }
}, },
computed: { computed: {
@ -169,11 +171,17 @@ export default {
this.$message.warn('密码不能为空!') this.$message.warn('密码不能为空!')
return return
} }
this.landing = true
this.login({ username: this.username, password: this.password }).then(response => { this.login({ username: this.username, password: this.password })
// Go to dashboard .then(response => {
this.loginSuccess() // Go to dashboard
}) this.loginSuccess()
})
.finally(() => {
setTimeout(() => {
this.landing = false
}, 500)
})
}, },
loginSuccess() { loginSuccess() {
// Cache the user info // Cache the user info

View File

@ -107,13 +107,22 @@
</span> </span>
<a-form layout="vertical"> <a-form layout="vertical">
<a-form-item label="原密码:"> <a-form-item label="原密码:">
<a-input-password v-model="passwordParam.oldPassword" /> <a-input-password
v-model="passwordParam.oldPassword"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="新密码:"> <a-form-item label="新密码:">
<a-input-password v-model="passwordParam.newPassword" /> <a-input-password
v-model="passwordParam.newPassword"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item label="确认密码:"> <a-form-item label="确认密码:">
<a-input-password v-model="passwordParam.confirmPassword" /> <a-input-password
v-model="passwordParam.confirmPassword"
autocomplete="new-password"
/>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-button <a-button

View File

@ -15,19 +15,19 @@ const assetsCDN = {
axios: 'axios', axios: 'axios',
marked: 'marked' marked: 'marked'
}, },
css: [ css: [],
],
js: [ js: [
'//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js', 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
'//cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js', 'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.min.js',
'//cdn.jsdelivr.net/npm/vuex@3.1.1/dist/vuex.min.js', 'https://cdn.jsdelivr.net/npm/vuex@3.1.1/dist/vuex.min.js',
'//cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js', 'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js',
'//cdn.jsdelivr.net/npm/marked@0.8.0/marked.min.js' 'https://cdn.jsdelivr.net/npm/marked@0.8.0/marked.min.js'
] ]
} }
// vue.config.js // vue.config.js
module.exports = { module.exports = {
publicPath: process.env.PUBLIC_PATH,
configureWebpack: { configureWebpack: {
plugins: [ plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)