feat(admin): 实现管理员登录认证和页面权限控制
- 添加管理员密码验证登录功能 - 实现登录状态管理和用户界面显示 - 集成权限检查确保页面访问安全 - 添加登录/登出流程处理 - 重构AdminView组件结构和样式 - 集成Element Plus图标和UI组件 - 添加设置页面路由配置 - 优化Token管理页面折叠表单设计 - 移除旧的响应式布局相关代码 - 更新应用标题动态渲染逻辑
This commit is contained in:
Vendored
+3
@@ -13,7 +13,10 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AddDataDialog: typeof import('./src/components/AddDataDialog.vue')['default']
|
AddDataDialog: typeof import('./src/components/AddDataDialog.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
|
|||||||
@@ -57,16 +57,4 @@ html, body {
|
|||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
margin: 20px 0 16px;
|
margin: 20px 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.content-card {
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide-on-mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
+16
-8
@@ -1,21 +1,29 @@
|
|||||||
import {createApp} from 'vue'
|
import {createApp} from 'vue'
|
||||||
import {createPinia} from 'pinia'
|
import {createPinia} from 'pinia'
|
||||||
import persistedState from 'pinia-plugin-persistedstate';
|
import persistedState from 'pinia-plugin-persistedstate';
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import './assets/main.css'
|
import './assets/main.css'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(persistedState)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
app.use(pinia)
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
document.title = '抖音数据去重 - dev'
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(createPinia().use(persistedState))
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(ElementPlus)
|
app.use(ElementPlus)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
var postfix: string = ''
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
postfix = '-dev'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.meta.title) {
|
||||||
|
document.title = to.meta.title + postfix
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -4,17 +4,20 @@ import TokenManageView from '@/views/admin/TokenManageView.vue'
|
|||||||
import TokenDetailView from '@/views/admin/TokenDetailView.vue'
|
import TokenDetailView from '@/views/admin/TokenDetailView.vue'
|
||||||
import AdminView from '@/views/AdminView.vue'
|
import AdminView from '@/views/AdminView.vue'
|
||||||
import HomeView from '@/views/HomeView.vue'
|
import HomeView from '@/views/HomeView.vue'
|
||||||
|
import SettingsView from '@/views/admin/SettingsView.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: "Home",
|
name: "Home",
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
|
meta: {title: "抖音数据去重",}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/haha',
|
path: '/haha',
|
||||||
name: "Admin",
|
name: "Admin",
|
||||||
component: AdminView,
|
component: AdminView,
|
||||||
|
meta: {title: "抖音数据去重 | 管理后台",},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -26,6 +29,11 @@ const routes = [
|
|||||||
name: "TokenDetail",
|
name: "TokenDetail",
|
||||||
component: TokenDetailView
|
component: TokenDetailView
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "settings",
|
||||||
|
name: "AdminSettings",
|
||||||
|
component: SettingsView,
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,9 @@ import {ref} from 'vue'
|
|||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
const homeToken = ref("")
|
|
||||||
|
|
||||||
const token = ref("")
|
const token = ref("")
|
||||||
|
|
||||||
const isAdmin = ref(false)
|
const isAdmin = ref(false)
|
||||||
|
|
||||||
|
|
||||||
return {token, isAdmin}
|
return {token, isAdmin}
|
||||||
}, {
|
}, {
|
||||||
persist: true
|
persist: true
|
||||||
|
|||||||
+134
-25
@@ -1,28 +1,49 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useCounterStore} from "@/stores/counter.ts"
|
import {useCounterStore} from "@/stores/counter.ts"
|
||||||
import {CloseBold, Document, Edit, User} from '@element-plus/icons-vue'
|
import {CloseBold, Document, Edit, Lock, Setting, User} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const store = useCounterStore()
|
const store = useCounterStore()
|
||||||
|
|
||||||
const activeIndex = ref(route.name?.toString() || "TokenManage")
|
const activeIndex = ref(route.name?.toString() || "TokenManage")
|
||||||
|
const _userName = ref("未登录")
|
||||||
|
const _isAdmin = ref(store.isAdmin)
|
||||||
|
const inputPassWord = ref('')
|
||||||
|
|
||||||
|
if (_isAdmin.value) {
|
||||||
|
_userName.value = "管理员"
|
||||||
|
} else {
|
||||||
|
_userName.value = "未登录"
|
||||||
|
}
|
||||||
|
watch(_isAdmin, (newValue) => {
|
||||||
|
store.isAdmin = newValue
|
||||||
|
if (newValue) {
|
||||||
|
_userName.value = "管理员"
|
||||||
|
} else {
|
||||||
|
_userName.value = "未登录"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
watch(route, (newRoute) => {
|
watch(route, (newRoute) => {
|
||||||
activeIndex.value = newRoute.name?.toString() || "TokenManage"
|
activeIndex.value = newRoute.name?.toString() || "TokenManage"
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSelect = (key: string) => {
|
const handleSelect = (name: string) => {
|
||||||
router.push({name: key})
|
router.push({name: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{key: 'TokenManage', label: '管理 Token', icon: Edit},
|
|
||||||
{key: 'TokenDetail', label: 'Token 详情', icon: Document},
|
|
||||||
]
|
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
store.isAdmin = false
|
_isAdmin.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkPassword = () => {
|
||||||
|
if (inputPassWord.value === "haha") {
|
||||||
|
ElMessage({message: '登录成功', type: 'success'})
|
||||||
|
_isAdmin.value = true
|
||||||
|
} else {
|
||||||
|
ElMessage.error('密码错误')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,27 +51,42 @@ const logout = () => {
|
|||||||
<div class="admin-layout">
|
<div class="admin-layout">
|
||||||
<el-header class="admin-header">
|
<el-header class="admin-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<div class="logo">
|
<div class="logo">DYPID 管理后台</div>
|
||||||
DYPID 管理后台
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="header-nav">
|
<nav class="header-nav">
|
||||||
<div v-for="item in menuItems" class="nav-item"
|
<div class="nav-item"
|
||||||
:key="item.key"
|
:class="{active:activeIndex=='TokenManage'}"
|
||||||
:class="{ active: activeIndex === item.key }"
|
@click="handleSelect('TokenManage')"
|
||||||
@click="handleSelect(item.key)"
|
|
||||||
>
|
>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<component :is="item.icon"/>
|
<Edit/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>{{ item.label }}</span>
|
<span>管理 Token</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-item"
|
||||||
|
:class="{active:activeIndex=='TokenDetail'}"
|
||||||
|
@click="handleSelect('TokenDetail')"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Document/>
|
||||||
|
</el-icon>
|
||||||
|
<span>Token 详情</span>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<el-dropdown v-if="store.isAdmin">
|
<el-button v-if="_isAdmin" circle link
|
||||||
<el-button :icon="User">管理员</el-button>
|
@click="handleSelect('AdminSettings')"
|
||||||
|
>
|
||||||
|
<el-icon size="16">
|
||||||
|
<Setting/>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical"/>
|
||||||
|
<el-dropdown :disabled="!_isAdmin">
|
||||||
|
<el-button :icon="User" link>{{ _userName }}</el-button>
|
||||||
|
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
@@ -69,10 +105,40 @@ const logout = () => {
|
|||||||
</el-header>
|
</el-header>
|
||||||
|
|
||||||
<el-main>
|
<el-main>
|
||||||
<router-view v-slot="{ Component }">
|
<!-- 登录页面 -->
|
||||||
<transition name="fade" mode="out-in">
|
<div v-if="!_isAdmin" class="login-page">
|
||||||
<component :is="Component"/>
|
<div class="login-card">
|
||||||
</transition>
|
<div class="login-icon">
|
||||||
|
<el-icon size="48" color="#409eff">
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="login-title">管理员登录</div>
|
||||||
|
<div class="login-subtitle">请输入管理员密码访问管理后台</div>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-model="inputPassWord"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入管理员密码"
|
||||||
|
size="large"
|
||||||
|
show-password
|
||||||
|
@keyup.enter="checkPassword"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary" size="large" class="login-btn" @click="checkPassword">
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-view v-else v-slot="{ Component }">
|
||||||
|
<component :is="Component"/>
|
||||||
</router-view>
|
</router-view>
|
||||||
</el-main>
|
</el-main>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +207,50 @@ const logout = () => {
|
|||||||
.header-right {
|
.header-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
}
|
||||||
|
|
||||||
|
/* 登录页面 */
|
||||||
|
.login-page {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 40px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card :deep(.el-input) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
+34
-96
@@ -66,34 +66,43 @@ const statCards = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="home-page">
|
<div class="p-6 h-dvh">
|
||||||
<div class="content-card" style="max-width: 1600px">
|
<div class="content-card h-full">
|
||||||
<div class="page-header">
|
<div class="text-center mb-8">
|
||||||
<div class="page-title">Token 信息查询</div>
|
<div class="page-title">Token 信息查询</div>
|
||||||
<div class="header-subtitle">输入 Token 以查看去重和缓存信息</div>
|
<div class="header-subtitle">输入 Token 以查看去重和缓存信息</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-box">
|
<div class="flex justify-center mb-8">
|
||||||
<el-input placeholder="输入 Token" size="large" clearable class="token-input"
|
<el-input placeholder="请输入要查询数据的Token..." size="large" clearable
|
||||||
v-model="inputToken" @change="inputChange"/>
|
class="max-w-120"
|
||||||
|
v-model="inputToken" @change="inputChange"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
Token
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-button type="primary" size="large"
|
<el-button type="primary" size="large"
|
||||||
@click="refresh" :loading="loading" :icon="Search">
|
class="ml-3"
|
||||||
|
@click="refresh" :loading="loading" :icon="Search"
|
||||||
|
>
|
||||||
查询
|
查询
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<el-button type="success" size="large" class=""
|
|
||||||
@click="showAddDataDialog = true" :icon="Plus">
|
|
||||||
增加数据
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AddDataDialog v-model="showAddDataDialog" :token="inputToken"/>
|
<AddDataDialog v-model="showAddDataDialog" :token="inputToken"/>
|
||||||
|
|
||||||
<div v-if="result" class="result-section">
|
<div v-if="result" class="result-section">
|
||||||
<div class="result-header">
|
<el-button type="primary" size=""
|
||||||
|
class=""
|
||||||
|
@click="showAddDataDialog = true" :icon="Plus"
|
||||||
|
>
|
||||||
|
增加数据
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<div class="flex ">
|
||||||
<span class="section-title">Token 信息</span>
|
<span class="section-title">Token 信息</span>
|
||||||
<span class="auto-refresh">每5秒自动刷新</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-cards">
|
<div class="stat-cards">
|
||||||
@@ -103,55 +112,38 @@ const statCards = [
|
|||||||
<component :is="card.icon"/>
|
<component :is="card.icon"/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-content">
|
<div class="flex-1">
|
||||||
<div class="stat-label">{{ card.label }}</div>
|
<div class="stat-label">{{ card.label }}</div>
|
||||||
<div class="stat-value">{{ result[card.key] || '-' }}</div>
|
<div class="stat-value">{{ result[card.key] || '-' }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="lastUpdate" class="update-time">
|
<el-text v-if="lastUpdate" type="info"
|
||||||
最后更新: {{ lastUpdate }}
|
class="block w-full mt-5 text-center"
|
||||||
</div>
|
>
|
||||||
|
每5秒更新一次数据 最后更新: {{ lastUpdate }}
|
||||||
|
</el-text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="empty-state">
|
<el-space v-else direction="vertical"
|
||||||
|
class="flex p-16"
|
||||||
|
>
|
||||||
<el-icon size="64" color="#dcdfe6">
|
<el-icon size="64" color="#dcdfe6">
|
||||||
<Search/>
|
<Search/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<div class="empty-text">请输入 Token 进行查询</div>
|
<el-text type="info" size="large" class="">请输入 Token 进行查询</el-text>
|
||||||
</div>
|
</el-space>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.home-page {
|
|
||||||
padding: 24px 48px;
|
|
||||||
max-width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle {
|
.header-subtitle {
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-box {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-input {
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-section {
|
.result-section {
|
||||||
animation: fadeIn 0.3s ease;
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
@@ -174,11 +166,6 @@ const statCards = [
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-refresh {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-cards {
|
.stat-cards {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
@@ -209,11 +196,6 @@ const statCards = [
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
@@ -228,48 +210,4 @@ const statCards = [
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-time {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 64px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.stat-cards {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.home-page {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-input {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-cards {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
TODO
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -143,19 +143,8 @@ const statCards = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="token-detail">
|
<div class="token-detail">
|
||||||
<!-- 检查权限 -->
|
|
||||||
<div v-if="!store.isAdmin" class="no-permission">
|
|
||||||
<div class="no-permission-card">
|
|
||||||
<el-icon size="64" color="#f56c6c">
|
|
||||||
<InfoFilled/>
|
|
||||||
</el-icon>
|
|
||||||
<div class="no-permission-title">无权限访问</div>
|
|
||||||
<div class="no-permission-text">您没有权限访问此页面</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 管理页面 -->
|
<!-- 管理页面 -->
|
||||||
<div v-else class="detail-page">
|
<div class="detail-page">
|
||||||
<div class="content-card">
|
<div class="content-card">
|
||||||
<div class="page-title">Token 详情</div>
|
<div class="page-title">Token 详情</div>
|
||||||
|
|
||||||
@@ -321,30 +310,6 @@ const statCards = [
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 无权限 */
|
|
||||||
.no-permission {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 60vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-permission-card {
|
|
||||||
text-align: center;
|
|
||||||
padding: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-permission-title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 24px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-permission-text {
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Token 选择 */
|
/* Token 选择 */
|
||||||
.token-select {
|
.token-select {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from "@/api"
|
import api from "@/api"
|
||||||
import {useCounterStore} from "@/stores/counter.ts"
|
import {useCounterStore} from "@/stores/counter.ts"
|
||||||
import {Delete, Edit, Key, Lock, Memo, Plus, View} from '@element-plus/icons-vue'
|
import {Delete, Edit, Key, Memo, Plus, View} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const store = useCounterStore()
|
const store = useCounterStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -12,8 +12,7 @@ const input = ref('')
|
|||||||
const value = ref('')
|
const value = ref('')
|
||||||
const dataFormat = ref('')
|
const dataFormat = ref('')
|
||||||
const inputNotes = ref('')
|
const inputNotes = ref('')
|
||||||
const inputPassWord = ref('')
|
const activeNames = ref(['1'])
|
||||||
const passwordVisible = ref(true)
|
|
||||||
|
|
||||||
const rowOut = ref<any>(null)
|
const rowOut = ref<any>(null)
|
||||||
const addDataDialogVisible = ref(false)
|
const addDataDialogVisible = ref(false)
|
||||||
@@ -46,24 +45,11 @@ const fetchTokens = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!store.isAdmin) {
|
if (store.isAdmin) {
|
||||||
passwordVisible.value = true
|
|
||||||
} else {
|
|
||||||
fetchTokens()
|
fetchTokens()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkPassword = () => {
|
|
||||||
if (inputPassWord.value === "haha") {
|
|
||||||
ElMessage({message: '登录成功', type: 'success'})
|
|
||||||
store.isAdmin = true
|
|
||||||
passwordVisible.value = false
|
|
||||||
fetchTokens()
|
|
||||||
} else {
|
|
||||||
ElMessage.error('密码错误')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addToken = async () => {
|
const addToken = async () => {
|
||||||
if (!input.value || !value.value || !dataFormat.value) {
|
if (!input.value || !value.value || !dataFormat.value) {
|
||||||
ElMessage.warning('请填写完整信息')
|
ElMessage.warning('请填写完整信息')
|
||||||
@@ -176,79 +162,49 @@ const deleteToken = async (row: any) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="token-manage">
|
<div class="token-manage">
|
||||||
<!-- 登录页面 -->
|
|
||||||
<div v-if="!store.isAdmin" class="login-page">
|
|
||||||
<div class="login-card">
|
|
||||||
<div class="login-icon">
|
|
||||||
<el-icon size="48" color="#409eff">
|
|
||||||
<Lock/>
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="login-title">管理员登录</div>
|
|
||||||
<div class="login-subtitle">请输入管理员密码访问管理后台</div>
|
|
||||||
|
|
||||||
<el-input
|
|
||||||
v-model="inputPassWord"
|
|
||||||
type="password"
|
|
||||||
placeholder="请输入管理员密码"
|
|
||||||
size="large"
|
|
||||||
show-password
|
|
||||||
@keyup.enter="checkPassword"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<el-icon>
|
|
||||||
<Lock/>
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
|
|
||||||
<el-button type="primary" size="large" class="login-btn" @click="checkPassword">
|
|
||||||
登录
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 管理页面 -->
|
<!-- 管理页面 -->
|
||||||
<div v-else class="manage-page">
|
<div class="manage-page">
|
||||||
<div class="content-card">
|
<div class="content-card">
|
||||||
<div class="page-title">Token 管理</div>
|
<div class="page-title">Token 管理</div>
|
||||||
|
|
||||||
<!-- 添加表单 -->
|
<!-- 添加表单 -->
|
||||||
<div class="add-form">
|
<el-collapse class="add-form" v-model="activeNames">
|
||||||
<div class="form-title">添加新 Token</div>
|
<el-collapse-item title="添加新 Token" name="1">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<el-input v-model="input" placeholder="Token 名称" clearable>
|
<el-input v-model="input" placeholder="Token 名称" clearable>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Key/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-select v-model="value" placeholder="去重对象" clearable>
|
||||||
|
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-select v-model="dataFormat" placeholder="数据格式" clearable>
|
||||||
|
<el-option v-for="item in dataFormatOptions" :key="item.value" :value="item.value"
|
||||||
|
:label="item.label"/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-input v-model="inputNotes" placeholder="备注(可选)" clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Memo/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary" @click="addToken">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Key/>
|
<Plus/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</template>
|
添加
|
||||||
</el-input>
|
</el-button>
|
||||||
|
</div>
|
||||||
<el-select v-model="value" placeholder="去重对象" clearable>
|
</el-collapse-item>
|
||||||
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"/>
|
</el-collapse>
|
||||||
</el-select>
|
|
||||||
|
|
||||||
<el-select v-model="dataFormat" placeholder="数据格式" clearable>
|
|
||||||
<el-option v-for="item in dataFormatOptions" :key="item.value" :value="item.value" :label="item.label"/>
|
|
||||||
</el-select>
|
|
||||||
|
|
||||||
<el-input v-model="inputNotes" placeholder="备注(可选)" clearable>
|
|
||||||
<template #prefix>
|
|
||||||
<el-icon>
|
|
||||||
<Memo/>
|
|
||||||
</el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
|
|
||||||
<el-button type="primary" @click="addToken">
|
|
||||||
<el-icon>
|
|
||||||
<Plus/>
|
|
||||||
</el-icon>
|
|
||||||
添加
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Token 列表 -->
|
<!-- Token 列表 -->
|
||||||
<div class="table-section">
|
<div class="table-section">
|
||||||
@@ -387,50 +343,6 @@ const deleteToken = async (row: any) => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 登录页面 */
|
|
||||||
.login-page {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 60vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
text-align: center;
|
|
||||||
padding: 48px 40px;
|
|
||||||
background: var(--bg-card);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-icon {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-subtitle {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-card :deep(.el-input) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 管理页面 */
|
/* 管理页面 */
|
||||||
.add-form {
|
.add-form {
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
|||||||
Reference in New Issue
Block a user