Files
abot/admin/dashboard/templates/base.html
liuwei d4b7cb32f6 feat(群级配置): 新增MySQL+Redis持久缓存并接入进群欢迎差异化配置
新增群级插件配置表与服务层,采用MySQL持久化+Redis长期缓存(TTL=-1);后台新增群级插件配置管理页面与API,支持按群按插件维护JSON配置并在修改后同步回填MySQL和刷新Redis;已将群成员变更监控插件接入该配置,支持欢迎文案与卡片URL等按群差异化。
2026-04-20 10:42:46 +08:00

959 lines
31 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}机器人管理后台{% endblock %}</title>
<link rel="icon" href="/static/favicon.ico">
<style>
:root {
--bg: #f4f7fb;
--bg-soft: #eef3f8;
--surface: rgba(255, 255, 255, 0.86);
--surface-strong: #ffffff;
--surface-muted: #f8fafc;
--border: rgba(148, 163, 184, 0.18);
--border-strong: rgba(148, 163, 184, 0.26);
--text: #0f172a;
--text-soft: #475569;
--text-faint: #94a3b8;
--primary: #4f46e5;
--primary-soft: rgba(79, 70, 229, 0.10);
--primary-soft-2: rgba(99, 102, 241, 0.16);
--info: #3b82f6;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--shadow-sm: 0 8px 24px rgba(15, 23, 42, 0.06);
--shadow-md: 0 18px 48px rgba(15, 23, 42, 0.08);
--radius-xs: 10px;
--radius-sm: 14px;
--radius-md: 18px;
--radius-lg: 24px;
--topbar-height: 72px;
--subnav-height: 64px;
--content-padding: 24px;
}
* {
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
margin: 0;
min-height: 100vh;
overflow: hidden;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(99, 102, 241, 0.10), transparent 28%),
radial-gradient(circle at top right, rgba(56, 189, 248, 0.08), transparent 24%),
linear-gradient(180deg, #f8fafc 0%, #f3f6fb 100%);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
h1, h2, h3, h4, h5, h6, p {
margin: 0;
}
.app-container {
min-height: 100vh;
opacity: 0;
transition: opacity .24s ease;
}
.app-container.loaded {
opacity: 1;
}
.topbar {
position: sticky;
top: 0;
z-index: 200;
height: var(--topbar-height);
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
padding: 0 24px;
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.04);
}
.topbar-left,
.topbar-right {
display: flex;
align-items: center;
gap: 16px;
min-width: 0;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.brand-logo {
width: 40px;
height: 40px;
border-radius: 14px;
object-fit: cover;
box-shadow: 0 10px 20px rgba(79, 70, 229, 0.16);
}
.brand-copy {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.brand-title {
font-size: 17px;
font-weight: 700;
color: var(--text);
letter-spacing: 0.01em;
}
.brand-subtitle {
font-size: 12px;
color: var(--text-faint);
white-space: nowrap;
}
.main-nav {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
overflow-x: auto;
padding: 4px;
border-radius: 999px;
background: rgba(255,255,255,0.58);
border: 1px solid rgba(148, 163, 184, 0.14);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
}
.main-nav::-webkit-scrollbar,
.sub-nav::-webkit-scrollbar {
display: none;
}
.main-nav-link,
.sub-nav-link {
appearance: none;
border: none;
background: transparent;
color: var(--text-soft);
cursor: pointer;
font: inherit;
white-space: nowrap;
transition: all .18s ease;
}
.main-nav-link {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 38px;
padding: 0 16px;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
}
.main-nav-link:hover {
background: rgba(255,255,255,0.92);
color: var(--text);
}
.main-nav-link.is-active {
color: var(--primary);
background: linear-gradient(135deg, rgba(79,70,229,0.12), rgba(99,102,241,0.08));
box-shadow: inset 0 0 0 1px rgba(99,102,241,0.10);
}
.status-pill,
.user-pill {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 999px;
background: rgba(255,255,255,0.72);
border: 1px solid var(--border);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.9);
color: var(--text-soft);
font-size: 13px;
white-space: nowrap;
}
.status-dot,
.user-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.status-dot {
background: var(--success);
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12);
}
.user-dot {
background: var(--info);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.10);
}
.logout-btn {
color: var(--text-soft) !important;
padding: 10px 14px !important;
border-radius: 999px !important;
transition: all .18s ease !important;
}
.logout-btn:hover {
color: var(--primary) !important;
background: var(--primary-soft) !important;
}
.subnav-bar {
position: sticky;
top: var(--topbar-height);
z-index: 190;
height: var(--subnav-height);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0 24px;
background: rgba(248, 250, 252, 0.84);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
}
.subnav-title {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.subnav-title h2 {
font-size: 19px;
font-weight: 700;
color: var(--text);
}
.subnav-title p {
font-size: 13px;
color: var(--text-soft);
}
.sub-nav {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
overflow-x: auto;
padding-bottom: 2px;
}
.subnav-right {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.sub-nav-link {
min-height: 36px;
padding: 0 14px;
border-radius: 999px;
border: 1px solid rgba(148, 163, 184, 0.12);
background: rgba(255,255,255,0.64);
font-size: 13px;
font-weight: 500;
}
.sub-nav-link:hover {
background: rgba(255,255,255,0.95);
color: var(--text);
border-color: rgba(148, 163, 184, 0.18);
}
.sub-nav-link.is-active {
background: linear-gradient(135deg, rgba(79,70,229,0.12), rgba(99,102,241,0.08));
color: var(--primary);
border-color: rgba(99, 102, 241, 0.12);
}
.layout-shell {
height: calc(100vh - var(--topbar-height) - var(--subnav-height));
overflow: hidden;
}
.content {
height: 100%;
overflow-y: auto;
padding: var(--content-padding);
}
.content-inner {
min-height: calc(100vh - var(--topbar-height) - var(--subnav-height) - (var(--content-padding) * 2));
}
.toolbar-card,
.el-card {
border-radius: var(--radius-md) !important;
border: 1px solid var(--border) !important;
background: rgba(255, 255, 255, 0.82) !important;
box-shadow: var(--shadow-sm) !important;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
overflow: hidden;
}
.el-card__header {
padding: 16px 18px !important;
background: linear-gradient(180deg, rgba(255,255,255,0.82), rgba(248,250,252,0.92)) !important;
border-bottom: 1px solid rgba(148, 163, 184, 0.12) !important;
color: var(--text) !important;
}
.el-card__body {
padding: 18px !important;
}
.el-button {
border-radius: 12px !important;
font-weight: 500 !important;
transition: all .18s ease !important;
}
.el-button--primary {
background: linear-gradient(135deg, #4f46e5, #6366f1) !important;
border-color: transparent !important;
box-shadow: 0 10px 20px rgba(79, 70, 229, 0.18) !important;
}
.el-button--primary:hover,
.el-button--primary:focus {
transform: translateY(-1px);
box-shadow: 0 14px 24px rgba(79, 70, 229, 0.22) !important;
}
.el-button--primary.is-plain,
.el-button--success.is-plain,
.el-button--warning.is-plain,
.el-button--danger.is-plain,
.el-button--info.is-plain {
background: rgba(255, 255, 255, 0.96) !important;
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06) !important;
}
.el-button--primary.is-plain {
color: #4338ca !important;
border-color: rgba(99, 102, 241, 0.34) !important;
}
.el-button--primary.is-plain:hover,
.el-button--primary.is-plain:focus {
color: #ffffff !important;
border-color: transparent !important;
background: linear-gradient(135deg, #4f46e5, #6366f1) !important;
box-shadow: 0 12px 22px rgba(79, 70, 229, 0.20) !important;
}
.el-button--success.is-plain {
color: #047857 !important;
border-color: rgba(16, 185, 129, 0.34) !important;
}
.el-button--success.is-plain:hover,
.el-button--success.is-plain:focus {
color: #ffffff !important;
border-color: transparent !important;
background: linear-gradient(135deg, #10b981, #34d399) !important;
box-shadow: 0 12px 22px rgba(16, 185, 129, 0.20) !important;
}
.el-button--warning.is-plain {
color: #b45309 !important;
border-color: rgba(245, 158, 11, 0.34) !important;
}
.el-button--warning.is-plain:hover,
.el-button--warning.is-plain:focus {
color: #ffffff !important;
border-color: transparent !important;
background: linear-gradient(135deg, #f59e0b, #fbbf24) !important;
box-shadow: 0 12px 22px rgba(245, 158, 11, 0.20) !important;
}
.el-button--danger.is-plain {
color: #b91c1c !important;
border-color: rgba(239, 68, 68, 0.34) !important;
}
.el-button--danger.is-plain:hover,
.el-button--danger.is-plain:focus {
color: #ffffff !important;
border-color: transparent !important;
background: linear-gradient(135deg, #ef4444, #f87171) !important;
box-shadow: 0 12px 22px rgba(239, 68, 68, 0.20) !important;
}
.el-button--info.is-plain {
color: #475569 !important;
border-color: rgba(100, 116, 139, 0.34) !important;
}
.el-button--info.is-plain:hover,
.el-button--info.is-plain:focus {
color: #ffffff !important;
border-color: transparent !important;
background: linear-gradient(135deg, #64748b, #94a3b8) !important;
box-shadow: 0 12px 22px rgba(100, 116, 139, 0.20) !important;
}
.el-button--default {
background: rgba(255,255,255,0.85) !important;
border-color: var(--border-strong) !important;
color: var(--text) !important;
}
.el-button--default:hover,
.el-button--default:focus {
border-color: rgba(99,102,241,0.3) !important;
color: var(--primary) !important;
background: rgba(255,255,255,0.96) !important;
}
.el-button--text,
.el-button--text:not(.is-disabled) {
color: #334155 !important;
font-weight: 600 !important;
padding: 6px 10px !important;
border-radius: 10px !important;
background: rgba(248, 250, 252, 0.9) !important;
border: 1px solid rgba(148, 163, 184, 0.14) !important;
}
.el-button--text:hover,
.el-button--text:focus,
.el-button--text:not(.is-disabled):hover,
.el-button--text:not(.is-disabled):focus {
color: var(--primary) !important;
background: rgba(99, 102, 241, 0.08) !important;
border-color: rgba(99, 102, 241, 0.18) !important;
}
.el-button--text [class*="el-icon-"],
.el-button--text span {
color: inherit !important;
}
.el-table .el-button--text,
.el-table .el-button--text:not(.is-disabled),
.el-card .el-button--text,
.el-card .el-button--text:not(.is-disabled) {
color: #1e293b !important;
background: rgba(255, 255, 255, 0.92) !important;
border-color: rgba(100, 116, 139, 0.18) !important;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.04) !important;
}
.el-table .el-button--text:hover,
.el-table .el-button--text:focus,
.el-card .el-button--text:hover,
.el-card .el-button--text:focus {
color: var(--primary) !important;
background: rgba(99, 102, 241, 0.10) !important;
border-color: rgba(99, 102, 241, 0.20) !important;
box-shadow: 0 8px 16px rgba(79, 70, 229, 0.10) !important;
}
.el-button--success,
.el-button--warning,
.el-button--danger,
.el-button--info {
color: #fff !important;
border-color: transparent !important;
}
.el-button--success { background: linear-gradient(135deg, #10b981, #34d399) !important; }
.el-button--warning { background: linear-gradient(135deg, #f59e0b, #fbbf24) !important; }
.el-button--danger { background: linear-gradient(135deg, #ef4444, #f87171) !important; }
.el-button--info { background: linear-gradient(135deg, #64748b, #94a3b8) !important; }
.el-input__inner,
.el-textarea__inner {
height: 40px;
border-radius: 12px !important;
background: rgba(248,250,252,0.9) !important;
border-color: rgba(148,163,184,0.22) !important;
color: var(--text) !important;
transition: all .18s ease !important;
}
.el-textarea__inner {
min-height: 110px;
}
.el-input__inner::placeholder,
.el-textarea__inner::placeholder {
color: var(--text-faint) !important;
}
.el-input__inner:focus,
.el-textarea__inner:focus {
border-color: rgba(99,102,241,0.42) !important;
box-shadow: 0 0 0 4px rgba(99,102,241,0.10) !important;
background: #fff !important;
}
.el-tag {
border-radius: 999px !important;
border: none !important;
padding: 0 10px !important;
font-weight: 600;
}
.el-table {
color: var(--text) !important;
background: transparent !important;
border-radius: 14px !important;
overflow: hidden;
}
.el-table::before,
.el-table--group::after,
.el-table--border::after,
.el-table__fixed-right::before,
.el-table__fixed::before {
display: none !important;
}
.el-table th {
background: rgba(248,250,252,0.95) !important;
color: var(--text-soft) !important;
font-weight: 600 !important;
border-bottom: 1px solid rgba(148,163,184,0.12) !important;
}
.el-table tr,
.el-table td,
.el-table__expanded-cell {
background: rgba(255,255,255,0.45) !important;
}
.el-table td,
.el-table th.is-leaf {
border-bottom: 1px solid rgba(148,163,184,0.10) !important;
}
.el-table--border,
.el-table--group {
border: 1px solid rgba(148,163,184,0.14) !important;
}
.el-table--border th,
.el-table--border td {
border-right: 1px solid rgba(148,163,184,0.08) !important;
}
.el-table__row:hover > td {
background: rgba(99,102,241,0.05) !important;
}
.el-dialog,
.el-message-box {
border-radius: 22px !important;
overflow: hidden !important;
border: 1px solid rgba(148,163,184,0.14) !important;
box-shadow: var(--shadow-md) !important;
background: rgba(255,255,255,0.94) !important;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.el-dialog__header,
.el-message-box__header {
padding: 18px 20px !important;
border-bottom: 1px solid rgba(148,163,184,0.10) !important;
background: rgba(248,250,252,0.86) !important;
}
.el-dialog__body,
.el-message-box__content {
color: var(--text) !important;
}
.el-pagination .el-pager li,
.el-pagination button {
border-radius: 10px !important;
border: 1px solid rgba(148,163,184,0.14) !important;
background: rgba(255,255,255,0.92) !important;
}
.el-pagination .el-pager li.active {
background: linear-gradient(135deg, #4f46e5, #6366f1) !important;
color: #fff !important;
}
.el-tabs__item {
color: var(--text-soft) !important;
}
.el-tabs__item.is-active,
.el-tabs__item:hover {
color: var(--primary) !important;
}
.el-tabs__active-bar {
background: var(--primary) !important;
height: 3px !important;
border-radius: 999px !important;
}
.toolbar-card {
margin-bottom: 18px;
}
.time-filter-form {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.time-range-inline {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: 999px;
background: rgba(255,255,255,0.82);
border: 1px solid rgba(148, 163, 184, 0.16);
box-shadow: 0 6px 14px rgba(15, 23, 42, 0.05);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.time-range-inline .el-select .el-input__inner {
height: 32px !important;
border-radius: 999px !important;
padding-left: 12px !important;
padding-right: 30px !important;
min-width: 110px;
font-size: 12px !important;
}
.time-range-inline .el-button {
min-height: 32px !important;
padding: 0 10px !important;
border-radius: 999px !important;
}
.v-modal {
background: rgba(15, 23, 42, 0.16) !important;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
a {
color: var(--primary);
}
a:hover {
color: #4338ca;
}
@media (max-width: 1320px) {
.topbar,
.subnav-bar,
.content {
padding-left: 18px;
padding-right: 18px;
}
.topbar {
gap: 12px;
}
.status-pill {
display: none;
}
}
</style>
<link rel="stylesheet" href="/static/css/element-ui/theme-chalk/index.min.css">
{% block styles %}{% endblock %}
<script src="/static/js/chart.js"></script>
<script src="/static/js/vue.js"></script>
<script src="/static/js/element-ui/index.min.js"></script>
<script src="/static/js/axios.min.js"></script>
{% block head %}{% endblock %}
</head>
<body>
<div id="app" class="app-container">
<header class="topbar">
<div class="topbar-left">
<div class="brand">
<img src="/static/logo.png" class="brand-logo" alt="logo">
<div class="brand-copy">
<div class="brand-title">机器人控制台</div>
<div class="brand-subtitle">更产品化的运营、消息与系统工作台</div>
</div>
</div>
<nav class="main-nav" aria-label="主导航">
<button
v-for="item in navGroups"
:key="item.key"
class="main-nav-link"
:class="{ 'is-active': activeGroup === item.key }"
@click="goToPrimary(item)">
<i :class="item.icon"></i>
<span>{% raw %}{{ item.label }}{% endraw %}</span>
</button>
</nav>
</div>
<div class="topbar-right">
<div class="status-pill">
<span class="status-dot"></span>
<span>控制台在线</span>
</div>
<div class="user-pill">
<span class="user-dot"></span>
<span>管理员已登录</span>
</div>
<el-button type="text" class="logout-btn" @click="logout">
<i class="el-icon-switch-button"></i> 退出
</el-button>
</div>
</header>
<section class="subnav-bar">
<div class="subnav-title">
<h2>{% raw %}{{ activeGroupMeta.label }}{% endraw %}</h2>
<p>{% raw %}{{ activeGroupMeta.description }}{% endraw %}</p>
</div>
<div class="subnav-right">
<div v-if="showTimeRangeSelector" class="time-range-inline">
<el-select v-model="timeRange" size="mini" @change="loadData">
<el-option label="最近7天" value="7"></el-option>
<el-option label="最近30天" value="30"></el-option>
<el-option label="最近90天" value="90"></el-option>
</el-select>
<el-button size="mini" type="primary" plain @click="loadData">
<i class="el-icon-refresh"></i>
</el-button>
</div>
<nav class="sub-nav" aria-label="二级导航">
<button
v-for="item in currentSubNav"
:key="item.path"
class="sub-nav-link"
:class="{ 'is-active': isPathActive(item.path) }"
@click="go(item.path)">
{% raw %}{{ item.label }}{% endraw %}
</button>
</nav>
</div>
</section>
<div class="layout-shell">
<main class="content">
<div class="content-inner">
{% block content %}{% endblock %}
</div>
</main>
</div>
</div>
<script>
const NAV_GROUPS = [
{
key: 'overview',
label: '概览',
icon: 'el-icon-s-home',
description: '查看整体运行、趋势与核心经营指标',
defaultPath: '/',
items: [
{ label: '首页概览', path: '/' }
]
},
{
key: 'messages',
label: '消息',
icon: 'el-icon-chat-line-square',
description: '处理消息、日志与定时推送相关工作',
defaultPath: '/messages',
items: [
{ label: '消息列表', path: '/messages' },
{ label: '定时推送', path: '/message_push' }
]
},
{
key: 'friend-circle',
label: '朋友圈',
icon: 'el-icon-picture-outline-round',
description: '集中管理朋友圈查看、发布、点赞与评论流程',
defaultPath: '/friend_circle',
items: [
{ label: '朋友圈管理', path: '/friend_circle' }
]
},
{
key: 'groups',
label: '群组',
icon: 'el-icon-s-cooperation',
description: '管理群组、权限、通讯录与虚拟群组能力',
defaultPath: '/groups',
items: [
{ label: '群组统计', path: '/groups' },
{ label: '通讯录', path: '/contacts' },
{ label: '虚拟群组', path: '/virtual_group' }
]
},
{
key: 'plugins',
label: '插件',
icon: 'el-icon-s-grid',
description: '查看插件使用、管理插件与接口说明',
defaultPath: '/plugins',
items: [
{ label: '插件统计', path: '/plugins' },
{ label: '插件管理', path: '/plugins_manage' },
{ label: '插件定时任务', path: '/plugin_schedules' },
{ label: '群级插件配置', path: '/group_plugin_config' },
{ label: '接口文档', path: '/api_docs' }
]
},
{
key: 'system',
label: '系统',
icon: 'el-icon-cpu',
description: '关注用户、资源与文件等平台基础能力',
defaultPath: '/users',
items: [
{ label: '用户统计', path: '/users' },
{ label: '资源监控', path: '/system_status' },
{ label: '系统定时任务', path: '/system_jobs' },
{ label: '全局配置', path: '/system_llm' },
{ label: '文件浏览', path: '/file_browser' },
{ label: '运行日志', path: '/wx_logs' },
{ label: '错误日志', path: '/errors' }
]
}
];
const baseApp = {
data() {
return {
currentView: '1',
timeRange: '7',
showTimeRangeSelector: false,
navGroups: NAV_GROUPS
}
},
computed: {
activeGroup() {
const path = this.normalizePath(window.location.pathname);
const matched = this.navGroups.find(group =>
group.items.some(item => this.normalizePath(item.path) === path)
);
return matched ? matched.key : 'overview';
},
activeGroupMeta() {
return this.navGroups.find(group => group.key === this.activeGroup) || this.navGroups[0];
},
currentSubNav() {
return this.activeGroupMeta.items || [];
}
},
created() {
const path = window.location.pathname;
this.showTimeRangeSelector = ['/', '/plugins', '/users', '/groups', '/errors'].includes(path);
},
mounted() {
document.querySelector('.app-container').classList.add('loaded');
},
methods: {
normalizePath(path) {
if (!path) return '/';
if (path.length > 1 && path.endsWith('/')) {
return path.slice(0, -1);
}
return path;
},
isPathActive(path) {
return this.normalizePath(window.location.pathname) === this.normalizePath(path);
},
go(path) {
if (path && this.normalizePath(window.location.pathname) !== this.normalizePath(path)) {
window.location.href = path;
}
},
goToPrimary(group) {
if (!group) return;
const current = this.normalizePath(window.location.pathname);
const ownPaths = (group.items || []).map(item => this.normalizePath(item.path));
if (ownPaths.includes(current)) return;
this.go(group.defaultPath || (group.items && group.items[0] && group.items[0].path));
},
handleSelect(key) {
const routes = {
'1': '/',
'2': '/plugins',
'3': '/users',
'4': '/groups',
'5': '/errors',
'7': '/messages',
'9': '/wx_logs',
'10': '/contacts',
'11': '/plugins_manage',
'12': '/virtual_group',
'13': '/api_docs',
'14': '/system_status',
'17': '/system_llm',
'15': '/file_browser',
'16': '/message_push'
};
if (routes[key] && window.location.pathname !== routes[key]) {
window.location.href = routes[key];
}
},
logout() {
this.$confirm('确认退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
window.location.href = '/logout';
});
}
}
};
</script>
{% block scripts %}{% endblock %}
</body>
</html>