Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pleroma/admin-fe
  • linafilippova/admin-fe
  • Exilat_a_Tolosa/admin-fe
  • mkljczk/admin-fe
  • maxf/admin-fe
  • kphrx/admin-fe
  • vaartis/admin-fe
  • ELR/admin-fe
  • eugenijm/admin-fe
  • jp/admin-fe
  • mkfain/admin-fe
  • lorenzoancora/admin-fe
  • alexgleason/admin-fe
  • seanking/admin-fe
  • ilja/admin-fe
15 results
Show changes
Showing
with 548 additions and 144 deletions
// SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
// SPDX-License-Identifier: MIT
export default {
computed: {
device() {
......
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<i v-if="icon" :class="icon" class="menu-item-icon"/>
<span slot="title">{{ title }}</span>
<el-badge :value="count" type="primary" class="count-badge" />
</div>
</template>
<script>
export default {
name: 'MenuItem',
functional: true,
name: 'Item',
props: {
count: {
type: String,
default: null
},
icon: {
type: String,
default: ''
......@@ -11,19 +30,20 @@ export default {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
vnodes.push(<svg-icon icon-class={icon}/>)
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
.count-badge {
margin-left: 5px;
height: 48px;
}
.menu-item-icon {
margin-right: 5px;
width: 18px;
text-align: center;
font-size: 18px;
vertical-align: middle;
}
</style>
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
-->
<template>
<!-- eslint-disable vue/require-component-is -->
......
<template>
<div v-if="!item.hidden&&item.children" class="menu-wrapper">
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<template>
<div v-if="!item.hidden && invitesEnabled" class="menu-wrapper">
<template
v-if="item.children && hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon||item.meta.icon" :title="generateTitle(onlyOneChild.meta.title)" />
<item
v-if="onlyOneChild.meta"
:count="showCount(item) ? normalizedReportsCount : null"
:icon="onlyOneChild.meta.icon||item.meta.icon"
:title="generateTitle(onlyOneChild.meta.title)" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)">
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" :id="item.meta.title">
<template slot="title">
<item v-if="item.meta" :icon="item.meta.icon" :title="generateTitle(item.meta.title)" />
<item
v-if="item.meta"
:count="showCount(item) ? normalizedReportsCount : null"
:icon="item.meta.icon"
:title="generateTitle(item.meta.title)" />
</template>
<template v-for="child in item.children">
......@@ -25,8 +40,12 @@
class="nest-menu" />
<app-link v-else :to="resolvePath(child.path)" :key="child.name">
<el-menu-item :index="resolvePath(child.path)">
<item v-if="child.meta" :icon="child.meta.icon" :title="generateTitle(child.meta.title)" />
<el-menu-item :index="resolvePath(child.path)" class="submenu-item">
<item
v-if="child.meta"
:count="showCount(item) ? normalizedReportsCount : null"
:icon="child.meta.icon"
:title="generateTitle(child.meta.title)" />
</el-menu-item>
</app-link>
</template>
......@@ -43,6 +62,7 @@ import { isExternal } from '@/utils'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
import numeral from 'numeral'
export default {
name: 'SidebarItem',
......@@ -68,16 +88,24 @@ export default {
onlyOneChild: null
}
},
computed: {
invitesEnabled() {
return this.basePath === '/invites' ? this.$store.state.app.invitesEnabled : true
},
normalizedReportsCount() {
return numeral(this.$store.state.reports.openReportsCount).format('0a')
}
},
methods: {
hasOneShowingChild(children, parent) {
if (parent.hasSubmenu) {
return false
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
})
// When there is only one child router, the child router is displayed by default
......@@ -99,6 +127,9 @@ export default {
}
return path.resolve(this.basePath, routePath)
},
showCount(item) {
return item.path === '/reports'
},
isExternalLink(routePath) {
return isExternal(routePath)
},
......@@ -106,3 +137,9 @@ export default {
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.submenu-item {
padding-left: 54px !important;
}
</style>
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
......@@ -7,6 +15,7 @@
:text-color="variables.menuText"
:active-text-color="variables.menuActiveText"
mode="vertical"
@open="handleOpen"
>
<sidebar-item v-for="route in permission_routers" :key="route.path" :item="route" :base-path="route.path"/>
</el-menu>
......@@ -17,13 +26,18 @@
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
import router from '@/router'
import { asyncRouterMap } from '@/router'
export default {
components: { SidebarItem },
computed: {
...mapGetters([
'permission_routers',
'sidebar'
'roles',
'privileges',
'sidebar',
'tabs'
]),
variables() {
return variables
......@@ -31,6 +45,59 @@ export default {
isCollapse() {
return !this.sidebar.opened
}
},
mounted() {
if (this.privileges?.indexOf('reports_manage_reports') !== -1) {
this.$store.dispatch('FetchOpenReportsCount')
}
},
methods: {
getMergedRoutes() {
const routes = router.getRoutes().filter(item => !item.hidden)
return routes.reduce((acc, element) => {
if (!element.parent || element.parent.path !== '/settings') {
return acc
} else {
const index = acc.findIndex(route => route.path === '/settings')
acc[index] = { ...acc[index], children: [...acc[index].children, element] }
return acc
}
}, [...asyncRouterMap])
},
async handleOpen($event) {
if ($event === '/settings') {
let settingsTabs = localStorage.getItem('settingsTabs')
if (settingsTabs === '[]') {
localStorage.removeItem('settingsTabs')
settingsTabs = null
}
if (!settingsTabs) {
await this.$store.dispatch('FetchSettings')
const menuItems = this.tabs
localStorage.setItem('settingsTabs', JSON.stringify(menuItems))
menuItems.forEach(({ label, path }) => {
router.addRoute('Settings', {
path,
component: () => import(`@/views/settings`),
name: label,
meta: { title: label }
})
})
const routes = this.getMergedRoutes()
this.$store.dispatch('GenerateRoutes', { roles: this.roles, _routesWithSettings: routes })
}
let isRequesting = true
const step = () => {
document.querySelector('#settings').scrollIntoView({ block: 'start', behavior: 'smooth' })
if (isRequesting) requestAnimationFrame(step)
}
requestAnimationFrame(step)
setTimeout(() => {
isRequesting = false
}, 300) // this equals to the hide-timeout of the el-submenu
}
}
}
}
</script>
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
......
// SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
// SPDX-License-Identifier: MIT
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar/index.vue'
export { default as TagsView } from './TagsView'
......
// SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
// SPDX-License-Identifier: MIT
//
// SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
// SPDX-License-Identifier: AGPL-3.0-only
import store from '@/store'
const { body } = document
......
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
-->
<script>
export default {
name: 'AuthRedirect',
......
<!--
SPDX-FileCopyrightText: 2017-2019 PanJiaChen <https://github.com/PanJiaChen/vue-element-admin>
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" class="login-form" auto-complete="on" label-position="left">
......@@ -9,7 +17,7 @@
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
<i class="el-icon-user"/>
</span>
<el-input
v-model="loginForm.username"
......@@ -23,7 +31,7 @@
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
<i class="el-icon-key"/>
</span>
<el-input
v-model="loginForm.password"
......
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div />
</template>
......
<template>
<div class="social-signup-container">
<div class="sign-btn" @click="wechatHandleClick('wechat')">
<span class="wx-svg-container"><svg-icon icon-class="wechat" class="icon"/></span> 微信
</div>
<div class="sign-btn" @click="tencentHandleClick('tencent')">
<span class="qq-svg-container"><svg-icon icon-class="qq" class="icon"/></span> QQ
</div>
</div>
</template>
<script>
// import openWindow from '@/utils/openWindow'
export default {
name: 'SocialSignin',
methods: {
wechatHandleClick(thirdpart) {
alert('ok')
// this.$store.commit('SET_AUTH_TYPE', thirdpart)
// const appid = 'xxxxx'
// const redirect_uri = encodeURIComponent('xxx/redirect?redirect=' + window.location.origin + '/auth-redirect')
// const url = 'https://open.weixin.qq.com/connect/qrconnect?appid=' + appid + '&redirect_uri=' + redirect_uri + '&response_type=code&scope=snsapi_login#wechat_redirect'
// openWindow(url, thirdpart, 540, 540)
},
tencentHandleClick(thirdpart) {
alert('ok')
// this.$store.commit('SET_AUTH_TYPE', thirdpart)
// const client_id = 'xxxxx'
// const redirect_uri = encodeURIComponent('xxx/redirect?redirect=' + window.location.origin + '/auth-redirect')
// const url = 'https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=' + client_id + '&redirect_uri=' + redirect_uri
// openWindow(url, thirdpart, 540, 540)
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.social-signup-container {
margin: 20px 0;
.sign-btn {
display: inline-block;
cursor: pointer;
}
.icon {
color: #fff;
font-size: 24px;
margin-top: 8px;
}
.wx-svg-container,
.qq-svg-container {
display: inline-block;
width: 40px;
height: 40px;
line-height: 40px;
text-align: center;
padding-top: 1px;
border-radius: 4px;
margin-bottom: 20px;
margin-right: 5px;
}
.wx-svg-container {
background-color: #24da70;
}
.qq-svg-container {
background-color: #6BA2D6;
margin-left: 50px;
}
}
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div class="media-proxy-cache-container">
<div class="media-proxy-cache-header-container">
<h1>{{ $t('mediaProxyCache.mediaProxyCache') }}</h1>
<reboot-button/>
</div>
<div v-if="mediaProxyEnabled">
<p class="media-proxy-cache-header">{{ $t('mediaProxyCache.evictObjectsHeader') }}</p>
<div class="url-input-container">
<el-input
:placeholder="$t('mediaProxyCache.url')"
v-model="urls"
type="textarea"
autosize
clearable
class="url-input"/>
<el-checkbox v-model="ban">{{ $t('mediaProxyCache.ban') }}</el-checkbox>
<el-button class="evict-button" @click="evictURL">{{ $t('mediaProxyCache.evict') }}</el-button>
</div>
<span class="expl url-input-expl">{{ $t('mediaProxyCache.multipleInput') }}</span>
<p class="media-proxy-cache-header">{{ $t('mediaProxyCache.listBannedUrlsHeader') }}</p>
<el-table
v-loading="loading"
:data="bannedUrls"
class="banned-urls-table"
@selection-change="handleSelectionChange">>
<el-table-column
type="selection"
align="center"
width="55"/>
<el-table-column :min-width="isDesktop ? 320 : 120" prop="url">
<template slot="header" slot-scope="scope">
<el-input
:placeholder="$t('users.search')"
v-model="search"
size="mini"
prefix-icon="el-icon-search"
@input="handleDebounceSearchInput"/>
</template>
</el-table-column>
<el-table-column>
<template slot="header">
<el-button
:disabled="removeSelectedDisabled"
size="mini"
class="remove-url-button"
@click="removeSelected()">{{ $t('mediaProxyCache.removeSelected') }}</el-button>
</template>
<template slot-scope="scope">
<el-button
size="mini"
class="remove-url-button"
@click="removeUrl(scope.row.url)">{{ $t('mediaProxyCache.remove') }}</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!loading" class="pagination">
<el-pagination
:total="urlsCount"
:current-page="currentPage"
:page-size="pageSize"
hide-on-single-page
layout="prev, pager, next"
@current-change="handlePageChange"
/>
</div>
</div>
<div v-else class="enable-mediaproxy-container">
<el-button type="text" @click="enableMediaProxy">{{ $t('mediaProxyCache.enable') }}</el-button>
{{ $t('mediaProxyCache.invalidationAndMediaProxy') }}
</div>
</div>
</template>
<script>
import debounce from 'lodash.debounce'
import RebootButton from '@/components/RebootButton'
export default {
name: 'MediaProxyCache',
components: { RebootButton },
data() {
return {
urls: '',
ban: false,
search: '',
selectedUrls: []
}
},
computed: {
bannedUrls() {
return this.$store.state.mediaProxyCache.bannedUrls
},
currentPage() {
return this.$store.state.mediaProxyCache.currentPage
},
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
loading() {
return this.$store.state.mediaProxyCache.loading
},
mediaProxyEnabled() {
return this.$store.state.mediaProxyCache.mediaProxyEnabled
},
pageSize() {
return this.$store.state.mediaProxyCache.pageSize
},
removeSelectedDisabled() {
return this.selectedUrls.length === 0
},
urlsCount() {
return this.$store.state.mediaProxyCache.totalUrlsCount
}
},
created() {
this.handleDebounceSearchInput = debounce((query) => {
this.$store.dispatch('SearchUrls', { query, page: 1 })
}, 500)
},
mounted() {
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('NeedReboot')
this.$store.dispatch('FetchMediaProxySetting')
this.$store.dispatch('ListBannedUrls', { page: 1 })
},
methods: {
enableMediaProxy() {
this.$confirm(
this.$t('mediaProxyCache.confirmEnablingMediaProxy'),
{
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
this.$message({
type: 'success',
message: this.$t('mediaProxyCache.enableMediaProxySuccessMessage')
})
this.$store.dispatch('EnableMediaProxy')
}).catch(() => {
this.$message({
type: 'info',
message: 'Canceled'
})
})
},
evictURL() {
const urls = this.splitUrls(this.urls)
this.$store.dispatch('PurgeUrls', { urls, ban: this.ban })
this.urls = ''
},
handlePageChange(page) {
this.$store.dispatch('ListBannedUrls', { page })
},
handleSelectionChange(value) {
this.$data.selectedUrls = value
},
removeSelected() {
const urls = this.selectedUrls.map(el => el.url)
this.$store.dispatch('RemoveBannedUrls', urls)
this.selectedUrls = []
},
removeUrl(url) {
this.$store.dispatch('RemoveBannedUrls', [url])
},
splitUrls(urls) {
return urls.split(',').map(url => url.trim()).filter(el => el.length > 0)
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
h1 {
margin: 0;
}
.enable-mediaproxy-container {
margin: 10px 15px;
button {
font-size: 16px;
}
}
.expl {
color: #666666;
font-size: 13px;
line-height: 22px;
margin: 5px 0 0 0;
overflow-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
}
.banned-urls-table {
margin-top: 15px;
margin-bottom: 15px;
}
.evict-button {
margin-left: 15px;
}
.media-proxy-cache-header {
margin-left: 15px;
margin-top: 22px;
font-weight: 500;
}
.media-proxy-cache-header-container {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 15px;
}
.pagination {
margin: 25px 0;
text-align: center;
}
.remove-url-button {
width: 150px;
}
.url-input {
margin-right: 15px;
}
.url-input-container {
display: flex;
align-items: baseline;
margin: 15px 15px 5px 15px;
}
.url-input-expl {
margin-left: 15px;
}
@media only screen and (max-width:480px) {
.url-input {
width: 100%;
margin-bottom: 5px;
}
}
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<span>
<router-link
v-if="propertyExists(actor, 'id')"
:to="{ name: 'UsersShow', params: { id: actor.id }}"
class="router-link">
<span v-if="propertyExists(actor, 'nickname')" style="font-weight: 600">
@{{ actor.nickname }}
</span>
</router-link>
<span v-if="subject.type === 'report' && propertyExists(subject, 'id')">
{{ logEntryMessageWithoutId[0] }}
<router-link
:to="{ name: 'ReportsShow', params: { id: subject.id }}"
class="router-link">
<span style="font-weight: 600">#{{ subject.id }}</span>
</router-link>
{{ logEntryMessageWithoutId[1] }}
</span>
<span v-else>{{ logEntryMessage }}</span>
</span>
</template>
<script>
export default {
name: 'LogEntryMessage',
props: {
actor: {
type: Object,
required: true
},
message: {
type: String,
required: true
},
subject: {
type: [Object, Array],
required: false,
default: function() {
return {}
}
}
},
computed: {
logEntryMessage() {
return this.actor.nickname ? this.message.split(this.actor.nickname)[1] : this.message
},
logEntryMessageWithoutId() {
return this.logEntryMessage.split(`#${this.subject.id}`)
}
},
methods: {
propertyExists(account, property) {
return account[property]
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.router-link {
text-decoration: none;
}
</style>
<!--
SPDX-FileCopyrightText: 2019-2022 Pleroma Authors <https://pleroma.social>
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div v-if="!loading" class="moderation-log-container">
<h1>{{ $t('moderationLog.moderationLog') }}</h1>
<div class="moderation-log-header-container">
<h1>{{ $t('moderationLog.moderationLog') }}</h1>
<reboot-button/>
</div>
<div class="moderation-log-nav-container">
<el-select
v-model="user"
......@@ -40,7 +48,8 @@
v-for="(logEntry, index) in log"
:key="index"
:timestamp="normalizeTimestamp(logEntry.time)">
{{ logEntry.message }}
<log-entry-message v-if="propertyExists(logEntry.data.actor, 'nickname')" :actor="logEntry.data.actor" :message="logEntry.message" :subject="logEntry.data.subject"/>
<span v-else>{{ logEntry.message }}</span>
</el-timeline-item>
</el-timeline>
<div class="pagination">
......@@ -57,11 +66,14 @@
</template>
<script>
import moment from 'moment'
import { DateTime } from 'luxon'
import _ from 'lodash'
import debounce from 'lodash.debounce'
import RebootButton from '@/components/RebootButton'
import LogEntryMessage from './LogEntryMessage'
export default {
components: { RebootButton, LogEntryMessage },
data() {
return {
dateRange: '',
......@@ -103,13 +115,12 @@ export default {
}, 500)
},
mounted() {
this.$store.dispatch('GetNodeInfo')
this.$store.dispatch('NeedReboot')
this.$store.dispatch('FetchModerationLog')
this.$store.dispatch('FetchAdmins')
},
methods: {
normalizeTimestamp(timestamp) {
return moment(timestamp * 1000).format('YYYY-MM-DD HH:mm')
},
fetchLogWithFilters() {
const filters = _.omitBy({
start_date: this.dateRange ? this.dateRange[0].toISOString() : null,
......@@ -120,6 +131,12 @@ export default {
}, val => val === '' || val === null)
this.$store.dispatch('FetchModerationLog', filters)
},
normalizeTimestamp(timestamp) {
return DateTime.fromSeconds(timestamp).toFormat('yyyy-MM-dd HH:mm')
},
propertyExists(account, property) {
return account[property]
}
}
}
......@@ -130,7 +147,7 @@ export default {
margin: 0 15px;
}
h1 {
margin: 22px 0 20px 0;
margin: 0;
}
.el-timeline {
margin: 25px 45px 0 0;
......@@ -139,6 +156,12 @@ h1 {
.moderation-log-date-panel {
width: 350px;
}
.moderation-log-header-container {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 0 15px 0;
}
.moderation-log-nav-container {
display: flex;
justify-content: space-between;
......@@ -150,14 +173,19 @@ h1 {
margin: 0 0 20px;
width: 350px;
}
.search-container {
text-align: right;
.reboot-button {
padding: 10px;
margin: 0;
width: 145px;
}
.pagination {
text-align: center;
}
@media only screen and (max-width:480px) {
h1 {
font-size: 24px;
}
.moderation-log-date-panel {
width: 100%;
}
......
<template >
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1">
<router-view />
</el-alert>
</div>
</template>
<template >
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-1" type="success">
<router-view />
</el-alert>
</div>
</template>
<template>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-2" type="success">
<router-view />
</el-alert>
</div>
</template>
<template functional>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-2-1" type="warning" />
</div>
</template>
<template functional>
<div style="padding:30px;">
<el-alert :closable="false" title="menu 1-2-2" type="warning" />
</div>
</template>