feat: 添加管理员权限控制和状态持久化

This commit is contained in:
2025-09-02 12:31:48 +08:00
parent 652b913930
commit c938e5b770
8 changed files with 180 additions and 59 deletions

46
web/package-lock.json generated
View File

@@ -11,6 +11,7 @@
"axios": "^1.11.0",
"element-plus": "^2.11.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},
@@ -2083,6 +2084,12 @@
}
}
},
"node_modules/deep-pick-omit": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/deep-pick-omit/-/deep-pick-omit-1.2.1.tgz",
"integrity": "sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==",
"license": "MIT"
},
"node_modules/default-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
@@ -2126,6 +2133,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -2135,6 +2148,12 @@
"node": ">=0.4.0"
}
},
"node_modules/destr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
"license": "MIT"
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -3074,6 +3093,33 @@
}
}
},
"node_modules/pinia-plugin-persistedstate": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.5.0.tgz",
"integrity": "sha512-QTkP1xJVyCdr2I2p3AKUZM84/e+IS+HktRxKGAIuDzkyaKKV48mQcYkJFVVDuvTxlI5j6X3oZObpqoVB8JnWpw==",
"license": "MIT",
"dependencies": {
"deep-pick-omit": "^1.2.1",
"defu": "^6.1.4",
"destr": "^2.0.5"
},
"peerDependencies": {
"@nuxt/kit": ">=3.0.0",
"@pinia/nuxt": ">=0.10.0",
"pinia": ">=3.0.0"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
},
"@pinia/nuxt": {
"optional": true
},
"pinia": {
"optional": true
}
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",

View File

@@ -17,6 +17,7 @@
"axios": "^1.11.0",
"element-plus": "^2.11.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import {ref, watch} from 'vue'
import {useRoute, useRouter} from "vue-router";
import {useCounterStore} from "@/stores/counter.ts";
const router = useRouter()
const route = useRoute()
@@ -28,9 +29,13 @@ const handleSelect = (key: string) => {
mode="horizontal"
@select="handleSelect"
>
<el-menu-item index="TokenList">Token列表</el-menu-item>
<el-menu-item index="TokenDetail">Token信息</el-menu-item>
<el-menu-item index="TokenDetail">Token详细信息</el-menu-item>
<el-menu-item index="TokenManage">管理Token</el-menu-item>
<el-menu-item v-if="useCounterStore().isAdmin">
<el-button type="danger" plain @click="useCounterStore().isAdmin=false">退出管理员</el-button>
</el-menu-item>
</el-menu>
</el-header>
<el-container>

View File

@@ -1,5 +1,6 @@
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import persistedState from 'pinia-plugin-persistedstate';
import App from './App.vue'
import router from './router'
@@ -8,7 +9,7 @@ import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(createPinia())
app.use(createPinia().use(persistedState))
app.use(router)
app.use(ElementPlus)

View File

@@ -1,10 +1,10 @@
import {createRouter, createWebHistory} from 'vue-router'
import TokenListView from '@/views/TokenListView.vue'
import TokenManageView from '@/views/TokenManageView.vue'
import TokenDetailView from '@/views/TokenDetailView.vue'
const routes = [
{path: '/', name: "TokenList", component: TokenListView},
{path: '/manage', name: "TokenManage", component: TokenManageView},
{path: '/', name: "TokenDetail", component: TokenDetailView},
];

View File

@@ -4,5 +4,10 @@ import {defineStore} from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const token = ref("")
return {token}
const isAdmin = ref(false)
return {token, isAdmin}
}, {
persist: true
})

View File

@@ -1,14 +1,15 @@
<script setup lang="ts">
import {useCounterStore} from "@/stores/counter.ts";
import {ref} from 'vue'
import {ref, watch} from 'vue'
import axios from "@/axios.ts";
const result = ref()
const value = ref('')
const getInfo = () => {
axios.get('/api/token/info', {
params: {
token: useCounterStore().token
token: value.value
}
}).then(res => {
if (res.status == 200) {
@@ -20,35 +21,64 @@ const getInfo = () => {
const deleteDedup = () => {
axios.delete('/api/token/info', {
params: {
token: useCounterStore().token,
token: value.value,
dedup_bf: true
}
}).then(res => {
getInfo()
})
getInfo()
}
const deleteRedis = () => {
axios.delete('/api/token/info', {
params: {
token: useCounterStore().token,
token: value.value,
cache_list: true
}
}).then(res => {
getInfo()
})
getInfo()
}
getInfo()
setInterval(getInfo, 5000)
watch(value, (newValue) => {
console.log(newValue)
getInfo()
})
interface optionsType {
value: string
}
const options = ref([] as optionsType[])
value.value = useCounterStore().token
axios.get('/api/token').then(res => {
if (res.status == 200) {
res.data.result.forEach((item: any) => {
options.value.push({"value": item.token})
})
}
})
</script>
<template>
<p>当前Token{{ useCounterStore().token }}</p>
<el-button type="danger" @click="deleteDedup">删除去重记录值</el-button>
<el-button type="danger" @click="deleteRedis">删除Redis数据</el-button>
<b>当前Token</b>
<el-select v-model="value" placeholder="选择Token" style="width: 240px">
<el-option
v-for="item in options"
:key="item.value"
:value="item.value"
/>
</el-select>
<el-divider/>
<el-button type="primary" @click="getInfo">手动刷新</el-button>
<b>Token信息每5秒刷新</b>
<el-button type="primary" plain @click="getInfo">手动刷新</el-button>
<el-descriptions
:title="'Token信息 - ' + useCounterStore().token+'(每5秒刷新)'"
direction="vertical"
:column="4"
border
@@ -57,6 +87,13 @@ setInterval(getInfo, 5000)
<el-descriptions-item label="去重记录值">{{ result?.dedup_items_number }}</el-descriptions-item>
<el-descriptions-item label="Redis中数据条数">{{ result?.cache_list_number }}</el-descriptions-item>
</el-descriptions>
<div v-if="useCounterStore().isAdmin">
<p><b>管理</b></p>
<el-button type="danger" @click="deleteDedup">删除去重记录值</el-button>
<el-button type="danger" @click="deleteRedis">删除Redis数据</el-button>
</div>
</template>
<style scoped>

View File

@@ -114,46 +114,36 @@ const deleteToken = (row: any) => {
ElMessage.error(error.response?.data?.error)
})
}
const inputPassWord = ref('')
const checkPassword = () => {
if (inputPassWord.value == "admin") {
ElMessage({
message: '密码正确',
type: 'success',
})
useCounterStore().isAdmin = true
} else {
ElMessage.error('密码错误')
}
}
</script>
<template>
<!--添加Token-->
<el-input v-model="input" style="width: 200px" placeholder="请输入Token名称"/>
<el-select v-model="value" placeholder="选择去重对象" style="width: 200px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button type="primary" @click="addToken">添加Token</el-button>
<!--非管理员-->
<div v-if="!useCounterStore().isAdmin" style="margin: auto auto; max-width: 400px">
<el-alert title="您没有权限访问此页面,输入管理员密码" type="error" :closable="false" show-icon/>
<p></p>
<el-input v-model="inputPassWord" style="width: 400px" placeholder="请输入管理员密码"/>
<p></p>
<el-button type="primary" @click="checkPassword">确认</el-button>
</div>
<!--Token列表-->
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="token" label="Token" width="180"/>
<el-table-column prop="dedup_object" label="去重对象" width="180"/>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="viewDetails(scope.row)">查看详细</el-button>
<el-button @click="dialogDedupObjectVisible(scope.row)" type="primary">更改去重对象</el-button>
<el-popconfirm
width="180"
title="确认删除此Token吗"
confirm-button-text="确认"
cancel-button-text="取消"
@confirm="deleteToken(scope.row)"
>
<template #reference>
<el-button type="danger">删除此Token</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="dedupObjectVisible" title="更改去重对象" width="400">
<!--管理员-->
<div v-if="useCounterStore().isAdmin">
<!--添加Token-->
<el-input v-model="input" style="width: 200px" placeholder="请输入Token名称"/>
<el-select v-model="value" placeholder="选择去重对象" style="width: 200px">
<el-option
v-for="item in options"
@@ -162,12 +152,48 @@ const deleteToken = (row: any) => {
:value="item.value"
/>
</el-select>
<template #footer>
<el-button type="primary" @click="updateDedupObject">
确定
</el-button>
</template>
</el-dialog>
<el-button type="primary" @click="addToken">添加Token</el-button>
<!--Token列表-->
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="token" label="Token" width="180"/>
<el-table-column prop="dedup_object" label="去重对象" width="180"/>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="viewDetails(scope.row)">查看详细</el-button>
<el-button @click="dialogDedupObjectVisible(scope.row)" type="primary">更改去重对象</el-button>
<el-popconfirm
width="180"
title="确认删除此Token吗"
confirm-button-text="确认"
cancel-button-text="取消"
@confirm="deleteToken(scope.row)"
>
<template #reference>
<el-button type="danger">删除此Token</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="dedupObjectVisible" title="更改去重对象" width="400">
<el-select v-model="value" placeholder="选择去重对象" style="width: 200px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<template #footer>
<el-button type="primary" @click="updateDedupObject">
确定
</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>