feat: 首次提交,添加Web界面和Redis支持的数据管理系统

This commit is contained in:
2025-09-01 00:45:37 +08:00
parent 7abb872b2c
commit b9fbe07b70
30 changed files with 4970 additions and 0 deletions

1
web/.env.production Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE_URL=

30
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

33
web/README.md Normal file
View File

@@ -0,0 +1,33 @@
# web
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

1
web/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

13
web/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

3758
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
web/package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"axios": "^1.11.0",
"element-plus": "^2.11.1",
"pinia": "^3.0.3",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.16.5",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^8.0.4",
"typescript": "~5.8.0",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^3.0.4"
}
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

59
web/src/App.vue Normal file
View File

@@ -0,0 +1,59 @@
<script setup lang="ts">
import {ref, watch} from 'vue'
import {useRoute, useRouter} from "vue-router";
const router = useRouter()
const route = useRoute()
const activeIndex = ref(route.name?.toString() || "TokenList")
watch(route, (newRoute) => {
activeIndex.value = newRoute.name?.toString() || "TokenList"
})
const handleSelect = (key: string) => {
router.push({
name: key
})
}
</script>
<template>
<el-container>
<el-header>
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
@select="handleSelect"
>
<el-menu-item index="TokenList">Token列表</el-menu-item>
<el-menu-item index="TokenDetail">Token信息</el-menu-item>
</el-menu>
</el-header>
<el-container>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<style>
html, body, #app {
height: 100%;
margin: 0;
padding: 0;
}
.el-container {
height: 100%;
}
.el-menu-demo {
line-height: 60px;
}
</style>

7
web/src/axios.ts Normal file
View File

@@ -0,0 +1,7 @@
import axios from 'axios';
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
});
export default instance;

15
web/src/main.ts Normal file
View File

@@ -0,0 +1,15 @@
import {createApp} from 'vue'
import {createPinia} from 'pinia'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')

16
web/src/router/index.ts Normal file
View File

@@ -0,0 +1,16 @@
import {createRouter, createWebHistory} from 'vue-router'
import TokenListView from '@/views/TokenListView.vue'
import TokenDetailView from '@/views/TokenDetailView.vue'
const routes = [
{path: '/', name: "TokenList", component: TokenListView},
{path: '/token', name: "TokenDetail", component: TokenDetailView},
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})
export default router

View File

@@ -0,0 +1,8 @@
import {ref, computed} from 'vue'
import {defineStore} from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const token = ref("")
return {token}
})

View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import {useCounterStore} from "@/stores/counter.ts";
import {ref} from 'vue'
import axios from "@/axios.ts";
const result = ref()
const getInfo = () => {
axios.get('/api/token/info', {
params: {
token: useCounterStore().token
}
}).then(res => {
if (res.status == 200) {
result.value = res.data.result
}
})
}
const deleteDedup = () => {
axios.delete('/api/token/info', {
params: {
token: useCounterStore().token,
dedup_bf: true
}
})
}
const deleteRedis = () => {
axios.delete('/api/token/info', {
params: {
token: useCounterStore().token,
cache_list: true
}
})
}
getInfo()
setInterval(getInfo, 5000)
</script>
<template>
<p>当前Token{{ useCounterStore().token }}</p>
<el-button type="danger" @click="deleteDedup">删除去重记录值</el-button>
<el-button type="danger" @click="deleteRedis">删除Redis数据</el-button>
<el-divider/>
<el-button type="primary" @click="getInfo">手动刷新</el-button>
<el-descriptions
:title="'Token信息 - ' + useCounterStore().token+'(每5秒刷新)'"
direction="vertical"
:column="4"
border
>
<el-descriptions-item label="去重对象">{{ result?.dedup_object }}</el-descriptions-item>
<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>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,183 @@
<script setup lang="ts">
import {ref} from "vue";
import axios from "@/axios.ts";
import {ElMessage, ElMessageBox} from 'element-plus'
const tableData = ref([])
axios.get("/api/token").then(res => {
tableData.value = res.data.result
})
const input = ref('')
const value = ref('')
const options = [
{
value: 'token',
label: 'token',
},
{
value: 'uid',
label: 'uid',
},
{
value: 'pid',
label: 'pid',
},
{
value: 'secid',
label: 'secid',
},
{
value: 'dyid',
label: 'dyid',
},
{
value: 'key',
label: 'key',
},
{
value: 'comment_id',
label: 'comment_id',
}
]
const addToken = () => {
axios.post('/api/token', {}, {
params: {
token: input.value,
dedup_object: value.value
}
}).then(response => {
if (response.data.result == "ok") {
ElMessage({
message: '添加成功',
type: 'success',
})
axios.get('/api/token').then(res => {
tableData.value = res.data.result
})
}
}).catch(error => {
ElMessage.error(error.response?.data?.error)
})
}
import {useCounterStore} from "@/stores/counter.ts";
import {useRouter} from "vue-router";
const router = useRouter()
const viewDetails = (row: any) => {
useCounterStore().token = row.token
router.push({
name: "TokenDetail"
})
}
var rowOut: any
const dedupObjectVisible = ref(false)
const dialogDedupObjectVisible = (row: any) => {
rowOut = row
dedupObjectVisible.value = true
}
const updateDedupObject = () => {
dedupObjectVisible.value = false
axios.put('/api/token', {}, {
params: {
token: rowOut.token,
dedup_object: value.value
}
}).then(res => {
if (res.data.result == "ok") {
ElMessage({
message: '更改成功',
type: 'success',
})
axios.get('/api/token').then(res => {
tableData.value = res.data.result
})
}
}).catch(error => {
ElMessage.error(error.response?.data?.error)
})
}
const deleteToken = (row: any) => {
axios.delete('/api/token', {
params: {
token: row.token
}
}).then(res => {
if (res.data.result == "ok") {
ElMessage({
message: '删除成功',
type: 'success',
})
axios.get('/api/token').then(res => {
tableData.value = res.data.result
})
}
}).catch(error => {
ElMessage.error(error.response?.data?.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>
<!--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>
</template>
<style scoped>
</style>

12
web/tsconfig.app.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
web/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
web/tsconfig.node.json Normal file
View File

@@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

18
web/vite.config.ts Normal file
View File

@@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})