Compare commits

..

9 Commits

Author SHA1 Message Date
ygxbnet ac082785f4 refactor(ui): 重构应用样式和组件结构
部署开发环境 / deploy-dev (push) Successful in 2m8s
- 移除 App.vue 中的内联样式,将 CSS 变量定义移到 main.css
- 在 HomeView.vue 中使用 Element Plus Card 组件替代自定义卡片样式
- 更新 HomeView.vue 中的统计卡片布局,使用 Space 和 Card 组件
- 添加 fade-in 动画到全局主题配置
- 在 components.d.ts 中注册 ElCard 和 ElContainer 组件
- 移除 SettingsView.vue 中的空样式标签
- 使用 Tailwind CSS 类替代原有的自定义样式类
2026-06-20 18:41:12 +08:00
ygxbnet fc640407d9 feat(admin): 实现管理员登录认证和页面权限控制
- 添加管理员密码验证登录功能
- 实现登录状态管理和用户界面显示
- 集成权限检查确保页面访问安全
- 添加登录/登出流程处理
- 重构AdminView组件结构和样式
- 集成Element Plus图标和UI组件
- 添加设置页面路由配置
- 优化Token管理页面折叠表单设计
- 移除旧的响应式布局相关代码
- 更新应用标题动态渲染逻辑
2026-06-19 17:36:50 +08:00
ygxbnet 4100a51eb8 build(vite): 集成自动导入插件优化开发体验
部署开发环境 / deploy-dev (push) Successful in 2m18s
- 移除手动导入的 Vue 和 Element Plus 相关模块
- 集成 unplugin-auto-import 插件实现自动导入
- 集成 unplugin-vue-components 插件实现组件自动注册
- 添加自动生成的类型声明文件 auto-imports.d.ts 和 components.d.ts
- 配置 Element Plus 解析器支持按需导入
- 更新 tsconfig.app.json 包含自动生成的类型文件
2026-06-15 21:50:22 +08:00
ygxbnet d5a159be05 refactor(style): 调整Tailwind CSS样式优先级
- 将main.css导入从文件开头移至Element Plus样式之后
- 在Tailwind导入后添加important关键字以提高样式优先级
- 确保全局样式在组件库样式之后正确应用
2026-06-15 21:44:32 +08:00
ygxbnet 4e0b283fa9 refactor(admin): 重构部分页面样式代码,修剪多余代码
部署开发环境 / deploy-dev (push) Successful in 1m38s
- 将AdminView中的header和main标签替换为el-header和el-main组件
- 移除自定义的导航菜单样式,使用Element Plus的导航组件
- 将管理员下拉菜单替换为el-dropdown组件
- 更新所有颜色变量为Element Plus的CSS变量
- 移除自定义按钮和表单样式,统一使用Element Plus组件
- 优化响应式布局和间距处理
2026-06-14 01:23:33 +08:00
ygxbnet ae0dddd255 refactor(router): 将管理页面移至admin文件夹
部署开发环境 / deploy-dev (push) Successful in 2m55s
2026-06-13 23:41:32 +08:00
ygxbnet 9e8db7754f feat(build): 集成 Tailwind CSS 样式框架
- 安装并配置 Tailwind CSS 和相关依赖
- 在 main.ts 中引入全局样式文件 src/assets/main.css
- 添加 @tailwindcss/vite 插件到项目依赖
- 更新 pnpm-lock.yaml 中的依赖关系和版本信息
- 修复开发环境标题设置中的缩进问题
2026-06-13 23:38:23 +08:00
ygxbnet 201ee8c0f9 refactor(api): 重构API调用方式并优化数据管理
部署开发环境 / deploy-dev (push) Successful in 2m1s
- 将原有的axios直接调用替换为统一的api模块封装
- 新增api.ts文件集中管理所有API接口方法
- 删除旧的axios.ts配置文件
- 统一API响应数据结构处理,将result改为data字段
- 优化HomeView中的Token相关状态管理和数据获取逻辑
- 重构TokenDetailView中信息获取和删除操作的API调用
- 更新TokenManageView中Token列表管理的CRUD操作实现
- 简化组件间的数据传递和状态同步机制
2026-06-03 22:09:35 +08:00
ygxbnet 58a29f2b9d refactor(后端): 重构后端API响应数据格式,统一为Code Message Data 2026-06-03 18:03:37 +08:00
22 changed files with 1105 additions and 971 deletions
+119 -35
View File
@@ -3,6 +3,7 @@ package controller
import ( import (
"dypid/internal/db" "dypid/internal/db"
"dypid/internal/global" "dypid/internal/global"
"dypid/internal/model"
"net/http" "net/http"
"strconv" "strconv"
@@ -11,7 +12,11 @@ import (
) )
func ListTokenHandler(c *gin.Context) { func ListTokenHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"result": db.ListToken()}) c.JSON(http.StatusOK, model.APIResponse{
Code: 200,
Message: "获取所有token成功",
Data: db.ListToken(),
})
} }
func CreateTokenHandler(c *gin.Context) { func CreateTokenHandler(c *gin.Context) {
@@ -23,24 +28,37 @@ func CreateTokenHandler(c *gin.Context) {
Notes string `form:"notes"` Notes string `form:"notes"`
}{} }{}
if err := c.ShouldBindQuery(&input); err != nil { if err := c.ShouldBindQuery(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数不能为空 " + err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "参数不能为空 " + err.Error(),
})
return return
} }
//检查Token是否存在 //检查Token是否存在
if db.CheckToken(input.Token) { if db.CheckToken(input.Token) {
c.JSON(http.StatusBadRequest, gin.H{"error": "创建Token失败,Token已经存在"}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "创建Token失败,Token已经存在,请勿重复创建",
})
return return
} }
//创建Token //创建Token
err := db.CreateToken(input.Token, input.DedupObject, input.DataFormat, input.Notes) err := db.CreateToken(input.Token, input.DedupObject, input.DataFormat, input.Notes)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: err.Error(),
})
return return
} }
//返回 //返回
c.JSON(http.StatusOK, gin.H{"result": "ok"}) c.JSON(http.StatusOK, model.APIResponse{
Code: 200,
Message: "创建Token成功",
})
} }
func UpdateTokenHandler(c *gin.Context) { func UpdateTokenHandler(c *gin.Context) {
@@ -51,22 +69,35 @@ func UpdateTokenHandler(c *gin.Context) {
Notes string `form:"notes"` Notes string `form:"notes"`
}{} }{}
if err := c.ShouldBindQuery(&input); err != nil { if err := c.ShouldBindQuery(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数不能为空 " + err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "参数不能为空 " + err.Error(),
})
return return
} }
//检查Token是否存在 //检查Token是否存在
if !db.CheckToken(input.Token) { if !db.CheckToken(input.Token) {
c.JSON(http.StatusBadRequest, gin.H{"error": "更改失败,Token不存在"}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "更改失败,Token不存在",
})
return return
} }
err := db.UpdateToken(input.Token, input.DedupObject, input.DataFormat, input.Notes) err := db.UpdateToken(input.Token, input.DedupObject, input.DataFormat, input.Notes)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: err.Error(),
})
return return
} }
c.JSON(http.StatusOK, gin.H{"result": "ok"}) c.JSON(http.StatusOK, model.APIResponse{
Code: 200,
Message: "更改Token成功",
})
} }
func DeleteTokenHandler(c *gin.Context) { func DeleteTokenHandler(c *gin.Context) {
@@ -74,22 +105,35 @@ func DeleteTokenHandler(c *gin.Context) {
Token string `form:"token" binding:"required"` Token string `form:"token" binding:"required"`
}{} }{}
if err := c.ShouldBindQuery(&input); err != nil { if err := c.ShouldBindQuery(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数不能为空 " + err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "参数不能为空 " + err.Error(),
})
return return
} }
//检查Token是否存在 //检查Token是否存在
if !db.CheckToken(input.Token) { if !db.CheckToken(input.Token) {
c.JSON(http.StatusBadRequest, gin.H{"error": "删除Token失败,Token不存在"}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "删除Token失败,Token不存在",
})
return return
} }
err := db.DeleteToken(input.Token) err := db.DeleteToken(input.Token)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: err.Error(),
})
return return
} }
c.JSON(http.StatusOK, gin.H{"result": "ok"}) c.JSON(http.StatusOK, model.APIResponse{
Code: 200,
Message: "删除Token成功",
})
} }
func GetTokenInfoHandler(c *gin.Context) { func GetTokenInfoHandler(c *gin.Context) {
@@ -97,23 +141,36 @@ func GetTokenInfoHandler(c *gin.Context) {
Token string `form:"token" binding:"required"` Token string `form:"token" binding:"required"`
}{} }{}
if err := c.ShouldBindQuery(&input); err != nil { if err := c.ShouldBindQuery(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Token不能为空"}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "参数不能为空 " + err.Error(),
})
return return
} }
//检查Token是否存在 //检查Token是否存在
if !db.CheckToken(input.Token) { if !db.CheckToken(input.Token) {
c.JSON(http.StatusBadRequest, gin.H{"error": "获取信息失败,Token不存在"}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "获取信息失败,Token不存在",
})
return return
} }
dedupObject, err := db.GetDedupObject(input.Token) dedupObject, err := db.GetDedupObject(input.Token)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取去重对象失败 " + err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: "获取去重对象失败 " + err.Error(),
})
return return
} }
dataFormat, err := db.GetDataFormat(input.Token) dataFormat, err := db.GetDataFormat(input.Token)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取数据格式失败 " + err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: "获取数据格式失败 " + err.Error(),
})
return return
} }
@@ -130,7 +187,11 @@ func GetTokenInfoHandler(c *gin.Context) {
output.DedupItemsNumber = global.RDB.CFInfo(global.RCtx, "dedup:"+input.Token+":"+dedupObject).Val().NumItemsInserted output.DedupItemsNumber = global.RDB.CFInfo(global.RCtx, "dedup:"+input.Token+":"+dedupObject).Val().NumItemsInserted
output.CacheListNumber = global.RDB.LLen(global.RCtx, "list:"+input.Token).Val() output.CacheListNumber = global.RDB.LLen(global.RCtx, "list:"+input.Token).Val()
c.JSON(http.StatusOK, gin.H{"result": output}) c.JSON(http.StatusOK, model.APIResponse{
Code: 200,
Message: "获取Token信息成功",
Data: output,
})
} }
func DeleteTokenInfoHandler(c *gin.Context) { func DeleteTokenInfoHandler(c *gin.Context) {
@@ -142,24 +203,29 @@ func DeleteTokenInfoHandler(c *gin.Context) {
BothNumber string `form:"both_number"` BothNumber string `form:"both_number"`
}{} }{}
if err := c.ShouldBindQuery(&input); err != nil { if err := c.ShouldBindQuery(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Token不能为空"}) c.JSON(http.StatusBadRequest, model.APIResponse{
return Code: 400,
} Message: "参数不能为空 " + err.Error(),
//检查Token是否存在 })
if !db.CheckToken(input.Token) {
c.JSON(http.StatusBadRequest, gin.H{"error": "删除Token失败,Token不存在"})
return return
} }
//检查token是否存在 //检查Token是否存在
_, err := db.GetDedupObject(input.Token) if !db.CheckToken(input.Token) {
if err != nil { c.JSON(http.StatusBadRequest, model.APIResponse{
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token不存在" + err.Error()}) Code: 400,
Message: "删除Token信息失败,Token不存在",
})
return return
} }
//获取去重对象
dedupObject, err := db.GetDedupObject(input.Token) dedupObject, err := db.GetDedupObject(input.Token)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "获取去重对象失败 " + err.Error(),
})
return return
} }
@@ -173,7 +239,10 @@ func DeleteTokenInfoHandler(c *gin.Context) {
default: default:
num, err := strconv.Atoi(input.DedupBF) num, err := strconv.Atoi(input.DedupBF)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "dedup_bf数量设置错误 " + err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "dedup_bf数量设置错误 " + err.Error(),
})
return return
} }
@@ -196,7 +265,10 @@ func DeleteTokenInfoHandler(c *gin.Context) {
return nil return nil
}) })
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: "删除去重对象失败 " + err.Error(),
})
return return
} }
@@ -213,7 +285,10 @@ func DeleteTokenInfoHandler(c *gin.Context) {
default: default:
num, err := strconv.Atoi(input.CacheList) num, err := strconv.Atoi(input.CacheList)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "cache_list数量设置错误 " + err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 400,
Message: "cache_list数量设置错误 " + err.Error(),
})
return return
} }
@@ -226,7 +301,10 @@ func DeleteTokenInfoHandler(c *gin.Context) {
default: default:
num, err := strconv.Atoi(input.BothNumber) num, err := strconv.Atoi(input.BothNumber)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "both_number数量设置错误 " + err.Error()}) c.JSON(http.StatusBadRequest, model.APIResponse{
Code: 500,
Message: "both_number数量设置错误 " + err.Error(),
})
return return
} }
@@ -248,12 +326,18 @@ func DeleteTokenInfoHandler(c *gin.Context) {
return nil return nil
}) })
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, model.APIResponse{
Code: 500,
Message: "删除Token信息失败 " + err.Error(),
})
return return
} }
} }
} }
//输出信息 //输出信息
c.JSON(http.StatusOK, gin.H{"result": "ok"}) c.JSON(http.StatusOK, model.APIResponse{
Code: 200,
Message: "删除Token信息成功",
})
} }
+7
View File
@@ -0,0 +1,7 @@
package model
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data"`
}
+4 -1
View File
@@ -22,7 +22,10 @@ func main() {
//初始化一个http服务对象 //初始化一个http服务对象
gin.SetMode(config.APPConfig.RunMode) gin.SetMode(config.APPConfig.RunMode)
r := gin.New() r := gin.New()
r.Use(gin.Recovery()) if config.APPConfig.RunMode == "debug" {
r.Use(gin.Logger()) //日志处理
}
r.Use(gin.Recovery()) //错误处理
r.Use(cors.Default()) //跨域设置 r.Use(cors.Default()) //跨域设置
//注册网页服务(Vue //注册网页服务(Vue
+80
View File
@@ -0,0 +1,80 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const ElMessage: typeof import('element-plus/es').ElMessage
const ElMessageBox: typeof import('element-plus/es').ElMessageBox
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp
const customRef: typeof import('vue').customRef
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const effectScope: typeof import('vue').effectScope
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const inject: typeof import('vue').inject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onDeactivated: typeof import('vue').onDeactivated
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const provide: typeof import('vue').provide
const reactive: typeof import('vue').reactive
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const resolveComponent: typeof import('vue').resolveComponent
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const toRaw: typeof import('vue').toRaw
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const unref: typeof import('vue').unref
const useAttrs: typeof import('vue').useAttrs
const useCssModule: typeof import('vue').useCssModule
const useCssVars: typeof import('vue').useCssVars
const useId: typeof import('vue').useId
const useLink: typeof import('vue-router').useLink
const useModel: typeof import('vue').useModel
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSlots: typeof import('vue').useSlots
const useTemplateRef: typeof import('vue').useTemplateRef
const watch: typeof import('vue').watch
const watchEffect: typeof import('vue').watchEffect
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
+44
View File
@@ -0,0 +1,44 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AddDataDialog: typeof import('./src/components/AddDataDialog.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElOption: typeof import('element-plus/es')['ElOption']
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
+4
View File
@@ -12,10 +12,12 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@tailwindcss/vite": "^4.3.0",
"axios": "^1.16.1", "axios": "^1.16.1",
"element-plus": "^2.14.1", "element-plus": "^2.14.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1", "pinia-plugin-persistedstate": "^4.7.1",
"tailwindcss": "^4.3.0",
"vue": "^3.5.35", "vue": "^3.5.35",
"vue-router": "^5.1.0" "vue-router": "^5.1.0"
}, },
@@ -26,6 +28,8 @@
"@vue/tsconfig": "^0.9.1", "@vue/tsconfig": "^0.9.1",
"npm-run-all2": "^8.0.4", "npm-run-all2": "^8.0.4",
"typescript": "~6.0.3", "typescript": "~6.0.3",
"unplugin-auto-import": "^21.0.0",
"unplugin-vue-components": "^32.1.0",
"vite": "^8.0.16", "vite": "^8.0.16",
"vite-plugin-vue-devtools": "^8.1.2", "vite-plugin-vue-devtools": "^8.1.2",
"vue-tsc": "^3.3.3" "vue-tsc": "^3.3.3"
+337 -23
View File
@@ -11,6 +11,9 @@ importers:
'@element-plus/icons-vue': '@element-plus/icons-vue':
specifier: ^2.3.2 specifier: ^2.3.2
version: 2.3.2(vue@3.5.35(typescript@6.0.3)) version: 2.3.2(vue@3.5.35(typescript@6.0.3))
'@tailwindcss/vite':
specifier: ^4.3.0
version: 4.3.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))
axios: axios:
specifier: ^1.16.1 specifier: ^1.16.1
version: 1.16.1 version: 1.16.1
@@ -23,12 +26,15 @@ importers:
pinia-plugin-persistedstate: pinia-plugin-persistedstate:
specifier: ^4.7.1 specifier: ^4.7.1
version: 4.7.1(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))) version: 4.7.1(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))
tailwindcss:
specifier: ^4.3.0
version: 4.3.0
vue: vue:
specifier: ^3.5.35 specifier: ^3.5.35
version: 3.5.35(typescript@6.0.3) version: 3.5.35(typescript@6.0.3)
vue-router: vue-router:
specifier: ^5.1.0 specifier: ^5.1.0
version: 5.1.0(@vue/compiler-sfc@3.5.35)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)) version: 5.1.0(@vue/compiler-sfc@3.5.35)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
devDependencies: devDependencies:
'@tsconfig/node24': '@tsconfig/node24':
specifier: ^24.0.4 specifier: ^24.0.4
@@ -38,7 +44,7 @@ importers:
version: 24.12.4 version: 24.12.4
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^6.0.7 specifier: ^6.0.7
version: 6.0.7(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)) version: 6.0.7(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
'@vue/tsconfig': '@vue/tsconfig':
specifier: ^0.9.1 specifier: ^0.9.1
version: 0.9.1(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)) version: 0.9.1(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
@@ -48,12 +54,18 @@ importers:
typescript: typescript:
specifier: ~6.0.3 specifier: ~6.0.3
version: 6.0.3 version: 6.0.3
unplugin-auto-import:
specifier: ^21.0.0
version: 21.0.0(@vueuse/core@14.3.0(vue@3.5.35(typescript@6.0.3)))
unplugin-vue-components:
specifier: ^32.1.0
version: 32.1.0(vue@3.5.35(typescript@6.0.3))
vite: vite:
specifier: ^8.0.16 specifier: ^8.0.16
version: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) version: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vite-plugin-vue-devtools: vite-plugin-vue-devtools:
specifier: ^8.1.2 specifier: ^8.1.2
version: 8.1.2(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)) version: 8.1.2(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))
vue-tsc: vue-tsc:
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.3.3(typescript@6.0.3) version: 3.3.3(typescript@6.0.3)
@@ -377,12 +389,109 @@ packages:
'@sxzz/popperjs-es@2.11.8': '@sxzz/popperjs-es@2.11.8':
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
'@tailwindcss/node@4.3.0':
resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
'@tailwindcss/oxide-android-arm64@4.3.0':
resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.3.0':
resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.3.0':
resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.3.0':
resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0':
resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==}
engines: {node: '>= 20'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.3.0':
resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.3.0':
resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.3.0':
resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.3.0':
resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.3.0':
resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.3.0':
resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.3.0':
resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.3.0':
resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==}
engines: {node: '>= 20'}
'@tailwindcss/vite@4.3.0':
resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==}
peerDependencies:
vite: ^5.2.0 || ^6 || ^7 || ^8
'@tsconfig/node24@24.0.4': '@tsconfig/node24@24.0.4':
resolution: {integrity: sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==} resolution: {integrity: sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==}
'@tybys/wasm-util@0.10.2': '@tybys/wasm-util@0.10.2':
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
'@types/jsesc@2.5.1': '@types/jsesc@2.5.1':
resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==}
@@ -657,6 +766,10 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.3.7 vue: ^3.3.7
enhanced-resolve@5.24.0:
resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==}
engines: {node: '>=10.13.0'}
entities@7.0.1: entities@7.0.1:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@@ -684,9 +797,16 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
exsolve@1.0.8: exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@@ -736,6 +856,9 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
has-symbols@1.1.0: has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -784,9 +907,16 @@ packages:
resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
engines: {node: '>=18'} engines: {node: '>=18'}
jiti@2.7.0:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
jsesc@3.1.0: jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1094,10 +1224,20 @@ packages:
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
superjson@2.2.6: superjson@2.2.6:
resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
engines: {node: '>=16'} engines: {node: '>=16'}
tailwindcss@4.3.0:
resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==}
tapable@2.3.3:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'}
tinyglobby@0.2.17: tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -1120,10 +1260,40 @@ packages:
undici-types@7.16.0: undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
unimport@5.7.0:
resolution: {integrity: sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==}
engines: {node: '>=18.12.0'}
unplugin-auto-import@21.0.0:
resolution: {integrity: sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@nuxt/kit': ^4.0.0
'@vueuse/core': '*'
peerDependenciesMeta:
'@nuxt/kit':
optional: true
'@vueuse/core':
optional: true
unplugin-utils@0.3.1: unplugin-utils@0.3.1:
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
unplugin-vue-components@32.1.0:
resolution: {integrity: sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@nuxt/kit': ^3.2.2 || ^4.0.0
vue: ^3.0.0
peerDependenciesMeta:
'@nuxt/kit':
optional: true
unplugin@2.3.11:
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
engines: {node: '>=18.12.0'}
unplugin@3.0.0: unplugin@3.0.0:
resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
@@ -1602,6 +1772,74 @@ snapshots:
'@sxzz/popperjs-es@2.11.8': {} '@sxzz/popperjs-es@2.11.8': {}
'@tailwindcss/node@4.3.0':
dependencies:
'@jridgewell/remapping': 2.3.5
enhanced-resolve: 5.24.0
jiti: 2.7.0
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
tailwindcss: 4.3.0
'@tailwindcss/oxide-android-arm64@4.3.0':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.3.0':
optional: true
'@tailwindcss/oxide-darwin-x64@4.3.0':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.3.0':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.3.0':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.3.0':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.3.0':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.3.0':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.3.0':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.3.0':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.3.0':
optional: true
'@tailwindcss/oxide@4.3.0':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.3.0
'@tailwindcss/oxide-darwin-arm64': 4.3.0
'@tailwindcss/oxide-darwin-x64': 4.3.0
'@tailwindcss/oxide-freebsd-x64': 4.3.0
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0
'@tailwindcss/oxide-linux-arm64-gnu': 4.3.0
'@tailwindcss/oxide-linux-arm64-musl': 4.3.0
'@tailwindcss/oxide-linux-x64-gnu': 4.3.0
'@tailwindcss/oxide-linux-x64-musl': 4.3.0
'@tailwindcss/oxide-wasm32-wasi': 4.3.0
'@tailwindcss/oxide-win32-arm64-msvc': 4.3.0
'@tailwindcss/oxide-win32-x64-msvc': 4.3.0
'@tailwindcss/vite@4.3.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))':
dependencies:
'@tailwindcss/node': 4.3.0
'@tailwindcss/oxide': 4.3.0
tailwindcss: 4.3.0
vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
'@tsconfig/node24@24.0.4': {} '@tsconfig/node24@24.0.4': {}
'@tybys/wasm-util@0.10.2': '@tybys/wasm-util@0.10.2':
@@ -1609,6 +1847,8 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@types/estree@1.0.9': {}
'@types/jsesc@2.5.1': {} '@types/jsesc@2.5.1': {}
'@types/lodash-es@4.17.12': '@types/lodash-es@4.17.12':
@@ -1623,10 +1863,10 @@ snapshots:
'@types/web-bluetooth@0.0.21': {} '@types/web-bluetooth@0.0.21': {}
'@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))': '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3))':
dependencies: dependencies:
'@rolldown/pluginutils': 1.0.1 '@rolldown/pluginutils': 1.0.1
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vue: 3.5.35(typescript@6.0.3) vue: 3.5.35(typescript@6.0.3)
'@volar/language-core@2.4.28': '@volar/language-core@2.4.28':
@@ -1937,6 +2177,11 @@ snapshots:
vue: 3.5.35(typescript@6.0.3) vue: 3.5.35(typescript@6.0.3)
vue-component-type-helpers: 3.3.3 vue-component-type-helpers: 3.3.3
enhanced-resolve@5.24.0:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
entities@7.0.1: {} entities@7.0.1: {}
error-stack-parser-es@1.0.5: {} error-stack-parser-es@1.0.5: {}
@@ -1958,8 +2203,14 @@ snapshots:
escalade@3.2.0: {} escalade@3.2.0: {}
escape-string-regexp@5.0.0: {}
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.9
exsolve@1.0.8: {} exsolve@1.0.8: {}
fdir@6.5.0(picomatch@4.0.4): fdir@6.5.0(picomatch@4.0.4):
@@ -2003,6 +2254,8 @@ snapshots:
gopd@1.2.0: {} gopd@1.2.0: {}
graceful-fs@4.2.11: {}
has-symbols@1.1.0: {} has-symbols@1.1.0: {}
has-tostringtag@1.0.2: has-tostringtag@1.0.2:
@@ -2040,8 +2293,12 @@ snapshots:
isexe@3.1.5: {} isexe@3.1.5: {}
jiti@2.7.0: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
jsesc@3.1.0: {} jsesc@3.1.0: {}
json-parse-even-better-errors@4.0.0: {} json-parse-even-better-errors@4.0.0: {}
@@ -2293,10 +2550,18 @@ snapshots:
speakingurl@14.0.1: {} speakingurl@14.0.1: {}
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
superjson@2.2.6: superjson@2.2.6:
dependencies: dependencies:
copy-anything: 4.0.5 copy-anything: 4.0.5
tailwindcss@4.3.0: {}
tapable@2.3.3: {}
tinyglobby@0.2.17: tinyglobby@0.2.17:
dependencies: dependencies:
fdir: 6.5.0(picomatch@4.0.4) fdir: 6.5.0(picomatch@4.0.4)
@@ -2313,11 +2578,59 @@ snapshots:
undici-types@7.16.0: {} undici-types@7.16.0: {}
unimport@5.7.0:
dependencies:
acorn: 8.16.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
pathe: 2.0.3
picomatch: 4.0.4
pkg-types: 2.3.1
scule: 1.3.0
strip-literal: 3.1.0
tinyglobby: 0.2.17
unplugin: 2.3.11
unplugin-utils: 0.3.1
unplugin-auto-import@21.0.0(@vueuse/core@14.3.0(vue@3.5.35(typescript@6.0.3))):
dependencies:
local-pkg: 1.2.1
magic-string: 0.30.21
picomatch: 4.0.4
unimport: 5.7.0
unplugin: 2.3.11
unplugin-utils: 0.3.1
optionalDependencies:
'@vueuse/core': 14.3.0(vue@3.5.35(typescript@6.0.3))
unplugin-utils@0.3.1: unplugin-utils@0.3.1:
dependencies: dependencies:
pathe: 2.0.3 pathe: 2.0.3
picomatch: 4.0.4 picomatch: 4.0.4
unplugin-vue-components@32.1.0(vue@3.5.35(typescript@6.0.3)):
dependencies:
chokidar: 5.0.0
local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
obug: 2.1.1
picomatch: 4.0.4
tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.35(typescript@6.0.3)
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5
acorn: 8.16.0
picomatch: 4.0.4
webpack-virtual-modules: 0.6.2
unplugin@3.0.0: unplugin@3.0.0:
dependencies: dependencies:
'@jridgewell/remapping': 2.3.5 '@jridgewell/remapping': 2.3.5
@@ -2330,17 +2643,17 @@ snapshots:
escalade: 3.2.0 escalade: 3.2.0
picocolors: 1.1.1 picocolors: 1.1.1
vite-dev-rpc@2.0.0(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)): vite-dev-rpc@2.0.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)):
dependencies: dependencies:
birpc: 4.0.0 birpc: 4.0.0
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vite-hot-client: 2.2.0(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)) vite-hot-client: 2.2.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))
vite-hot-client@2.2.0(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)): vite-hot-client@2.2.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)):
dependencies: dependencies:
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vite-plugin-inspect@11.4.1(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)): vite-plugin-inspect@11.4.1(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)):
dependencies: dependencies:
ansis: 4.3.1 ansis: 4.3.1
error-stack-parser-es: 1.0.5 error-stack-parser-es: 1.0.5
@@ -2350,24 +2663,24 @@ snapshots:
perfect-debounce: 2.1.0 perfect-debounce: 2.1.0
sirv: 3.0.2 sirv: 3.0.2
unplugin-utils: 0.3.1 unplugin-utils: 0.3.1
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vite-dev-rpc: 2.0.0(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)) vite-dev-rpc: 2.0.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))
vite-plugin-vue-devtools@8.1.2(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)): vite-plugin-vue-devtools@8.1.2(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)):
dependencies: dependencies:
'@vue/devtools-core': 8.1.2(vue@3.5.35(typescript@6.0.3)) '@vue/devtools-core': 8.1.2(vue@3.5.35(typescript@6.0.3))
'@vue/devtools-kit': 8.1.2 '@vue/devtools-kit': 8.1.2
'@vue/devtools-shared': 8.1.2 '@vue/devtools-shared': 8.1.2
sirv: 3.0.2 sirv: 3.0.2
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vite-plugin-inspect: 11.4.1(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)) vite-plugin-inspect: 11.4.1(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))
vite-plugin-vue-inspector: 6.0.0(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)) vite-plugin-vue-inspector: 6.0.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
- supports-color - supports-color
- vue - vue
vite-plugin-vue-inspector@6.0.0(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0)): vite-plugin-vue-inspector@6.0.0(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)):
dependencies: dependencies:
'@babel/core': 7.29.7 '@babel/core': 7.29.7
'@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7)
@@ -2378,11 +2691,11 @@ snapshots:
'@vue/compiler-dom': 3.5.35 '@vue/compiler-dom': 3.5.35
kolorist: 1.8.0 kolorist: 1.8.0
magic-string: 0.30.21 magic-string: 0.30.21
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0): vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0):
dependencies: dependencies:
lightningcss: 1.32.0 lightningcss: 1.32.0
picomatch: 4.0.4 picomatch: 4.0.4
@@ -2392,13 +2705,14 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/node': 24.12.4 '@types/node': 24.12.4
fsevents: 2.3.3 fsevents: 2.3.3
jiti: 2.7.0
yaml: 2.9.0 yaml: 2.9.0
vscode-uri@3.1.0: {} vscode-uri@3.1.0: {}
vue-component-type-helpers@3.3.3: {} vue-component-type-helpers@3.3.3: {}
vue-router@5.1.0(@vue/compiler-sfc@3.5.35)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))(vite@8.0.16(@types/node@24.12.4)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)): vue-router@5.1.0(@vue/compiler-sfc@3.5.35)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)))(vite@8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.35(typescript@6.0.3)):
dependencies: dependencies:
'@babel/generator': 8.0.0-rc.6 '@babel/generator': 8.0.0-rc.6
'@vue-macros/common': 3.1.2(vue@3.5.35(typescript@6.0.3)) '@vue-macros/common': 3.1.2(vue@3.5.35(typescript@6.0.3))
@@ -2421,7 +2735,7 @@ snapshots:
optionalDependencies: optionalDependencies:
'@vue/compiler-sfc': 3.5.35 '@vue/compiler-sfc': 3.5.35
pinia: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)) pinia: 3.0.4(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))
vite: 8.0.16(@types/node@24.12.4)(yaml@2.9.0) vite: 8.0.16(@types/node@24.12.4)(jiti@2.7.0)(yaml@2.9.0)
vue-tsc@3.3.3(typescript@6.0.3): vue-tsc@3.3.3(typescript@6.0.3):
dependencies: dependencies:
+1 -210
View File
@@ -1,218 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref} from 'vue'
const themeMode = ref('light') const themeMode = ref('light')
</script> </script>
<template> <template>
<div :class="themeMode" class="app-container"> <div :class="themeMode" class="min-h-screen bg-(--bg-page) max-w-full">
<router-view></router-view> <router-view></router-view>
</div> </div>
</template> </template>
<style>
:root {
--primary-color: #409eff;
--primary-light: #79bbff;
--success-color: #67c23a;
--warning-color: #e6a23c;
--danger-color: #f56c6c;
--info-color: #909399;
--text-primary: #303133;
--text-regular: #606266;
--text-secondary: #909399;
--border-color: #dcdfe6;
--bg-page: #f5f7fa;
--bg-card: #ffffff;
--bg-header: #ffffff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #app {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.app-container {
min-height: 100vh;
background: var(--bg-page);
max-width: 100%;
}
/* 卡片通用样式 */
.content-card {
background: var(--bg-card);
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
/* 页面标题样式 */
.page-title {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-color);
}
/* Section 标题 */
.section-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
margin: 20px 0 16px;
}
/* 按钮组样式 */
.btn-group {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
/* 表格工具栏 */
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
flex-wrap: wrap;
gap: 12px;
}
/* Flex 布局工具类 */
.flex-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
/* Gap 间距 */
.gap-sm {
gap: 8px;
}
.gap-md {
gap: 12px;
}
.gap-lg {
gap: 16px;
}
/* 间距工具类 */
.mt-sm {
margin-top: 8px;
}
.mt-md {
margin-top: 16px;
}
.mt-lg {
margin-top: 24px;
}
.mb-sm {
margin-bottom: 8px;
}
.mb-md {
margin-bottom: 16px;
}
.mb-lg {
margin-bottom: 24px;
}
.ml-sm {
margin-left: 8px;
}
.ml-md {
margin-left: 12px;
}
.ml-lg {
margin-left: 16px;
}
.mr-sm {
margin-right: 8px;
}
.mr-md {
margin-right: 12px;
}
.mr-lg {
margin-right: 16px;
}
/* 文本工具类 */
.text-primary {
color: var(--text-primary);
}
.text-success {
color: var(--success-color);
}
.text-warning {
color: var(--warning-color);
}
.text-danger {
color: var(--danger-color);
}
.text-info {
color: var(--info-color);
}
/* 加载动画 */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
}
/* 空状态 */
.empty-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 48px;
color: var(--text-secondary);
}
/* 响应式 */
@media (max-width: 768px) {
.content-card {
padding: 16px;
border-radius: 0;
}
.hide-on-mobile {
display: none;
}
}
</style>
+50
View File
@@ -0,0 +1,50 @@
import axios from "axios";
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
})
export default {
getTokenList: () =>
axiosInstance.get('/api/token'),
createToken: (params: {
token: string
dedup_object: string
data_format: string
notes: string
}) =>
axiosInstance.post('/api/token', {}, {params}),
updateToken: (params: {
token: string
dedup_object?: string
data_format?: string
notes?: string
}) =>
axiosInstance.put('/api/token', {}, {params}),
deleteToken: (params: {
token: string
}) =>
axiosInstance.delete('/api/token', {params}),
getTokenInfo: (params: {
token: string
}) =>
axiosInstance.get('/api/token/info', {params}),
deleteTokenInfo: (params: {
token: string
dedup_bf?: string
cache_list?: string
both_number?: string
}) =>
axiosInstance.delete('/api/token/info', {params}),
uploadData: (params: {
data: string
token: string
}) =>
axiosInstance.post('/api/data', {}, {params}),
}
+23
View File
@@ -0,0 +1,23 @@
@import "tailwindcss" important;
@theme {
--animate-fade-in: fade-in 0.3s ease;
--bg-page: #f5f7fa;
--bg-card: #ffffff;
--bg-header: #ffffff;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
:root {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
-7
View File
@@ -1,7 +0,0 @@
import axios from 'axios';
const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
});
export default instance;
+3 -10
View File
@@ -1,7 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, ref} from "vue" import api from "@/api"
import axios from "@/axios.ts"
import {ElMessage} from 'element-plus'
import {Upload} from '@element-plus/icons-vue' import {Upload} from '@element-plus/icons-vue'
const props = defineProps<{ const props = defineProps<{
@@ -42,12 +40,7 @@ const handleUpload = async () => {
for (const line of lines) { for (const line of lines) {
try { try {
await axios.post('/api/data', {}, { await api.uploadData({data: line, token: props.token || ''})
params: {
data: line,
token: props.token || ''
}
})
uploadedCount.value++ uploadedCount.value++
} catch (error: any) { } catch (error: any) {
ElMessage.error(error.response?.data?.error || '上传失败') ElMessage.error(error.response?.data?.error || '上传失败')
@@ -140,7 +133,7 @@ const handleUpload = async () => {
.progress-text { .progress-text {
font-size: 14px; font-size: 14px;
color: var(--text-secondary); color: var(--el-text-color-secondary);
white-space: nowrap; white-space: nowrap;
} }
+17 -8
View File
@@ -1,20 +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'
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
}
})
+10 -2
View File
@@ -1,20 +1,23 @@
import {createRouter, createWebHistory} from 'vue-router' import {createRouter, createWebHistory} from 'vue-router'
import TokenManageView from '@/views/TokenManageView.vue' import TokenManageView from '@/views/admin/TokenManageView.vue'
import TokenDetailView from '@/views/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,
}
], ],
}, },
-4
View File
@@ -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
+120 -188
View File
@@ -1,224 +1,156 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, watch} from 'vue'
import {useRoute, useRouter} from "vue-router"
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('密码错误')
}
}
const navItemClass = (name: string) => {
return activeIndex.value === name
? 'bg-[var(--el-color-primary)] text-white'
: 'text-[var(--el-text-color-regular)] hover:bg-[var(--bg-page)] hover:text-[var(--el-text-color-primary)]'
} }
</script> </script>
<template> <template>
<div class="admin-layout"> <div class="min-h-screen flex flex-col bg-(--bg-page)">
<header class="admin-header"> <el-header
<div class="header-left"> class="flex items-center justify-between bg-(--bg-header) border-b border-(--el-border-color) sticky top-0 z-50 max-md:flex-wrap max-md:h-auto max-md:px-4 max-md:py-3">
<div class="logo"> <div class="flex items-center">
<el-icon size="24"> <div class="flex items-center gap-2.5 text-(--el-color-primary) font-semibold text-lg">DYPID 管理后台</div>
<Edit/>
</el-icon>
<span class="logo-text">DYPID 管理后台</span>
</div>
</div> </div>
<nav class="header-nav"> <nav class="flex gap-2 max-md:order-3 max-md:w-full max-md:mt-3 max-md:overflow-x-auto max-md:pb-1">
<div <div
v-for="item in menuItems" class="flex items-center gap-1.5 px-4 py-2 rounded-lg cursor-pointer text-sm transition-all duration-200 max-md:px-3 max-md:py-2 max-md:text-xs"
:key="item.key" :class="navItemClass('TokenManage')"
class="nav-item" @click="handleSelect('TokenManage')"
:class="{ active: activeIndex === item.key }"
@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="flex items-center gap-1.5 px-4 py-2 rounded-lg cursor-pointer text-sm transition-all duration-200 max-md:px-3 max-md:py-2 max-md:text-xs"
:class="navItemClass('TokenDetail')"
@click="handleSelect('TokenDetail')"
>
<el-icon>
<Document/>
</el-icon>
<span>Token 详情</span>
</div> </div>
</nav> </nav>
<div class="header-right"> <div class="flex items-center">
<div v-if="store.isAdmin" class="admin-badge"> <el-button v-if="_isAdmin" circle link
<el-icon> @click="handleSelect('AdminSettings')"
<User/>
</el-icon>
<span>管理员</span>
</div>
<el-button
v-if="store.isAdmin"
type="danger"
plain
size="small"
@click="logout"
> >
<el-icon> <el-icon size="16">
<CloseBold/> <Setting/>
</el-icon> </el-icon>
退出
</el-button> </el-button>
</div> <el-divider direction="vertical"/>
</header> <el-dropdown :disabled="!_isAdmin">
<el-button :icon="User" link>{{ _userName }}</el-button>
<main class="admin-main"> <template #dropdown>
<router-view v-slot="{ Component }"> <el-dropdown-menu>
<transition name="fade" mode="out-in"> <el-dropdown-item @click="logout()">
<component :is="Component"/> <el-text type="danger">
</transition> <el-icon>
<CloseBold/>
</el-icon>
退出登录
</el-text>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main>
<!-- 登录页面 -->
<div v-if="!_isAdmin" class="flex justify-center items-center min-h-[60vh]">
<div
class="w-full max-w-100 text-center py-12 px-10 bg-(--bg-card) rounded-2xl shadow-[0_4px_24px_rgba(0,0,0,0.08)]">
<div class="mb-6">
<el-icon size="48" color="#409eff">
<Lock/>
</el-icon>
</div>
<div class="text-2xl font-semibold text-(--el-text-color-primary) mb-2">管理员登录</div>
<div class="text-sm text-(--el-text-color-secondary) mb-8">请输入管理员密码访问管理后台</div>
<el-input
v-model="inputPassWord"
type="password"
placeholder="请输入管理员密码"
size="large"
show-password
class="mb-4"
@keyup.enter="checkPassword"
>
<template #prefix>
<el-icon>
<Lock/>
</el-icon>
</template>
</el-input>
<el-button type="primary" size="large" class="w-full mt-2" @click="checkPassword">
登录
</el-button>
</div>
</div>
<router-view v-else v-slot="{ Component }">
<component :is="Component"/>
</router-view> </router-view>
</main> </el-main>
</div> </div>
</template> </template>
<style scoped>
.admin-layout {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-page);
}
.admin-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 60px;
background: var(--bg-header);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 100;
}
.header-left {
display: flex;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
color: var(--primary-color);
font-weight: 600;
font-size: 18px;
}
.logo-text {
display: none;
}
@media (min-width: 768px) {
.logo-text {
display: inline;
}
}
.header-nav {
display: flex;
gap: 8px;
}
.nav-item {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
color: var(--text-regular);
font-size: 14px;
transition: all 0.2s;
}
.nav-item:hover {
background: var(--bg-page);
color: var(--text-primary);
}
.nav-item.active {
background: var(--primary-color);
color: #fff;
}
.header-right {
display: flex;
align-items: center;
gap: 12px;
}
.admin-badge {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--danger-color);
color: #fff;
border-radius: 20px;
font-size: 13px;
}
.admin-main {
flex: 1;
padding: 24px 48px;
max-width: 100%;
overflow-x: auto;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
@media (max-width: 768px) {
.admin-header {
padding: 0 16px;
flex-wrap: wrap;
height: auto;
padding: 12px 16px;
}
.header-nav {
order: 3;
width: 100%;
margin-top: 12px;
overflow-x: auto;
}
.nav-item {
padding: 8px 12px;
font-size: 13px;
}
.nav-item span {
display: none;
}
.admin-main {
padding: 16px;
}
}
</style>
+173 -225
View File
@@ -1,14 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, onUnmounted, ref} from 'vue' import api from "@/api"
import axios from "@/axios.ts" import {DataAnalysis, Document, Key, Plus, Search, Warning} from '@element-plus/icons-vue'
import {useRoute} from "vue-router"
import {ElMessage} from "element-plus"
import {DataAnalysis, Document, Key, Search, Upload, Warning} from '@element-plus/icons-vue'
import AddDataDialog from "@/components/AddDataDialog.vue";
const route = useRoute() const route = useRoute()
const token = ref(route.query.token as string || '') const inputToken = ref(route.query.token as string || '')
const input = ref(route.query.token as string || '')
const result = ref<any>(null) const result = ref<any>(null)
const loading = ref(false) const loading = ref(false)
const lastUpdate = ref('') const lastUpdate = ref('')
@@ -20,267 +15,220 @@ const updateTime = () => {
} }
const fetchInfo = async () => { const fetchInfo = async () => {
if (!input.value) return if (!inputToken.value) return
try { try {
const res = await axios.get('/api/token/info', { const res = await api.getTokenInfo({token: inputToken.value})
params: {token: input.value} result.value = res.data.data
})
result.value = res.data.result
token.value = input.value
updateTime() updateTime()
} catch { } catch {
result.value = null
ElMessage({message: 'Token输入错误', type: 'error', duration: 2000}) ElMessage({message: 'Token输入错误', type: 'error', duration: 2000})
} }
} }
const refresh = async () => { const refresh = async () => {
loading.value = true loading.value = true
await fetchInfo()
try {
const res = await api.getTokenInfo({token: inputToken.value})
result.value = res.data.data
updateTime()
ElMessage({message: '查询成功', type: 'success', duration: 1500})
} catch {
result.value = null
ElMessage({message: 'Token输入错误', type: 'error', duration: 2000})
}
loading.value = false loading.value = false
ElMessage({message: '查询成功', type: 'success', duration: 1500})
} }
const inputChange = () => { const inputChange = () => {
if (input.value) fetchInfo() if (inputToken.value) fetchInfo()
} }
let timer: number let timer: number
onMounted(() => { onMounted(() => {
if (token.value) fetchInfo() fetchInfo()
timer = window.setInterval(fetchInfo, 5000) timer = window.setInterval(fetchInfo, 5000)
}) })
onUnmounted(() => { onUnmounted(() => {
clearInterval(timer) clearInterval(timer)
}) })
const statCards = [
{label: '去重对象', key: 'dedup_object', icon: DataAnalysis, color: '#409eff'},
{label: '上传数据格式', key: 'data_format', icon: Document, color: '#67c23a'},
{label: '去重记录值', key: 'dedup_items_number', icon: Key, color: '#e6a23c'},
{label: 'Redis中数据条数', key: 'cache_list_number', icon: Warning, color: '#909399'},
]
</script> </script>
<template> <template>
<div class="home-page"> <div class="p-6 h-dvh">
<div class="content-card" style="max-width: 1600px;"> <div class="bg-white rounded-lg p-6 shadow-[0_2px_12px_rgba(0,0,0,0.08)] h-full">
<div class="page-header"> <div class="text-center mb-4">
<div class="page-title">Token 信息查询</div> <div class="text-xl font-semibold text-(--el-text-color-primary) mb-5 pb-3 border-b border-(--el-border-color)">
<div class="header-subtitle">输入 Token 以查看去重和缓存信息</div> Token 信息查询
</div>
<div class="text-(--el-text-color-secondary) mt-2">输入 Token 以查看去重和缓存信息</div>
</div> </div>
<div class="search-box"> <div class="flex justify-center mb-8">
<el-input <el-input placeholder="请输入要查询数据的Token..." size="large" clearable
v-model="input" class="max-w-120"
placeholder="输入 Token" v-model="inputToken" @change="inputChange"
clearable
size="large"
class="token-input"
@change="inputChange"
> >
<template #prefix> <template #prepend>
<el-icon> Token
<Search/>
</el-icon>
</template> </template>
</el-input> </el-input>
<el-button type="primary" size="large" @click="refresh" :loading="loading"> <el-button type="primary" size="large"
class="ml-3"
@click="refresh" :loading="loading" :icon="Search"
>
查询 查询
</el-button> </el-button>
<el-button type="success" size="large" @click="showAddDataDialog = true">
<el-icon>
<Upload/>
</el-icon>
增加数据
</el-button>
</div> </div>
<AddDataDialog v-model="showAddDataDialog" :token="token"/> <el-space direction="vertical"
class="w-full animate-fade-in"
v-if="result"
>
<el-card class="p-6">
<template #header>
<el-text>Token 信息</el-text>
<el-divider direction="vertical"/>
<el-tag type="primary" effect="plain">{{ inputToken }}</el-tag>
</template>
<div v-if="result" class="result-section"> <el-space direction="vertical">
<div class="result-header"> <el-space>
<span class="section-title">Token 信息</span> <div
<span class="auto-refresh">每5秒自动刷新</span> class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
</div> hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
min-w-100"
>
<div
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
style="background: #409eff20; color: #409eff"
>
<el-icon size="24">
<DataAnalysis/>
</el-icon>
</div>
<div class="flex-1">
<div class="text-xs text-(--el-text-color-secondary) mb-1">
去重对象
</div>
<div
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
>
{{ result['dedup_object'] || '-' }}
</div>
</div>
</div>
<div
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
min-w-100"
>
<div
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
style="background: #67c23a20; color: #67c23a"
>
<el-icon size="24">
<Document/>
</el-icon>
</div>
<div class="flex-1">
<div class="text-xs text-(--el-text-color-secondary) mb-1">
上传数据格式
</div>
<div
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
>
{{ result['data_format'] || '-' }}
</div>
</div>
</div>
</el-space>
<el-space>
<div
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
min-w-100"
>
<div
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
style="background: #e6a23c20; color: #e6a23c"
>
<el-icon size="24">
<Key/>
</el-icon>
</div>
<div class="flex-1">
<div class="text-xs text-(--el-text-color-secondary) mb-1">
去重记录值
</div>
<div
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
>
{{ result['dedup_items_number'] || '-' }}
</div>
</div>
</div>
<div
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
min-w-100"
>
<div
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
style="background: #90939920; color: #909399"
>
<el-icon size="24">
<Warning/>
</el-icon>
</div>
<div class="flex-1">
<div class="text-xs text-(--el-text-color-secondary) mb-1">
Redis中数据条数
</div>
<div
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
>
{{ result['cache_list_number'] || '-' }}
</div>
</div>
</div>
</el-space>
</el-space>
<div class="stat-cards"> <el-text v-if="lastUpdate" type="info"
<div v-for="card in statCards" :key="card.key" class="stat-card"> class="block w-full mt-5 text-center"
<div class="stat-icon" :style="{ background: card.color + '20', color: card.color }"> >
<el-icon size="24"> 每5秒更新一次数据 最后更新: {{ lastUpdate }}
<component :is="card.icon"/> </el-text>
</el-icon>
</div>
<div class="stat-content">
<div class="stat-label">{{ card.label }}</div>
<div class="stat-value">{{ result[card.key] || '-' }}</div>
</div>
</div>
</div>
<div v-if="lastUpdate" class="update-time"> <template #footer>
最后更新: {{ lastUpdate }} <el-text>操作</el-text>
</div> <el-divider direction="vertical"/>
</div> <el-button type="primary" size=""
class=""
@click="showAddDataDialog = true" :icon="Plus"
>
添加数据
</el-button>
</template>
</el-card>
</el-space>
<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>
<AddDataDialog v-model="showAddDataDialog" :token="inputToken"/>
</template> </template>
<style scoped>
.home-page {
padding: 24px 48px;
max-width: 100%;
margin: 0 auto;
}
.page-header {
text-align: center;
margin-bottom: 32px;
}
.header-subtitle {
color: var(--text-secondary);
margin-top: 8px;
}
.search-box {
display: flex;
gap: 12px;
margin-bottom: 32px;
}
.token-input {
max-width: 400px;
}
.result-section {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.auto-refresh {
font-size: 12px;
color: var(--text-secondary);
}
.stat-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.stat-card {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
background: var(--bg-page);
border-radius: 12px;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.stat-icon {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-content {
flex: 1;
min-width: 0;
}
.stat-label {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.stat-value {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.update-time {
text-align: center;
margin-top: 20px;
font-size: 13px;
color: var(--text-secondary);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 64px;
color: var(--text-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>
+7
View File
@@ -0,0 +1,7 @@
<script setup lang="ts">
</script>
<template>
TODO
</template>
@@ -1,8 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, onUnmounted, ref, watch} from 'vue'
import {useCounterStore} from "@/stores/counter.ts" import {useCounterStore} from "@/stores/counter.ts"
import axios from "@/axios.ts" import api from "@/api"
import {ElMessage, ElMessageBox} from 'element-plus'
import {DataAnalysis, Delete, Document, InfoFilled, Key, Refresh, Search, Warning} from '@element-plus/icons-vue' import {DataAnalysis, Delete, Document, InfoFilled, Key, Refresh, Search, Warning} from '@element-plus/icons-vue'
const store = useCounterStore() const store = useCounterStore()
@@ -23,22 +21,18 @@ const isLoading = ref(false)
const fetchInfo = async () => { const fetchInfo = async () => {
if (!value.value) return if (!value.value) return
const res = await axios.get('/api/token/info', { const res = await api.getTokenInfo({token: value.value})
params: {
token: value.value
}
})
if (res.status === 200) { if (res.status === 200) {
result.value = res.data.result result.value = res.data.data
lastUpdate.value = new Date().toLocaleTimeString() lastUpdate.value = new Date().toLocaleTimeString()
} }
} }
const fetchTokens = async () => { const fetchTokens = async () => {
try { try {
const res = await axios.get('/api/token') const res = await api.getTokenList()
if (res.status === 200) { if (res.status === 200) {
options.value = res.data.result?.map((item: any) => item.token) || [] options.value = res.data.data?.map((item: any) => item.token) || []
} }
} catch (error) { } catch (error) {
console.error('Failed to fetch tokens:', error) console.error('Failed to fetch tokens:', error)
@@ -58,12 +52,7 @@ const deleteDedup = () => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}).then(async () => { }).then(async () => {
await axios.delete('/api/token/info', { await api.deleteTokenInfo({token: value.value, dedup_bf: "all"})
params: {
token: value.value,
dedup_bf: "all"
}
})
ElMessage({message: '删除成功', type: 'success'}) ElMessage({message: '删除成功', type: 'success'})
await fetchInfo() await fetchInfo()
}).catch(() => { }).catch(() => {
@@ -76,12 +65,7 @@ const deleteRedis = () => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}).then(async () => { }).then(async () => {
await axios.delete('/api/token/info', { await api.deleteTokenInfo({token: value.value, cache_list: "all"})
params: {
token: value.value,
cache_list: "all"
}
})
ElMessage({message: '删除成功', type: 'success'}) ElMessage({message: '删除成功', type: 'success'})
await fetchInfo() await fetchInfo()
}).catch(() => { }).catch(() => {
@@ -91,12 +75,7 @@ const deleteRedis = () => {
const deleteSpecifyDedup = async () => { const deleteSpecifyDedup = async () => {
isLoading.value = true isLoading.value = true
await axios.delete('/api/token/info', { await api.deleteTokenInfo({token: value.value, dedup_bf: inputSpecifyDedup.value})
params: {
token: value.value,
dedup_bf: inputSpecifyDedup.value
}
})
isLoading.value = false isLoading.value = false
ElMessage({message: '删除成功', type: 'success'}) ElMessage({message: '删除成功', type: 'success'})
@@ -109,12 +88,7 @@ const deleteSpecifyDedup = async () => {
const deleteSpecifyRaw = async () => { const deleteSpecifyRaw = async () => {
isLoading.value = true isLoading.value = true
await axios.delete('/api/token/info', { await api.deleteTokenInfo({token: value.value, cache_list: inputSpecifyRaw.value})
params: {
token: value.value,
cache_list: inputSpecifyRaw.value
}
})
isLoading.value = false isLoading.value = false
ElMessage({message: '删除成功', type: 'success'}) ElMessage({message: '删除成功', type: 'success'})
@@ -127,12 +101,7 @@ const deleteSpecifyRaw = async () => {
const deleteSpecifyData = async () => { const deleteSpecifyData = async () => {
isLoading.value = true isLoading.value = true
await axios.delete('/api/token/info', { await api.deleteTokenInfo({token: value.value, both_number: inputSpecifyData.value})
params: {
token: value.value,
both_number: inputSpecifyData.value
}
})
isLoading.value = false isLoading.value = false
ElMessage({message: '删除成功', type: 'success'}) ElMessage({message: '删除成功', type: 'success'})
@@ -174,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>
@@ -347,36 +305,11 @@ const statCards = [
<style scoped> <style scoped>
.token-detail { .token-detail {
max-width: 1600px;
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
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(--text-secondary);
}
/* Token 选择 */ /* Token 选择 */
.token-select { .token-select {
display: flex; display: flex;
@@ -388,7 +321,7 @@ const statCards = [
.update-time { .update-time {
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--el-text-color-secondary);
} }
/* 信息卡片 */ /* 信息卡片 */
@@ -440,14 +373,14 @@ const statCards = [
.stat-label { .stat-label {
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--el-text-color-secondary);
margin-bottom: 4px; margin-bottom: 4px;
} }
.stat-value { .stat-value {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--el-text-color-primary);
word-break: break-all; word-break: break-all;
} }
@@ -459,7 +392,7 @@ const statCards = [
.section-subtitle { .section-subtitle {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--text-secondary); color: var(--el-text-color-secondary);
margin: 24px 0 16px; margin: 24px 0 16px;
} }
@@ -491,12 +424,12 @@ const statCards = [
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--primary-color); background: var(--el-color-primary);
color: #fff; color: #fff;
} }
.action-icon.warning { .action-icon.warning {
background: var(--warning-color); background: var(--el-color-warning);
} }
.action-info { .action-info {
@@ -507,13 +440,13 @@ const statCards = [
.action-label { .action-label {
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--el-text-color-primary);
margin-bottom: 4px; margin-bottom: 4px;
} }
.action-desc { .action-desc {
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--el-text-color-secondary);
} }
/* 空状态 */ /* 空状态 */
@@ -522,7 +455,7 @@ const statCards = [
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 64px; padding: 64px;
color: var(--text-secondary); color: var(--el-text-color-secondary);
} }
.empty-text { .empty-text {
@@ -1,11 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, ref} from "vue" import api from "@/api"
import axios from "@/axios.ts"
import {ElMessage} from 'element-plus'
import {useCounterStore} from "@/stores/counter.ts" import {useCounterStore} from "@/stores/counter.ts"
import {useRouter} from "vue-router" import {Delete, Edit, Key, Memo, Plus, View} from '@element-plus/icons-vue'
import {Delete, Edit, Key, Lock, Memo, Plus, View} from '@element-plus/icons-vue'
import AddDataDialog from '@/components/AddDataDialog.vue'
const store = useCounterStore() const store = useCounterStore()
const router = useRouter() const router = useRouter()
@@ -16,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)
@@ -42,45 +37,30 @@ const dataFormatOptions = [
const fetchTokens = async () => { const fetchTokens = async () => {
loading.value = true loading.value = true
try { try {
const res = await axios.get("/api/token") const res = await api.getTokenList()
tableData.value = res.data.result || [] tableData.value = res.data.data || []
} finally { } finally {
loading.value = false loading.value = false
} }
} }
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('请填写完整信息')
return return
} }
try { try {
await axios.post('/api/token', {}, { await api.createToken({
params: { token: input.value,
token: input.value, dedup_object: value.value,
dedup_object: value.value, data_format: dataFormat.value,
data_format: dataFormat.value, notes: inputNotes.value,
notes: inputNotes.value,
}
}) })
ElMessage({message: '添加成功', type: 'success'}) ElMessage({message: '添加成功', type: 'success'})
input.value = '' input.value = ''
@@ -123,13 +103,11 @@ const dialogDataADDVisible = (row: any) => {
const updateDedupObject = async () => { const updateDedupObject = async () => {
try { try {
await axios.put('/api/token', {}, { await api.updateToken({
params: { token: rowOut.value.token,
token: rowOut.value.token, dedup_object: value.value,
dedup_object: value.value, data_format: rowOut.value.data_format,
data_format: rowOut.value.data_format, notes: rowOut.value.notes,
notes: rowOut.value.notes,
}
}) })
ElMessage({message: '更新成功', type: 'success'}) ElMessage({message: '更新成功', type: 'success'})
dedupObjectVisible.value = false dedupObjectVisible.value = false
@@ -141,13 +119,11 @@ const updateDedupObject = async () => {
const updateDataFormat = async () => { const updateDataFormat = async () => {
try { try {
await axios.put('/api/token', {}, { await api.updateToken({
params: { token: rowOut.value.token,
token: rowOut.value.token, dedup_object: rowOut.value.dedup_object,
dedup_object: rowOut.value.dedup_object, data_format: dataFormat.value,
data_format: dataFormat.value, notes: rowOut.value.notes,
notes: rowOut.value.notes,
}
}) })
ElMessage({message: '更新成功', type: 'success'}) ElMessage({message: '更新成功', type: 'success'})
dataFormatVisible.value = false dataFormatVisible.value = false
@@ -159,13 +135,11 @@ const updateDataFormat = async () => {
const updateNotes = async () => { const updateNotes = async () => {
try { try {
await axios.put('/api/token', {}, { await api.updateToken({
params: { token: rowOut.value.token,
token: rowOut.value.token, dedup_object: rowOut.value.dedup_object,
dedup_object: rowOut.value.dedup_object, data_format: rowOut.value.data_format,
data_format: rowOut.value.data_format, notes: inputNotes.value,
notes: inputNotes.value
}
}) })
ElMessage({message: '更新成功', type: 'success'}) ElMessage({message: '更新成功', type: 'success'})
inputNotesVisible.value = false inputNotesVisible.value = false
@@ -177,9 +151,7 @@ const updateNotes = async () => {
const deleteToken = async (row: any) => { const deleteToken = async (row: any) => {
try { try {
await axios.delete('/api/token', { await api.deleteToken({token: row.token})
params: {token: row.token}
})
ElMessage({message: '删除成功', type: 'success'}) ElMessage({message: '删除成功', type: 'success'})
fetchTokens() fetchTokens()
} catch (error: any) { } catch (error: any) {
@@ -190,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">
@@ -271,7 +213,8 @@ const deleteToken = async (row: any) => {
<span class="table-count"> {{ tableData.length }} </span> <span class="table-count"> {{ tableData.length }} </span>
</div> </div>
<el-table :data="tableData" v-loading="loading" stripe style="width: 100%"> <el-table empty-text="没有数据" stripe style="width: 100%"
:data="tableData" v-loading="loading">
<el-table-column prop="token" label="Token" min-width="100" show-overflow-tooltip> <el-table-column prop="token" label="Token" min-width="100" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<div class="token-cell"> <div class="token-cell">
@@ -395,56 +338,11 @@ const deleteToken = async (row: any) => {
<style scoped> <style scoped>
.token-manage { .token-manage {
max-width: 1600px;
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
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(--text-primary);
margin-bottom: 8px;
}
.login-subtitle {
font-size: 14px;
color: var(--text-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;
@@ -456,7 +354,7 @@ const deleteToken = async (row: any) => {
.form-title { .form-title {
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--el-text-color-primary);
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -489,7 +387,7 @@ const deleteToken = async (row: any) => {
.table-count { .table-count {
font-size: 13px; font-size: 13px;
color: var(--text-secondary); color: var(--el-text-color-secondary);
} }
.token-cell { .token-cell {
@@ -504,7 +402,7 @@ const deleteToken = async (row: any) => {
} }
.notes-text { .notes-text {
color: var(--text-secondary); color: var(--el-text-color-secondary);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
+3 -1
View File
@@ -3,7 +3,9 @@
"include": [ "include": [
"env.d.ts", "env.d.ts",
"src/**/*", "src/**/*",
"src/**/*.vue" "src/**/*.vue",
"auto-imports.d.ts",
"components.d.ts"
], ],
"exclude": [ "exclude": [
"src/**/__tests__/*" "src/**/__tests__/*"
+15
View File
@@ -3,12 +3,27 @@ import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite' import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {ElementPlusResolver} from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
vueDevTools(), vueDevTools(),
tailwindcss(),
AutoImport({
imports: [
'vue',
'vue-router',
],
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
], ],
resolve: { resolve: {
alias: { alias: {