refactor: theme setting page (halo-dev/console#380)

* refactor: theme setting page

Signed-off-by: Ryan Wang <i@ryanc.cc>

* feat: add theme descriptions

Signed-off-by: Ryan Wang <i@ryanc.cc>

* refactor: local upgrade component

Signed-off-by: Ryan Wang <i@ryanc.cc>

* refactor: theme descriptions

Signed-off-by: Ryan Wang <i@ryanc.cc>

* chore: bump admin-api deps
pull/3445/head
Ryan Wang 2022-01-14 15:37:31 +08:00 committed by GitHub
parent a57442284e
commit b1cf9ee421
12 changed files with 546 additions and 132 deletions

View File

@ -24,7 +24,7 @@
"@codemirror/basic-setup": "^0.19.0", "@codemirror/basic-setup": "^0.19.0",
"@codemirror/lang-html": "^0.19.3", "@codemirror/lang-html": "^0.19.3",
"@codemirror/lang-java": "^0.19.1", "@codemirror/lang-java": "^0.19.1",
"@halo-dev/admin-api": "^1.0.0-alpha.44", "@halo-dev/admin-api": "^1.0.0-alpha.46",
"ant-design-vue": "^1.7.8", "ant-design-vue": "^1.7.8",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",

View File

@ -5,7 +5,7 @@ specifiers:
'@codemirror/basic-setup': ^0.19.0 '@codemirror/basic-setup': ^0.19.0
'@codemirror/lang-html': ^0.19.3 '@codemirror/lang-html': ^0.19.3
'@codemirror/lang-java': ^0.19.1 '@codemirror/lang-java': ^0.19.1
'@halo-dev/admin-api': ^1.0.0-alpha.44 '@halo-dev/admin-api': ^1.0.0-alpha.46
'@vue/cli-plugin-babel': ^3.12.1 '@vue/cli-plugin-babel': ^3.12.1
'@vue/cli-plugin-eslint': ^4.5.15 '@vue/cli-plugin-eslint': ^4.5.15
'@vue/cli-plugin-unit-jest': ^4.5.15 '@vue/cli-plugin-unit-jest': ^4.5.15
@ -54,7 +54,7 @@ dependencies:
'@codemirror/basic-setup': 0.19.0 '@codemirror/basic-setup': 0.19.0
'@codemirror/lang-html': 0.19.3 '@codemirror/lang-html': 0.19.3
'@codemirror/lang-java': 0.19.1 '@codemirror/lang-java': 0.19.1
'@halo-dev/admin-api': 1.0.0-alpha.44 '@halo-dev/admin-api': 1.0.0-alpha.46
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
crypto-js: 4.1.1 crypto-js: 4.1.1
dayjs: 1.10.7 dayjs: 1.10.7
@ -1348,32 +1348,33 @@ packages:
purgecss: 2.3.0 purgecss: 2.3.0
dev: true dev: true
/@halo-dev/admin-api/1.0.0-alpha.44: /@halo-dev/admin-api/1.0.0-alpha.46:
resolution: {integrity: sha512-nCJsx4gDxCjkoGJKsBpcgLhAUc8RYrqKMGM1VSi9t64xTFdXDjIJgtcIQuBgJ0b4R58JIp6A6ti5S764vz4BDA==} resolution: {integrity: sha512-OlDZm2/L/9IKlhCjWWFO1KkqxfEip/nMZrqavMQXghcHomWBqxvMttB4P1vg+0xmoZh65uYhl3De6VSIbL2FsQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
dependencies: dependencies:
'@halo-dev/rest-api-client': 1.0.0-alpha.44 '@halo-dev/rest-api-client': 1.0.0-alpha.46
tslib: 2.3.1
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: false dev: false
/@halo-dev/logger/1.0.0-alpha.44: /@halo-dev/logger/1.0.0-alpha.46:
resolution: {integrity: sha512-ORHP6pj8wLb+mwsk+pYqvH9tqNWTr+96AiZgtSMcdwojA1KupFSVzHN0aqpk8HeGfSrBNbMz4VT6ey+OVnWrcQ==} resolution: {integrity: sha512-iBLa55wq6i++9l2L4w1X0vY5mMmqOFBbzjvTNv9TcG2ssUNXL1s0uRgasjvlQAI5EDfvmHhth/RW4ACPM+EUnQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
dependencies: dependencies:
tslib: 2.3.1 tslib: 2.3.1
dev: false dev: false
/@halo-dev/rest-api-client/1.0.0-alpha.44: /@halo-dev/rest-api-client/1.0.0-alpha.46:
resolution: {integrity: sha512-fCzh7ihLpI7hrq0S2M9YjROTvoTT5JeEOqv5O/hon4bTfpBqEb7REjjYMONR8lJNgmEI9wzviEhn7Xxg7nX/vA==} resolution: {integrity: sha512-kv88fszMZe26KAVfZXzjIvqZV6wsMWCxMtoWgk9F8DkYs3kQt9n+98g5azmtFgZh2RYh3O4einjcFOUQ44yRxg==}
engines: {node: '>=12'} engines: {node: '>=12'}
dependencies: dependencies:
'@halo-dev/logger': 1.0.0-alpha.44 '@halo-dev/logger': 1.0.0-alpha.46
axios: 0.24.0 axios: 0.24.0
form-data: 4.0.0 form-data: 4.0.0
js-base64: 3.7.2 js-base64: 3.7.2
qs: 6.10.1 qs: 6.10.3
store: 2.0.12 tslib: 2.3.1
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: false dev: false
@ -9403,8 +9404,8 @@ packages:
engines: {node: '>=0.6.0', teleport: '>=0.2.0'} engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true dev: true
/qs/6.10.1: /qs/6.10.3:
resolution: {integrity: sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==} resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
dependencies: dependencies:
side-channel: 1.0.4 side-channel: 1.0.4
@ -10351,10 +10352,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/store/2.0.12:
resolution: {integrity: sha1-jFNOKguDH3K3X8XxEZhXxE711ZM=}
dev: false
/stream-browserify/2.0.2: /stream-browserify/2.0.2:
resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==} resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==}
dependencies: dependencies:

View File

@ -0,0 +1,47 @@
<template>
<div>
<a-input :defaultValue="defaultValue" :placeholder="placeholder" :value="value" @change="onInputChange">
<a slot="addonAfter" href="javascript:void(0);" @click="attachmentDrawerVisible = true">
<a-icon type="picture" />
</a>
</a-input>
<AttachmentSelectDrawer
v-model="attachmentDrawerVisible"
title="选择附件"
@listenToSelect="handleSelectAttachment"
/>
</div>
</template>
<script>
export default {
name: 'AttachmentInput',
props: {
value: {
type: String,
default: ''
},
defaultValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
attachmentDrawerVisible: false
}
},
methods: {
onInputChange(e) {
this.$emit('input', e.target.value)
},
handleSelectAttachment(data) {
this.$emit('input', encodeURI(data.path))
this.attachmentDrawerVisible = false
}
}
}
</script>

View File

@ -7,6 +7,7 @@ import AttachmentSelectDrawer from './Attachment/AttachmentSelectDrawer'
import AttachmentUploadModal from './Attachment/AttachmentUploadModal' import AttachmentUploadModal from './Attachment/AttachmentUploadModal'
import ReactiveButton from './Button/ReactiveButton' import ReactiveButton from './Button/ReactiveButton'
import PostTag from './Post/PostTag' import PostTag from './Post/PostTag'
import AttachmentInput from './Input/AttachmentInput'
const _components = { const _components = {
Ellipsis, Ellipsis,
@ -15,7 +16,8 @@ const _components = {
AttachmentSelectDrawer, AttachmentSelectDrawer,
AttachmentUploadModal, AttachmentUploadModal,
ReactiveButton, ReactiveButton,
PostTag PostTag,
AttachmentInput
} }
const components = {} const components = {}

View File

@ -141,16 +141,22 @@ export const asyncRouterMap = [
meta: { title: '主题', hiddenHeaderContent: false } meta: { title: '主题', hiddenHeaderContent: false }
}, },
{ {
path: '/interface/menus', path: '/interface/themes/setting',
name: 'MenuList', name: 'ThemeSetting',
component: () => import('@/views/interface/MenuList'), component: () => import('@/views/interface/ThemeSetting'),
meta: { title: '菜单', hiddenHeaderContent: false } meta: { title: '主题设置', hiddenHeaderContent: false }
}, },
{ {
path: '/interface/themes/edit', path: '/interface/themes/edit',
name: 'ThemeEdit', name: 'ThemeEdit',
component: () => import('@/views/interface/ThemeEdit'), component: () => import('@/views/interface/ThemeEdit'),
meta: { title: '主题编辑', hiddenHeaderContent: false } meta: { title: '主题编辑', hiddenHeaderContent: false }
},
{
path: '/interface/menus',
name: 'MenuList',
component: () => import('@/views/interface/MenuList'),
meta: { title: '菜单设置', hiddenHeaderContent: false }
} }
] ]
}, },

View File

@ -52,7 +52,8 @@ import {
TimePicker, TimePicker,
Tooltip, Tooltip,
Tree, Tree,
TreeSelect TreeSelect,
Descriptions
} from 'ant-design-vue' } from 'ant-design-vue'
Vue.use(Affix) Vue.use(Affix)
@ -106,6 +107,7 @@ Vue.use(Steps)
Vue.use(Empty) Vue.use(Empty)
Vue.use(Result) Vue.use(Result)
Vue.use(Space) Vue.use(Space)
Vue.use(Descriptions)
// message config // message config
message.config({ message.config({

View File

@ -6,6 +6,7 @@
<a-page-header :breadcrumb="{ props: { routes: breadList } }" :sub-title="subTitle" :title="title"> <a-page-header :breadcrumb="{ props: { routes: breadList } }" :sub-title="subTitle" :title="title">
<slot slot="extra" name="extra"></slot> <slot slot="extra" name="extra"></slot>
<slot slot="footer" name="footer"></slot> <slot slot="footer" name="footer"></slot>
<slot name="content" />
</a-page-header> </a-page-header>
</div> </div>
</div> </div>
@ -15,6 +16,7 @@
<a-page-header :breadcrumb="{ props: { routes: breadList } }" :sub-title="subTitle" :title="title"> <a-page-header :breadcrumb="{ props: { routes: breadList } }" :sub-title="subTitle" :title="title">
<slot slot="extra" name="extra"></slot> <slot slot="extra" name="extra"></slot>
<slot slot="footer" name="footer"></slot> <slot slot="footer" name="footer"></slot>
<slot name="content" />
</a-page-header> </a-page-header>
</div> </div>
</div> </div>

View File

@ -29,7 +29,7 @@
<a-icon style="margin-right:3px" type="lock" /> <a-icon style="margin-right:3px" type="lock" />
启用 启用
</div> </div>
<div @click="handleOpenThemeSettingDrawer(item)"> <div @click="handleRouteToThemeSetting(item)">
<a-icon style="margin-right:3px" type="setting" /> <a-icon style="margin-right:3px" type="setting" />
设置 设置
</div> </div>
@ -49,7 +49,7 @@
</a-menu-item> </a-menu-item>
<a-menu-item :key="3" @click="handleOpenLocalUpdateModal(item)"> <a-menu-item :key="3" @click="handleOpenLocalUpdateModal(item)">
<a-icon style="margin-right:3px" type="file" /> <a-icon style="margin-right:3px" type="file" />
从主题包更新 本地更新
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</a-dropdown> </a-dropdown>
@ -125,63 +125,36 @@
</a-tabs> </a-tabs>
</div> </div>
</a-modal> </a-modal>
<a-modal
v-model="localUpdateModel.visible" <ThemeDeleteConfirmModal
:afterClose="onThemeInstallModalClose" :theme="themeDeleteModal.selected"
:footer="null" :visible.sync="themeDeleteModal.visible"
destroyOnClose @onAfterClose="themeDeleteModal.selected = {}"
title="更新主题" @success="handleListThemes"
> />
<FilePondUpload
ref="updateByFile" <ThemeLocalUpgradeModal
:accepts="['application/x-zip', 'application/x-zip-compressed', 'application/zip']" :theme="localUpgradeModel.selected"
:field="localUpdateModel.selected.id" :visible.sync="localUpgradeModel.visible"
:multiple="false" @onAfterClose="localUpgradeModel.selected = {}"
:uploadHandler="localUpdateModel.uploadHandler" @success="handleListThemes"
label="点击选择主题更新包或将主题更新包拖拽到此处<br>仅支持 ZIP 格式的文件" />
name="file"
@success="handleUploadSucceed"
></FilePondUpload>
</a-modal>
<a-modal
v-model="themeDeleteModal.visible"
:afterClose="onThemeDeleteModalClose"
:closable="false"
:width="416"
destroyOnClose
title="提示"
>
<template slot="footer">
<a-button @click="themeDeleteModal.visible = false">
取消
</a-button>
<ReactiveButton
:errored="themeDeleteModal.deleteErrored"
:loading="themeDeleteModal.deleting"
erroredText="删除失败"
loadedText="删除成功"
text="确定"
@callback="handleDeleteThemeCallback"
@click="handleDeleteTheme(themeDeleteModal.selected.id, themeDeleteModal.deleteSettings)"
></ReactiveButton>
</template>
<p>确定删除{{ themeDeleteModal.selected.name }}主题</p>
<a-checkbox v-model="themeDeleteModal.deleteSettings">
同时删除主题配置
</a-checkbox>
</a-modal>
</page-view> </page-view>
</template> </template>
<script> <script>
import ThemeSettingDrawer from './components/ThemeSettingDrawer' import ThemeSettingDrawer from './components/ThemeSettingDrawer'
import ThemeDeleteConfirmModal from './components/ThemeDeleteConfirmModal'
import ThemeLocalUpgradeModal from './components/ThemeLocalUpgradeModal'
import { PageView } from '@/layouts' import { PageView } from '@/layouts'
import apiClient from '@/utils/api-client' import apiClient from '@/utils/api-client'
export default { export default {
components: { components: {
PageView, PageView,
ThemeSettingDrawer ThemeSettingDrawer,
ThemeDeleteConfirmModal,
ThemeLocalUpgradeModal
}, },
data() { data() {
return { return {
@ -208,18 +181,14 @@ export default {
} }
}, },
localUpdateModel: { localUpgradeModel: {
visible: false, visible: false,
uploadHandler: (file, options, field) => apiClient.theme.updateByUpload(file, options, field),
selected: {} selected: {}
}, },
themeDeleteModal: { themeDeleteModal: {
visible: false, visible: false,
deleteSettings: false, selected: {}
selected: {},
deleting: false,
deleteErrored: false
}, },
themeSettingDrawer: { themeSettingDrawer: {
@ -245,20 +214,6 @@ export default {
beforeMount() { beforeMount() {
this.handleListThemes() this.handleListThemes()
}, },
destroyed() {
this.$log.debug('Theme list destroyed.')
this.themeSettingDrawer.visible = false
this.installModal.visible = false
this.localUpdateModel.visible = false
this.themeDeleteModal.visible = false
},
beforeRouteLeave(to, from, next) {
this.themeSettingDrawer.visible = false
this.installModal.visible = false
this.localUpdateModel.visible = false
this.themeDeleteModal.visible = false
next()
},
methods: { methods: {
handleListThemes() { handleListThemes() {
this.list.loading = true this.list.loading = true
@ -281,30 +236,8 @@ export default {
this.handleListThemes() this.handleListThemes()
}) })
}, },
handleDeleteTheme(themeId, deleteSettings) {
this.themeDeleteModal.deleting = true
apiClient.theme
.delete(themeId, deleteSettings)
.catch(() => {
this.themeDeleteModal.deleteErrored = false
})
.finally(() => {
setTimeout(() => {
this.themeDeleteModal.deleting = false
}, 400)
})
},
handleDeleteThemeCallback() {
if (this.themeDeleteModal.deleteErrored) {
this.themeDeleteModal.deleteErrored = false
} else {
this.themeDeleteModal.visible = false
this.handleListThemes()
}
},
handleUploadSucceed() { handleUploadSucceed() {
this.installModal.visible = false this.installModal.visible = false
this.localUpdateModel.visible = false
this.handleListThemes() this.handleListThemes()
}, },
handleRemoteFetching() { handleRemoteFetching() {
@ -333,12 +266,11 @@ export default {
} }
}, },
handleOpenLocalUpdateModal(item) { handleOpenLocalUpdateModal(item) {
this.localUpdateModel.selected = item this.localUpgradeModel.selected = item
this.localUpdateModel.visible = true this.localUpgradeModel.visible = true
}, },
handleOpenThemeSettingDrawer(theme) { handleRouteToThemeSetting(theme) {
this.themeSettingDrawer.selected = theme this.$router.push({ name: 'ThemeSetting', query: { themeId: theme.id } })
this.themeSettingDrawer.visible = true
}, },
handleOpenThemeDeleteModal(item) { handleOpenThemeDeleteModal(item) {
this.themeDeleteModal.visible = true this.themeDeleteModal.visible = true
@ -368,20 +300,12 @@ export default {
if (this.$refs.upload) { if (this.$refs.upload) {
this.$refs.upload.handleClearFileList() this.$refs.upload.handleClearFileList()
} }
if (this.$refs.updateByFile) {
this.$refs.updateByFile.handleClearFileList()
}
this.installModal.remote.url = null this.installModal.remote.url = null
this.handleListThemes() this.handleListThemes()
}, },
onThemeSettingsDrawerClose() { onThemeSettingsDrawerClose() {
this.themeSettingDrawer.visible = false this.themeSettingDrawer.visible = false
this.themeSettingDrawer.selected = {} this.themeSettingDrawer.selected = {}
},
onThemeDeleteModalClose() {
this.themeDeleteModal.visible = false
this.themeDeleteModal.deleteSettings = false
this.themeDeleteModal.selected = {}
} }
} }
} }

View File

@ -0,0 +1,286 @@
<template>
<page-view :sub-title="theme.current.version || '-'" :title="theme.current.name || '-'" affix>
<template slot="extra">
<a-dropdown>
<a-menu slot="overlay">
<a-menu-item key="1" @click="handleRemoteUpdate">
<a-icon type="cloud" />
在线更新
</a-menu-item>
<a-menu-item key="2" @click="localUpgradeModel.visible = true">
<a-icon type="file" />
本地更新
</a-menu-item>
</a-menu>
<a-button icon="upload">
更新
<a-icon type="down" />
</a-button>
</a-dropdown>
<a-button
:disabled="theme.current.activated"
icon="delete"
type="danger"
@click="themeDeleteModal.visible = true"
>
删除
</a-button>
</template>
<a-spin :spinning="theme.loading">
<div v-if="theme.current.id" class="card-container">
<a-tabs defaultActiveKey="0" type="card">
<a-tab-pane :key="0" tab="关于">
<a-avatar :alt="theme.current.name" :size="72" :src="theme.current.logo" shape="square" />
<a-divider />
<a-descriptions :column="1" layout="horizontal">
<a-descriptions-item label="作者">
<a :href="theme.current.author.website || '#'">
{{ theme.current.author.name }}
</a>
</a-descriptions-item>
<a-descriptions-item label="介绍">
{{ theme.current.description || '-' }}
</a-descriptions-item>
<a-descriptions-item label="官网">
<a :href="theme.current.website || '#'">
{{ theme.current.website || '-' }}
</a>
</a-descriptions-item>
<a-descriptions-item label="Git 仓库">
<a :href="theme.current.repo || '#'">
{{ theme.current.repo || '-' }}
</a>
</a-descriptions-item>
<a-descriptions-item label="主题标识">
{{ theme.current.id }}
</a-descriptions-item>
<a-descriptions-item label="当前版本">
{{ theme.current.version }}
</a-descriptions-item>
<a-descriptions-item label="存储位置">
{{ theme.current.themePath }}
</a-descriptions-item>
</a-descriptions>
</a-tab-pane>
<a-tab-pane v-for="(group, index) in theme.configurations" :key="index + 1" :tab="group.label">
<a-form
:wrapperCol="{
xl: { span: 8 },
lg: { span: 8 },
sm: { span: 12 },
xs: { span: 24 }
}"
layout="vertical"
>
<a-form-item v-for="(item, formItemIndex) in group.items" :key="formItemIndex" :label="item.label + ''">
<p v-if="item.description && item.description !== ''" slot="help" v-html="item.description"></p>
<a-input
v-if="item.type === 'TEXT'"
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
:placeholder="item.placeholder"
/>
<a-input
v-else-if="item.type === 'TEXTAREA'"
v-model="theme.settings[item.name]"
:autoSize="{ minRows: 5 }"
:placeholder="item.placeholder"
type="textarea"
/>
<a-radio-group
v-else-if="item.type === 'RADIO'"
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
>
<a-radio v-for="(option, radioIndex) in item.options" :key="radioIndex" :value="option.value">
{{ option.label }}
</a-radio>
</a-radio-group>
<a-select
v-else-if="item.type === 'SELECT'"
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
>
<a-select-option v-for="option in item.options" :key="option.value" :value="option.value">
{{ option.label }}
</a-select-option>
</a-select>
<verte
v-else-if="item.type === 'COLOR'"
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
model="hex"
picker="square"
style="display: inline-block;height: 24px;"
></verte>
<AttachmentInput
v-else-if="item.type === 'ATTACHMENT'"
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
:placeholder="item.placeholder"
/>
<a-input-number
v-else-if="item.type === 'NUMBER'"
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
style="width:100%"
/>
<a-switch
v-else-if="item.type === 'SWITCH'"
v-model="theme.settings[item.name]"
:defaultChecked="item.defaultValue"
/>
<a-input
v-else
v-model="theme.settings[item.name]"
:defaultValue="item.defaultValue"
:placeholder="item.placeholder"
/>
</a-form-item>
<a-form-item>
<ReactiveButton
:errored="theme.saveErrored"
:loading="theme.saving"
erroredText="保存失败"
loadedText="保存成功"
text="保存"
type="primary"
@callback="theme.saveErrored = false"
@click="handleSaveSettings"
></ReactiveButton>
</a-form-item>
</a-form>
</a-tab-pane>
</a-tabs>
</div>
</a-spin>
<ThemeDeleteConfirmModal
:theme="theme.current"
:visible.sync="themeDeleteModal.visible"
@success="onThemeDeleteSucceed"
/>
<ThemeLocalUpgradeModal
:theme="theme.current"
:visible.sync="localUpgradeModel.visible"
@success="handleGetTheme"
/>
</page-view>
</template>
<script>
// components
import Verte from 'verte'
import 'verte/dist/verte.css'
import { PageView } from '@/layouts'
import ThemeDeleteConfirmModal from './components/ThemeDeleteConfirmModal'
import ThemeLocalUpgradeModal from './components/ThemeLocalUpgradeModal'
// utils
import apiClient from '@/utils/api-client'
export default {
name: 'ThemeSetting',
components: {
PageView,
Verte,
ThemeDeleteConfirmModal,
ThemeLocalUpgradeModal
},
data() {
return {
theme: {
current: {},
settings: [],
configurations: [],
loading: false,
saving: false,
saveErrored: false
},
themeDeleteModal: {
visible: false
},
localUpgradeModel: {
visible: false
}
}
},
beforeRouteEnter(to, from, next) {
// Get post id from query
const themeId = to.query.themeId
next(async vm => {
await vm.handleGetTheme(themeId)
})
},
methods: {
async handleGetTheme(themeId) {
try {
this.theme.loading = true
if (themeId) {
const { data } = await apiClient.theme.get(themeId)
this.theme.current = data
} else {
const { data } = await apiClient.theme.getActivatedTheme()
this.theme.current = data
}
await this.handleGetConfigurations()
await this.handleGetSettings()
} finally {
this.theme.loading = false
}
},
async handleGetConfigurations() {
try {
const { data } = await apiClient.theme.listConfigurations(this.theme.current.id)
this.theme.configurations = data
} catch (error) {
this.$log.error(error)
}
},
async handleGetSettings() {
try {
const { data } = await apiClient.theme.listSettings(this.theme.current.id)
this.theme.settings = data
} catch (error) {
this.$log.error(error)
}
},
async handleSaveSettings() {
try {
this.theme.saving = true
await apiClient.theme.saveSettings(this.theme.current.id, this.theme.settings)
} catch (error) {
this.$log.error(error)
this.theme.saveErrored = true
} finally {
setTimeout(() => {
this.theme.saving = false
}, 400)
}
},
onThemeDeleteSucceed() {
this.$router.replace({ name: 'ThemeList' })
},
handleRemoteUpdate() {
const _this = this
_this.$confirm({
title: '提示',
maskClosable: true,
content: '确定更新【' + _this.theme.current.name + '】主题?',
async onOk() {
const hideLoading = _this.$message.loading('更新中...', 0)
try {
await apiClient.theme.updateThemeByFetching(_this.theme.current.id)
_this.$message.success('更新成功!')
} catch (e) {
_this.$log.error('Failed to update theme: ', e)
} finally {
hideLoading()
await _this.handleGetTheme(_this.theme.current.id)
}
}
})
}
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<a-modal v-model="modalVisible" :afterClose="onAfterClose" :closable="false" :width="416" destroyOnClose title="提示">
<template slot="footer">
<a-button @click="modalVisible = false">
取消
</a-button>
<ReactiveButton
:errored="deleteErrored"
:loading="deleting"
erroredText="删除失败"
loadedText="删除成功"
text="确定"
@callback="handleDeleteCallback"
@click="handleDelete()"
></ReactiveButton>
</template>
<p>确定删除{{ theme.name }}主题</p>
<a-checkbox v-model="deleteSettings">
同时删除主题配置
</a-checkbox>
</a-modal>
</template>
<script>
import apiClient from '@/utils/api-client'
export default {
name: 'ThemeDeleteConfirmModal',
props: {
visible: {
type: Boolean,
default: false
},
theme: {
type: Object,
default: () => ({})
}
},
data() {
return {
deleteErrored: false,
deleting: false,
deleteSettings: false
}
},
computed: {
modalVisible: {
get() {
return this.visible
},
set(value) {
this.$emit('update:visible', value)
}
}
},
methods: {
async handleDelete() {
try {
this.deleting = true
await apiClient.theme.delete(this.theme.id, this.deleteSettings)
} catch (e) {
this.deleteErrored = false
this.$log.error('Delete theme failed', e)
} finally {
setTimeout(() => {
this.deleting = false
}, 400)
}
},
handleDeleteCallback() {
if (this.deleteErrored) {
this.deleteErrored = false
} else {
this.modalVisible = false
this.$emit('success')
}
},
onAfterClose() {
this.deleteErrored = false
this.deleting = false
this.deleteSettings = false
this.$emit('onAfterClose')
}
}
}
</script>

View File

@ -0,0 +1,56 @@
<template>
<a-modal v-model="modalVisible" :afterClose="onModalClose" :footer="null" destroyOnClose title="更新主题">
<FilePondUpload
ref="updateByFile"
:accepts="['application/x-zip', 'application/x-zip-compressed', 'application/zip']"
:field="theme.id"
:multiple="false"
:uploadHandler="uploadHandler"
label="点击选择主题更新包或将主题更新包拖拽到此处<br>仅支持 ZIP 格式的文件"
name="file"
@success="onThemeUploadSuccess"
></FilePondUpload>
</a-modal>
</template>
<script>
import apiClient from '@/utils/api-client'
export default {
name: 'ThemeLocalUpgradeModal',
props: {
visible: {
type: Boolean,
default: false
},
theme: {
type: Object,
default: () => ({})
}
},
data() {
return {
uploadHandler: (file, options, field) => apiClient.theme.updateByUpload(file, options, field)
}
},
computed: {
modalVisible: {
get() {
return this.visible
},
set(value) {
this.$emit('update:visible', value)
}
}
},
methods: {
onModalClose() {
this.$refs.updateByFile.handleClearFileList()
this.$emit('onAfterClose')
},
onThemeUploadSuccess() {
this.modalVisible = false
this.$emit('success')
}
}
}
</script>

View File

@ -27,6 +27,7 @@
<li>版本{{ environments.version }}</li> <li>版本{{ environments.version }}</li>
<li>数据库{{ environments.database }}</li> <li>数据库{{ environments.database }}</li>
<li>运行模式{{ environments.mode }}</li> <li>运行模式{{ environments.mode }}</li>
<li>启用主题{{ activatedTheme.name }}</li>
<li>启动时间{{ environments.startTime | moment }}</li> <li>启动时间{{ environments.startTime | moment }}</li>
</ul> </ul>
<a class="mr-3" href="https://halo.run" target="_blank" <a class="mr-3" href="https://halo.run" target="_blank"
@ -123,7 +124,8 @@ export default {
checking: false, checking: false,
isLatest: false, isLatest: false,
latestData: {}, latestData: {},
versionContentVisible: false versionContentVisible: false,
activatedTheme: {}
} }
}, },
computed: { computed: {
@ -145,19 +147,24 @@ export default {
}, },
created() { created() {
this.getEnvironments() this.getEnvironments()
this.handleGetActivatedTheme()
this.fetchContributors() this.fetchContributors()
}, },
methods: { methods: {
async getEnvironments() { async getEnvironments() {
await apiClient.getEnvironment().then(response => { const { data } = await apiClient.getEnvironment()
this.environments = response.data this.environments = data
})
this.checkServerUpdate() this.checkServerUpdate()
}, },
async handleGetActivatedTheme() {
const { data } = await apiClient.theme.getActivatedTheme()
this.activatedTheme = data
},
handleCopyEnvironments() { handleCopyEnvironments() {
const text = `版本:${this.environments.version} const text = `版本:${this.environments.version}
数据库${this.environments.database} 数据库${this.environments.database}
运行模式${this.environments.mode} 运行模式${this.environments.mode}
启用主题${this.activatedTheme.name}
User Agent${navigator.userAgent}` User Agent${navigator.userAgent}`
this.$copyText(text) this.$copyText(text)
.then(message => { .then(message => {