mirror of
https://github.com/lzh-1625/go_process_manager.git
synced 2025-12-24 12:57:52 +08:00
add event page
This commit is contained in:
16
boot/boot.go
16
boot/boot.go
@@ -41,16 +41,26 @@ func initDb() {
|
||||
func initConfiguration() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
panic("config init fail")
|
||||
log.Panic("config init fail", err)
|
||||
}
|
||||
}()
|
||||
configKvMap := map[string]string{}
|
||||
|
||||
data, err := repository.ConfigRepository.GetAllConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range data {
|
||||
configKvMap[v.Key] = *v.Value
|
||||
}
|
||||
|
||||
typeElem := reflect.TypeOf(config.CF).Elem()
|
||||
valueElem := reflect.ValueOf(config.CF).Elem()
|
||||
for i := 0; i < typeElem.NumField(); i++ {
|
||||
typeField := typeElem.Field(i)
|
||||
valueField := valueElem.Field(i)
|
||||
value, err := repository.ConfigRepository.GetConfigValue(typeField.Name)
|
||||
if err != nil {
|
||||
value, ok := configKvMap[typeField.Name]
|
||||
if !ok {
|
||||
value = typeField.Tag.Get("default")
|
||||
}
|
||||
if value == "-" {
|
||||
|
||||
22
internal/app/api/event.go
Normal file
22
internal/app/api/event.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lzh-1625/go_process_manager/internal/app/logic"
|
||||
"github.com/lzh-1625/go_process_manager/internal/app/model"
|
||||
)
|
||||
|
||||
type eventApi struct{}
|
||||
|
||||
var EventApi = new(eventApi)
|
||||
|
||||
func (e *eventApi) GetEventList(ctx *gin.Context, req model.EventListReq) any {
|
||||
data, total, err := logic.EventLogic.Get(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return model.EventListResp{
|
||||
Total: total,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
@@ -33,3 +33,7 @@ func (e *eventLogic) Create(name string, eventType eum.EventType, additionalKv .
|
||||
log.Logger.Errorw("事件创建失败", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *eventLogic) Get(req model.EventListReq) ([]*model.Event, int64, error) {
|
||||
return repository.EventRepository.GetList(req)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package logic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -35,12 +37,14 @@ func NewTaskJob(data model.Task) (*TaskJob, error) {
|
||||
return tj, nil
|
||||
}
|
||||
|
||||
func (t *TaskJob) Run(ctx context.Context) {
|
||||
func (t *TaskJob) Run(ctx context.Context) (err error) {
|
||||
if ctx.Value(eum.CtxTaskTraceId{}) == nil {
|
||||
ctx = context.WithValue(ctx, eum.CtxTaskTraceId{}, uuid.NewString())
|
||||
}
|
||||
EventLogic.Create(t.TaskConfig.Name, eum.EventTaskStart, "traceId", ctx.Value(eum.CtxTaskTraceId{}).(string))
|
||||
defer EventLogic.Create(t.TaskConfig.Name, eum.EventTaskStop, "traceId", ctx.Value(eum.CtxTaskTraceId{}).(string))
|
||||
defer func() {
|
||||
EventLogic.Create(t.TaskConfig.Name, eum.EventTaskStop, "traceId", ctx.Value(eum.CtxTaskTraceId{}).(string), "success", strconv.FormatBool(err == nil))
|
||||
}()
|
||||
t.Running = true
|
||||
middle.TaskWaitCond.Trigger()
|
||||
defer func() {
|
||||
@@ -48,11 +52,12 @@ func (t *TaskJob) Run(ctx context.Context) {
|
||||
middle.TaskWaitCond.Trigger()
|
||||
}()
|
||||
var ok bool
|
||||
var proc *ProcessBase
|
||||
// 判断条件是否满足
|
||||
if t.TaskConfig.Condition == eum.TaskCondPass {
|
||||
if t.TaskConfig.Condition == eum.TaskCondPass || t.TaskConfig.ProcessId == 0 {
|
||||
ok = true
|
||||
} else {
|
||||
proc, err := ProcessCtlLogic.GetProcess(t.TaskConfig.OperationTarget)
|
||||
proc, err = ProcessCtlLogic.GetProcess(t.TaskConfig.OperationTarget)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -63,7 +68,7 @@ func (t *TaskJob) Run(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
proc, err := ProcessCtlLogic.GetProcess(t.TaskConfig.OperationTarget)
|
||||
proc, err = ProcessCtlLogic.GetProcess(t.TaskConfig.OperationTarget)
|
||||
if err != nil {
|
||||
log.Logger.Debugw("不存在该进程,结束任务")
|
||||
return
|
||||
@@ -73,12 +78,14 @@ func (t *TaskJob) Run(ctx context.Context) {
|
||||
log.Logger.Infow("任务开始执行")
|
||||
if !OperationHandle[t.TaskConfig.Operation](t.TaskConfig, proc) {
|
||||
log.Logger.Warnw("任务执行失败")
|
||||
err = errors.New("task execute failed")
|
||||
return
|
||||
}
|
||||
log.Logger.Infow("任务执行成功", "target", t.TaskConfig.OperationTarget)
|
||||
|
||||
if t.TaskConfig.NextId != nil {
|
||||
nextTask, err := TaskLogic.getTaskJob(*t.TaskConfig.NextId)
|
||||
var nextTask *TaskJob
|
||||
nextTask, err = TaskLogic.getTaskJob(*t.TaskConfig.NextId)
|
||||
if err != nil {
|
||||
log.Logger.Errorw("无法获取到下一个节点,结束任务", "nextId", t.TaskConfig.NextId)
|
||||
return
|
||||
@@ -92,11 +99,12 @@ func (t *TaskJob) Run(ctx context.Context) {
|
||||
log.Logger.Errorw("下一个节点已在运行,结束任务", "nextId", t.TaskConfig.NextId)
|
||||
return
|
||||
}
|
||||
nextTask.Run(ctx)
|
||||
err = nextTask.Run(ctx)
|
||||
}
|
||||
} else {
|
||||
log.Logger.Infow("任务流结束")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TaskJob) InitCronHandle() error {
|
||||
|
||||
@@ -17,3 +17,17 @@ type Event struct {
|
||||
func (*Event) TableName() string {
|
||||
return "event"
|
||||
}
|
||||
|
||||
type EventListReq struct {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
StartTime int64 `form:"startTime"`
|
||||
EndTime int64 `form:"endTime"`
|
||||
Type eum.EventType `form:"type"`
|
||||
Name string `form:"name"`
|
||||
}
|
||||
|
||||
type EventListResp struct {
|
||||
Total int64 `json:"total"`
|
||||
Data []*Event `json:"data"`
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ func (c *configRepository) GetConfigValue(key string) (string, error) {
|
||||
return *data.Value, err
|
||||
}
|
||||
|
||||
func (c *configRepository) GetAllConfig() ([]*model.Config, error) {
|
||||
data, err := query.Config.Select(query.Config.Value).Find()
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *configRepository) SetConfigValue(key, value string) error {
|
||||
config := model.Config{Key: key}
|
||||
updateData := model.Config{Value: &value}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/lzh-1625/go_process_manager/internal/app/model"
|
||||
"github.com/lzh-1625/go_process_manager/internal/app/repository/query"
|
||||
)
|
||||
@@ -12,3 +15,21 @@ var EventRepository = new(eventRepository)
|
||||
func (e *eventRepository) Create(event model.Event) error {
|
||||
return query.Event.Create(&event)
|
||||
}
|
||||
|
||||
func (e *eventRepository) GetList(req model.EventListReq) ([]*model.Event, int64, error) {
|
||||
tx := query.Event.WithContext(context.TODO())
|
||||
|
||||
if req.StartTime != 0 {
|
||||
tx = tx.Where(query.Event.CreatedTime.Gte(time.Unix(req.StartTime, 0)))
|
||||
}
|
||||
if req.EndTime != 0 {
|
||||
tx = tx.Where(query.Event.CreatedTime.Lte(time.Unix(req.EndTime, 0)))
|
||||
}
|
||||
if req.Type != "" {
|
||||
tx = tx.Where(query.Event.Type.Eq(string(req.Type)))
|
||||
}
|
||||
if req.Name != "" {
|
||||
tx = tx.Where(query.Event.Name.Like("%" + req.Name + "%"))
|
||||
}
|
||||
return tx.Order(query.Event.CreatedTime.Desc()).FindByPage((req.Page-1)*req.Size, req.Size)
|
||||
}
|
||||
|
||||
@@ -122,6 +122,11 @@ func routePathInit(r *gin.Engine) {
|
||||
fileGroup.GET("", bind(api.FileApi.FileReadHandler, Query))
|
||||
}
|
||||
|
||||
eventGroup := apiGroup.Group("/event").Use(middle.RolePermission(eum.RoleAdmin))
|
||||
{
|
||||
eventGroup.GET("", bind(api.EventApi.GetEventList, Query))
|
||||
}
|
||||
|
||||
permissionGroup := apiGroup.Group("/permission").Use(middle.RolePermission(eum.RoleRoot))
|
||||
{
|
||||
permissionGroup.GET("/list", bind(api.PermissionApi.GetPermissionList, Query))
|
||||
@@ -138,7 +143,7 @@ func routePathInit(r *gin.Engine) {
|
||||
{
|
||||
configGroup.GET("", bind(api.ConfigApi.GetSystemConfiguration, None))
|
||||
configGroup.PUT("", bind(api.ConfigApi.SetSystemConfiguration, None))
|
||||
configGroup.GET("/reload", bind(api.ConfigApi.LogConfigReload, None))
|
||||
configGroup.PUT("/reload", bind(api.ConfigApi.LogConfigReload, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
resources/src/api/event.ts
Normal file
7
resources/src/api/event.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import api from "./api";
|
||||
import type { EventListReq, EventListResp } from "../types/event/event";
|
||||
|
||||
export function getEventList(params: EventListReq) {
|
||||
return api.get<EventListResp>("/event", params).then((res) => res);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const props = defineProps({
|
||||
<v-list nav dense>
|
||||
<template v-for="menuArea in props.menu" :key="menuArea.key">
|
||||
<div v-if="menuArea.key || menuArea.text" class="pa-1 mt-2 text-overline">
|
||||
{{ menuArea.text }}
|
||||
{{ menuArea.key ? $t(menuArea.key) : menuArea.text }}
|
||||
</div>
|
||||
<template v-if="menuArea.items">
|
||||
<template v-for="menuItem in menuArea.items" :key="menuItem.key">
|
||||
@@ -28,7 +28,7 @@ const props = defineProps({
|
||||
<Icon class="mx-2 mr-5" width="20" :icon="menuItem.icon" />
|
||||
</template>
|
||||
<v-list-item-title
|
||||
v-text="$t(menuItem.key)"
|
||||
v-text="menuItem.key ? $t(menuItem.key) : menuItem.text"
|
||||
class="font-weight-bold"
|
||||
></v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -40,7 +40,7 @@ const props = defineProps({
|
||||
<Icon class="mx-2 mr-5" width="20" :icon="menuItem.icon" />
|
||||
</template>
|
||||
<v-list-item-title
|
||||
v-text="menuItem.text"
|
||||
v-text="menuItem.key ? $t(menuItem.key) : menuItem.text"
|
||||
class="font-weight-bold"
|
||||
></v-list-item-title>
|
||||
</v-list-item>
|
||||
@@ -57,7 +57,7 @@ const props = defineProps({
|
||||
<Icon class="mx-2 mr-5" width="20" :icon="subMenuItem.icon" />
|
||||
</template>
|
||||
<v-list-item-title
|
||||
v-text="subMenuItem.text"
|
||||
v-text="subMenuItem.key ? $t(subMenuItem.key) : subMenuItem.text"
|
||||
class="font-weight-bold"
|
||||
></v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
@@ -5,7 +5,14 @@ export default [
|
||||
key: "menu.log",
|
||||
link: "/log",
|
||||
},
|
||||
{
|
||||
icon: "mdi-bell-ring",
|
||||
name: "event-page",
|
||||
key: "menu.event",
|
||||
link: "/event",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,5 +5,11 @@ export default [
|
||||
text: "系统设置",
|
||||
link: "/settings",
|
||||
},
|
||||
{
|
||||
icon: "mdi-bell-ring",
|
||||
key: "menu.push",
|
||||
text: "推送管理",
|
||||
link: "/settings/push",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -27,34 +27,42 @@ export default {
|
||||
},
|
||||
{
|
||||
text: "process",
|
||||
key: "menu.group.process",
|
||||
items: menuProcess,
|
||||
},
|
||||
{
|
||||
text: "task",
|
||||
key: "menu.group.task",
|
||||
items: menuTask,
|
||||
},
|
||||
{
|
||||
text: "log",
|
||||
key: "menu.group.log",
|
||||
items: menuLog,
|
||||
},
|
||||
{
|
||||
text: "user",
|
||||
key: "menu.group.user",
|
||||
items: menuUser,
|
||||
},
|
||||
{
|
||||
text: "settings",
|
||||
key: "menu.group.settings",
|
||||
items: menuSettings,
|
||||
},
|
||||
{
|
||||
text: "Apps",
|
||||
key: "menu.group.apps",
|
||||
items: menuApps,
|
||||
},
|
||||
{
|
||||
text: "Data",
|
||||
key: "menu.group.data",
|
||||
items: menuData,
|
||||
},
|
||||
{
|
||||
text: "Landing",
|
||||
key: "menu.group.landing",
|
||||
items: [
|
||||
...menuLanding,
|
||||
// {
|
||||
@@ -68,21 +76,22 @@ export default {
|
||||
|
||||
{
|
||||
text: "UI - Theme Preview",
|
||||
key: "menu.group.ui",
|
||||
items: menuUI,
|
||||
},
|
||||
{
|
||||
text: "Pages",
|
||||
key: "menu.pages",
|
||||
key: "menu.group.pages",
|
||||
items: menuPages,
|
||||
},
|
||||
{
|
||||
text: "Charts",
|
||||
key: "menu.charts",
|
||||
key: "menu.group.charts",
|
||||
items: menuCharts,
|
||||
},
|
||||
{
|
||||
text: "UML",
|
||||
// key: "menu.uml",
|
||||
key: "menu.group.uml",
|
||||
items: menuUML,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -28,7 +28,29 @@ export default {
|
||||
signin: "Sign In",
|
||||
},
|
||||
menu: {
|
||||
process: "ProcessManager",
|
||||
// Core feature menus
|
||||
process: "Process Manager",
|
||||
task: "Scheduled Tasks",
|
||||
log: "Log Viewer",
|
||||
event: "System Events",
|
||||
user: "User Management",
|
||||
settings: "Settings",
|
||||
push: "Push Management",
|
||||
// Group titles
|
||||
group: {
|
||||
process: "Process",
|
||||
task: "Task",
|
||||
log: "Log",
|
||||
user: "User",
|
||||
settings: "Settings",
|
||||
apps: "Apps",
|
||||
data: "Data",
|
||||
landing: "Landing",
|
||||
ui: "UI - Theme Preview",
|
||||
pages: "Pages",
|
||||
charts: "Charts",
|
||||
uml: "UML",
|
||||
},
|
||||
search: 'Search (press "ctrl + /" to focus)',
|
||||
dashboard: "Dashboard",
|
||||
logout: "Logout",
|
||||
|
||||
@@ -34,6 +34,29 @@ export default {
|
||||
signin: "サインイン",
|
||||
},
|
||||
menu: {
|
||||
// コア機能メニュー
|
||||
process: "プロセス管理",
|
||||
task: "スケジュールタスク",
|
||||
log: "ログビューア",
|
||||
event: "システムイベント",
|
||||
user: "ユーザー管理",
|
||||
settings: "システム設定",
|
||||
push: "プッシュ管理",
|
||||
// グループタイトル
|
||||
group: {
|
||||
process: "プロセス",
|
||||
task: "タスク",
|
||||
log: "ログ",
|
||||
user: "ユーザー",
|
||||
settings: "設定",
|
||||
apps: "アプリ",
|
||||
data: "データ",
|
||||
landing: "ランディング",
|
||||
ui: "UI - テーマプレビュー",
|
||||
pages: "ページ",
|
||||
charts: "チャート",
|
||||
uml: "UML",
|
||||
},
|
||||
search: "検索(フォーカスするには「ctrl + /」を押します)",
|
||||
dashboard: "ダッシュボード",
|
||||
logout: "ログアウト",
|
||||
|
||||
@@ -28,8 +28,29 @@ export default {
|
||||
signin: "登录",
|
||||
},
|
||||
menu: {
|
||||
// 核心功能菜单
|
||||
process: "进程管理",
|
||||
search: "搜索(按“ Ctrl + /”进行聚焦)",
|
||||
task: "定时任务",
|
||||
log: "日志查看",
|
||||
event: "系统事件",
|
||||
user: "用户管理",
|
||||
settings: "系统设置",
|
||||
push: "推送管理",
|
||||
// 分组标题
|
||||
group: {
|
||||
process: "进程",
|
||||
task: "任务",
|
||||
log: "日志",
|
||||
user: "用户",
|
||||
settings: "设置",
|
||||
apps: "应用",
|
||||
data: "数据",
|
||||
landing: "着陆页",
|
||||
ui: "UI - 主题预览",
|
||||
pages: "页面",
|
||||
charts: "图表",
|
||||
uml: "UML",
|
||||
},
|
||||
dashboard: "仪表板",
|
||||
logout: "登出",
|
||||
profile: "个人资料",
|
||||
|
||||
@@ -9,5 +9,14 @@ export default [
|
||||
category: "Data",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/event",
|
||||
component: () => import("@/views/log/Event.vue"),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
layout: "landing",
|
||||
category: "Data",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -9,5 +9,14 @@ export default [
|
||||
category: "Settings",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/settings/push",
|
||||
component: () => import("@/views/settings/Push.vue"),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
layout: "landing",
|
||||
category: "Settings",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
33
resources/src/types/event/event.ts
Normal file
33
resources/src/types/event/event.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// 事件类型枚举
|
||||
export type EventType =
|
||||
| "ProcessStart"
|
||||
| "ProcessStop"
|
||||
| "ProcessWarning"
|
||||
| "TaskStart"
|
||||
| "TaskStop";
|
||||
|
||||
// 事件模型
|
||||
export interface Event {
|
||||
id: number;
|
||||
name: string;
|
||||
type: EventType;
|
||||
additional: string;
|
||||
createdTime: string;
|
||||
}
|
||||
|
||||
// 事件列表请求参数
|
||||
export interface EventListReq {
|
||||
page?: number;
|
||||
size?: number;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
type?: EventType;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// 事件列表响应
|
||||
export interface EventListResp {
|
||||
total: number;
|
||||
data: Event[];
|
||||
}
|
||||
|
||||
375
resources/src/views/log/Event.vue
Normal file
375
resources/src/views/log/Event.vue
Normal file
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<v-container fluid class="py-6 px-8">
|
||||
<!-- 事件查看工具栏 -->
|
||||
<v-card class="mb-6 rounded-2xl elevation-3">
|
||||
<!-- 顶部标题和操作按钮 -->
|
||||
<div class="pa-4 d-flex align-center justify-space-between flex-wrap">
|
||||
<div class="d-flex align-center mb-2 mb-sm-0">
|
||||
<v-icon size="40" color="primary" class="mr-3">mdi-bell-ring</v-icon>
|
||||
<span class="text-h5 font-weight-bold text-primary">系统事件</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center ga-3 flex-wrap">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
class="rounded-lg px-4"
|
||||
@click="refreshEvents"
|
||||
:loading="loading"
|
||||
>
|
||||
<v-icon start>mdi-refresh</v-icon>
|
||||
刷新
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<v-expansion-panels flat>
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title>
|
||||
<v-icon start>mdi-filter</v-icon>
|
||||
筛选条件
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<v-container fluid>
|
||||
<v-row dense>
|
||||
<!-- 进程/任务名筛选 -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
label="名称"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
v-model="searchForm.name"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-tag"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 事件类型筛选 -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
label="事件类型"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
v-model="searchForm.type"
|
||||
:items="eventTypes"
|
||||
item-title="label"
|
||||
item-value="value"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-shape"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 开始时间 -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
label="开始时间"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
type="datetime-local"
|
||||
v-model="searchForm.startTime"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-calendar-start"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 结束时间 -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
label="结束时间"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
type="datetime-local"
|
||||
v-model="searchForm.endTime"
|
||||
clearable
|
||||
prepend-inner-icon="mdi-calendar-end"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<v-col cols="12" class="d-flex align-center ga-2">
|
||||
<v-btn color="primary" @click="searchEvents" :loading="loading">
|
||||
<v-icon start>mdi-magnify</v-icon>
|
||||
搜索
|
||||
</v-btn>
|
||||
<v-btn color="grey" variant="tonal" @click="resetSearch">
|
||||
<v-icon start>mdi-refresh</v-icon>
|
||||
重置
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card>
|
||||
|
||||
<!-- 事件列表 -->
|
||||
<v-card class="rounded-2xl elevation-2">
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="eventData"
|
||||
:loading="loading"
|
||||
:items-per-page="pageSize"
|
||||
item-key="id"
|
||||
class="text-body-2"
|
||||
density="comfortable"
|
||||
>
|
||||
<!-- 事件类型 -->
|
||||
<template #item.type="{ item }">
|
||||
<v-chip
|
||||
:color="getEventTypeColor(item.type)"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
>
|
||||
<v-icon start size="small">{{ getEventTypeIcon(item.type) }}</v-icon>
|
||||
{{ getEventTypeLabel(item.type) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<!-- 名称 -->
|
||||
<template #item.name="{ item }">
|
||||
<v-chip color="primary" size="small" variant="tonal">
|
||||
{{ item.name }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<!-- 附加信息 -->
|
||||
<template #item.additional="{ item }">
|
||||
<div v-if="item.additional" class="additional-info">
|
||||
<template v-for="(value, key) in parseAdditional(item.additional)" :key="key">
|
||||
<v-chip size="x-small" variant="outlined" class="mr-1 mb-1">
|
||||
{{ key }}: {{ value }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</div>
|
||||
<span v-else class="text-grey">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 时间 -->
|
||||
<template #item.createdTime="{ item }">
|
||||
<span class="text-caption">{{ formatTime(item.createdTime) }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 底部分页 -->
|
||||
<template #bottom>
|
||||
<div class="text-center pa-4">
|
||||
<v-pagination
|
||||
v-model="currentPage"
|
||||
:length="totalPages"
|
||||
:total-visible="7"
|
||||
@update:model-value="handlePageChange"
|
||||
></v-pagination>
|
||||
<div class="mt-2 text-caption text-grey">
|
||||
共 {{ totalEvents }} 条事件,每页 {{ pageSize }} 条
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { getEventList } from "~/src/api/event";
|
||||
import type { Event, EventListReq, EventType } from "~/src/types/event/event";
|
||||
import { useSnackbarStore } from "~/src/stores/snackbarStore";
|
||||
|
||||
const snackbarStore = useSnackbarStore();
|
||||
|
||||
// 事件类型选项
|
||||
const eventTypes = [
|
||||
{ label: "进程启动", value: "ProcessStart" },
|
||||
{ label: "进程停止", value: "ProcessStop" },
|
||||
{ label: "进程警告", value: "ProcessWarning" },
|
||||
{ label: "任务启动", value: "TaskStart" },
|
||||
{ label: "任务停止", value: "TaskStop" },
|
||||
];
|
||||
|
||||
// 表头定义
|
||||
const headers = [
|
||||
{ title: "事件类型", key: "type", width: "150px" },
|
||||
{ title: "名称", key: "name", width: "150px" },
|
||||
{ title: "附加信息", key: "additional", sortable: false },
|
||||
{ title: "时间", key: "createdTime", width: "180px" },
|
||||
];
|
||||
|
||||
// 数据
|
||||
const eventData = ref<Event[]>([]);
|
||||
const totalEvents = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const loading = ref(false);
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = ref({
|
||||
name: "",
|
||||
type: "" as EventType | "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
});
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(totalEvents.value / pageSize.value);
|
||||
});
|
||||
|
||||
// 获取事件类型颜色
|
||||
const getEventTypeColor = (type: EventType) => {
|
||||
const colorMap: Record<EventType, string> = {
|
||||
ProcessStart: "success",
|
||||
ProcessStop: "error",
|
||||
ProcessWarning: "warning",
|
||||
TaskStart: "info",
|
||||
TaskStop: "secondary",
|
||||
};
|
||||
return colorMap[type] || "grey";
|
||||
};
|
||||
|
||||
// 获取事件类型图标
|
||||
const getEventTypeIcon = (type: EventType) => {
|
||||
const iconMap: Record<EventType, string> = {
|
||||
ProcessStart: "mdi-play-circle",
|
||||
ProcessStop: "mdi-stop-circle",
|
||||
ProcessWarning: "mdi-alert-circle",
|
||||
TaskStart: "mdi-clock-start",
|
||||
TaskStop: "mdi-clock-end",
|
||||
};
|
||||
return iconMap[type] || "mdi-information";
|
||||
};
|
||||
|
||||
// 获取事件类型标签
|
||||
const getEventTypeLabel = (type: EventType) => {
|
||||
const labelMap: Record<EventType, string> = {
|
||||
ProcessStart: "进程启动",
|
||||
ProcessStop: "进程停止",
|
||||
ProcessWarning: "进程警告",
|
||||
TaskStart: "任务启动",
|
||||
TaskStop: "任务停止",
|
||||
};
|
||||
return labelMap[type] || type;
|
||||
};
|
||||
|
||||
// 解析附加信息
|
||||
const parseAdditional = (additional: string): Record<string, string> => {
|
||||
try {
|
||||
return JSON.parse(additional);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timestamp: string) => {
|
||||
if (!timestamp) return "-";
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
};
|
||||
|
||||
// 构建查询参数
|
||||
const buildQuery = (): EventListReq => {
|
||||
const query: EventListReq = {
|
||||
page: currentPage.value,
|
||||
size: pageSize.value,
|
||||
};
|
||||
|
||||
if (searchForm.value.name) {
|
||||
query.name = searchForm.value.name;
|
||||
}
|
||||
|
||||
if (searchForm.value.type) {
|
||||
query.type = searchForm.value.type as EventType;
|
||||
}
|
||||
|
||||
if (searchForm.value.startTime) {
|
||||
query.startTime = new Date(searchForm.value.startTime).getTime();
|
||||
}
|
||||
|
||||
if (searchForm.value.endTime) {
|
||||
query.endTime = new Date(searchForm.value.endTime).getTime();
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
// 加载事件
|
||||
const loadEvents = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const query = buildQuery();
|
||||
const response = await getEventList(query);
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
eventData.value = response.data.data || [];
|
||||
totalEvents.value = response.data.total || 0;
|
||||
} else {
|
||||
snackbarStore.showErrorMessage("加载事件失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载事件错误:", error);
|
||||
snackbarStore.showErrorMessage("加载事件出错");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索事件
|
||||
const searchEvents = () => {
|
||||
currentPage.value = 1;
|
||||
loadEvents();
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchForm.value = {
|
||||
name: "",
|
||||
type: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
};
|
||||
currentPage.value = 1;
|
||||
loadEvents();
|
||||
};
|
||||
|
||||
// 刷新事件
|
||||
const refreshEvents = () => {
|
||||
loadEvents();
|
||||
};
|
||||
|
||||
// 处理页码变化
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page;
|
||||
loadEvents();
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadEvents();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.additional-info {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
:deep(.v-data-table__th) {
|
||||
font-weight: 600 !important;
|
||||
font-size: 0.875rem !important;
|
||||
}
|
||||
|
||||
:deep(.v-data-table__td) {
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
430
resources/src/views/settings/Push.vue
Normal file
430
resources/src/views/settings/Push.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<v-container fluid class="py-6 px-8">
|
||||
<!-- 页面标题和操作按钮 -->
|
||||
<v-card class="mb-6 rounded-2xl elevation-3">
|
||||
<div class="pa-4 d-flex align-center justify-space-between flex-wrap">
|
||||
<div class="d-flex align-center mb-2 mb-sm-0">
|
||||
<v-icon size="40" color="primary" class="mr-3">mdi-bell-ring</v-icon>
|
||||
<span class="text-h5 font-weight-bold text-primary">推送管理</span>
|
||||
</div>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
class="rounded-lg px-4"
|
||||
@click="openAddDialog"
|
||||
>
|
||||
<v-icon start>mdi-plus</v-icon>
|
||||
新增推送
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<!-- 推送列表 -->
|
||||
<v-card class="rounded-2xl elevation-2" :loading="loading">
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="pushList"
|
||||
:loading="loading"
|
||||
item-key="id"
|
||||
class="text-body-2"
|
||||
density="comfortable"
|
||||
>
|
||||
<!-- HTTP方法列 -->
|
||||
<template #item.method="{ item }">
|
||||
<v-chip
|
||||
:color="getMethodColor(item.method)"
|
||||
size="small"
|
||||
variant="flat"
|
||||
class="font-weight-bold"
|
||||
>
|
||||
{{ item.method }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<!-- URL列 -->
|
||||
<template #item.url="{ item }">
|
||||
<div class="text-truncate" style="max-width: 300px;" :title="item.url">
|
||||
{{ item.url }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Body列 -->
|
||||
<template #item.body="{ item }">
|
||||
<div class="text-truncate" style="max-width: 200px;" :title="item.body">
|
||||
{{ item.body || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 备注列 -->
|
||||
<template #item.remark="{ item }">
|
||||
<div class="text-truncate" style="max-width: 150px;" :title="item.remark">
|
||||
{{ item.remark || '-' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 启用状态列 -->
|
||||
<template #item.enable="{ item }">
|
||||
<v-switch
|
||||
:model-value="item.enable"
|
||||
color="success"
|
||||
density="compact"
|
||||
hide-details
|
||||
@update:model-value="toggleEnable(item)"
|
||||
></v-switch>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #item.actions="{ item }">
|
||||
<div class="d-flex ga-1">
|
||||
<v-btn
|
||||
color="primary"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
icon
|
||||
@click="openEditDialog(item)"
|
||||
>
|
||||
<v-icon size="small">mdi-pencil</v-icon>
|
||||
<v-tooltip activator="parent" location="top">编辑</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
size="small"
|
||||
variant="tonal"
|
||||
icon
|
||||
@click="confirmDelete(item)"
|
||||
>
|
||||
<v-icon size="small">mdi-delete</v-icon>
|
||||
<v-tooltip activator="parent" location="top">删除</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 无数据提示 -->
|
||||
<template #no-data>
|
||||
<div class="text-center py-8">
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-4">mdi-bell-off</v-icon>
|
||||
<div class="text-h6 text-grey">暂无推送配置</div>
|
||||
<div class="text-body-2 text-grey-lighten-1 mb-4">点击上方按钮添加新的推送配置</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<v-dialog v-model="dialog" max-width="600" persistent>
|
||||
<v-card class="rounded-xl">
|
||||
<v-card-title class="d-flex align-center pa-4">
|
||||
<v-icon color="primary" class="mr-2">
|
||||
{{ isEdit ? 'mdi-pencil' : 'mdi-plus' }}
|
||||
</v-icon>
|
||||
{{ isEdit ? '编辑推送' : '新增推送' }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text class="pa-4">
|
||||
<v-form ref="formRef" v-model="formValid">
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="4">
|
||||
<v-select
|
||||
v-model="form.method"
|
||||
label="HTTP方法"
|
||||
:items="methodOptions"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[rules.required]"
|
||||
prepend-inner-icon="mdi-web"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8">
|
||||
<v-text-field
|
||||
v-model="form.url"
|
||||
label="推送URL"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[rules.required, rules.url]"
|
||||
prepend-inner-icon="mdi-link"
|
||||
placeholder="https://example.com/webhook"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="form.body"
|
||||
label="请求体 (Body)"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rows="4"
|
||||
prepend-inner-icon="mdi-code-json"
|
||||
placeholder='{"message": "{{.Message}}", "time": "{{.Time}}"}'
|
||||
hint="支持模板变量: {{.Message}}, {{.Time}}, {{.Level}} 等"
|
||||
persistent-hint
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="form.remark"
|
||||
label="备注"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
prepend-inner-icon="mdi-note-text"
|
||||
placeholder="推送配置描述"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-switch
|
||||
v-model="form.enable"
|
||||
label="启用推送"
|
||||
color="success"
|
||||
hide-details
|
||||
></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="tonal" @click="closeDialog">取消</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
@click="submitForm"
|
||||
:loading="submitLoading"
|
||||
:disabled="!formValid"
|
||||
>
|
||||
{{ isEdit ? '保存' : '创建' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<v-dialog v-model="deleteDialog" max-width="400">
|
||||
<v-card class="rounded-xl">
|
||||
<v-card-title class="d-flex align-center pa-4">
|
||||
<v-icon color="error" class="mr-2">mdi-alert-circle</v-icon>
|
||||
确认删除
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text class="pa-4">
|
||||
<p>确定要删除这个推送配置吗?</p>
|
||||
<p class="text-caption text-grey mt-2">
|
||||
备注: {{ deleteItem?.remark || '无备注' }}
|
||||
</p>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="tonal" @click="deleteDialog = false">取消</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
@click="handleDelete"
|
||||
:loading="deleteLoading"
|
||||
>
|
||||
删除
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { getPushList, createPush, editPush, deletePush } from "~/src/api/push";
|
||||
import type { PushItem } from "~/src/types/push/push";
|
||||
import { useSnackbarStore } from "~/src/stores/snackbarStore";
|
||||
|
||||
const snackbarStore = useSnackbarStore();
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false);
|
||||
const pushList = ref<PushItem[]>([]);
|
||||
|
||||
// 对话框状态
|
||||
const dialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const formRef = ref();
|
||||
const formValid = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 删除对话框状态
|
||||
const deleteDialog = ref(false);
|
||||
const deleteItem = ref<PushItem | null>(null);
|
||||
const deleteLoading = ref(false);
|
||||
|
||||
// HTTP方法选项
|
||||
const methodOptions = ["GET", "POST", "PUT", "DELETE"];
|
||||
|
||||
// 表单数据
|
||||
const defaultForm = {
|
||||
id: 0,
|
||||
method: "POST",
|
||||
url: "",
|
||||
body: "",
|
||||
remark: "",
|
||||
enable: true,
|
||||
};
|
||||
const form = ref({ ...defaultForm });
|
||||
|
||||
// 表格列定义
|
||||
const headers = [
|
||||
{ title: "HTTP方法", key: "method", width: "100px" },
|
||||
{ title: "推送URL", key: "url" },
|
||||
{ title: "请求体", key: "body" },
|
||||
{ title: "备注", key: "remark" },
|
||||
{ title: "启用", key: "enable", width: "80px" },
|
||||
{ title: "操作", key: "actions", width: "120px", sortable: false },
|
||||
];
|
||||
|
||||
// 验证规则
|
||||
const rules = {
|
||||
required: (v: string) => !!v || "必填项",
|
||||
url: (v: string) => {
|
||||
if (!v) return true;
|
||||
try {
|
||||
new URL(v);
|
||||
return true;
|
||||
} catch {
|
||||
return "请输入有效的URL";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 获取HTTP方法颜色
|
||||
const getMethodColor = (method: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
GET: "success",
|
||||
POST: "primary",
|
||||
PUT: "warning",
|
||||
DELETE: "error",
|
||||
};
|
||||
return colors[method] || "grey";
|
||||
};
|
||||
|
||||
// 加载推送列表
|
||||
const loadPushList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getPushList();
|
||||
if (res.code === 0 && res.data) {
|
||||
pushList.value = res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载推送列表失败:", error);
|
||||
snackbarStore.showErrorMessage("加载推送列表失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 打开新增对话框
|
||||
const openAddDialog = () => {
|
||||
isEdit.value = false;
|
||||
form.value = { ...defaultForm };
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
// 打开编辑对话框
|
||||
const openEditDialog = (item: PushItem) => {
|
||||
isEdit.value = true;
|
||||
form.value = { ...item };
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
// 关闭对话框
|
||||
const closeDialog = () => {
|
||||
dialog.value = false;
|
||||
form.value = { ...defaultForm };
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formValid.value) return;
|
||||
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
const data = {
|
||||
id: form.value.id,
|
||||
method: form.value.method,
|
||||
url: form.value.url,
|
||||
body: form.value.body,
|
||||
remark: form.value.remark,
|
||||
enable: form.value.enable,
|
||||
};
|
||||
|
||||
let res;
|
||||
if (isEdit.value) {
|
||||
res = await editPush(data);
|
||||
} else {
|
||||
res = await createPush(data);
|
||||
}
|
||||
|
||||
if (res.code === 0) {
|
||||
snackbarStore.showSuccessMessage(isEdit.value ? "保存成功" : "创建成功");
|
||||
closeDialog();
|
||||
loadPushList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("提交失败:", error);
|
||||
snackbarStore.showErrorMessage(isEdit.value ? "保存失败" : "创建失败");
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 切换启用状态
|
||||
const toggleEnable = async (item: PushItem) => {
|
||||
try {
|
||||
const res = await editPush({
|
||||
...item,
|
||||
enable: !item.enable,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
snackbarStore.showSuccessMessage(item.enable ? "已禁用" : "已启用");
|
||||
loadPushList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("切换状态失败:", error);
|
||||
snackbarStore.showErrorMessage("操作失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 确认删除
|
||||
const confirmDelete = (item: PushItem) => {
|
||||
deleteItem.value = item;
|
||||
deleteDialog.value = true;
|
||||
};
|
||||
|
||||
// 执行删除
|
||||
const handleDelete = async () => {
|
||||
if (!deleteItem.value) return;
|
||||
|
||||
deleteLoading.value = true;
|
||||
try {
|
||||
const res = await deletePush(deleteItem.value.id);
|
||||
if (res.code === 0) {
|
||||
snackbarStore.showSuccessMessage("删除成功");
|
||||
deleteDialog.value = false;
|
||||
deleteItem.value = null;
|
||||
loadPushList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("删除失败:", error);
|
||||
snackbarStore.showErrorMessage("删除失败");
|
||||
} finally {
|
||||
deleteLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadPushList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-data-table :deep(th) {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user