befly-admin-ui 1.8.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +188 -0
- package/jsconfig.json +14 -0
- package/package.json +51 -0
- package/styles/variables.scss +148 -0
- package/utils/arrayToTree.js +115 -0
- package/utils/cleanParams.js +29 -0
- package/utils/fieldClear.js +62 -0
- package/utils/genShortId.js +12 -0
- package/utils/hashPassword.js +9 -0
- package/utils/scanViewsDir.js +120 -0
- package/utils/withDefaultColumns.js +46 -0
- package/views/config/dict/components/edit.vue +120 -0
- package/views/config/dict/index.vue +188 -0
- package/views/config/dictType/components/edit.vue +110 -0
- package/views/config/dictType/index.vue +153 -0
- package/views/config/index.vue +3 -0
- package/views/config/system/components/edit.vue +184 -0
- package/views/config/system/index.vue +188 -0
- package/views/index/components/addonList.vue +148 -0
- package/views/index/components/environmentInfo.vue +116 -0
- package/views/index/components/operationLogs.vue +127 -0
- package/views/index/components/performanceMetrics.vue +153 -0
- package/views/index/components/quickActions.vue +30 -0
- package/views/index/components/serviceStatus.vue +197 -0
- package/views/index/components/systemNotifications.vue +144 -0
- package/views/index/components/systemOverview.vue +194 -0
- package/views/index/components/systemResources.vue +121 -0
- package/views/index/components/userInfo.vue +210 -0
- package/views/index/index.vue +67 -0
- package/views/jsconfig.json +15 -0
- package/views/log/email/index.vue +221 -0
- package/views/log/index.vue +3 -0
- package/views/log/login/index.vue +95 -0
- package/views/log/operate/index.vue +169 -0
- package/views/login_1/index.vue +400 -0
- package/views/people/admin/components/edit.vue +173 -0
- package/views/people/admin/index.vue +121 -0
- package/views/people/index.vue +3 -0
- package/views/permission/api/index.vue +146 -0
- package/views/permission/index.vue +3 -0
- package/views/permission/menu/index.vue +109 -0
- package/views/permission/role/components/api.vue +371 -0
- package/views/permission/role/components/edit.vue +143 -0
- package/views/permission/role/components/menu.vue +310 -0
- package/views/permission/role/index.vue +175 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PagedTableDetail class="page-email page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['sendResult', 'sendTime']">
|
|
3
|
+
<template #toolLeft>
|
|
4
|
+
<TButton theme="primary" @click="openSendDialog">
|
|
5
|
+
<template #icon>
|
|
6
|
+
<SendIcon />
|
|
7
|
+
</template>
|
|
8
|
+
发送邮件
|
|
9
|
+
</TButton>
|
|
10
|
+
<TButton variant="outline" @click="onVerify">
|
|
11
|
+
<template #icon>
|
|
12
|
+
<CheckCircleIcon />
|
|
13
|
+
</template>
|
|
14
|
+
验证配置
|
|
15
|
+
</TButton>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<template #toolRight="scope">
|
|
19
|
+
<TButton shape="circle" @click="onReload(scope.reload)">
|
|
20
|
+
<template #icon>
|
|
21
|
+
<RefreshIcon />
|
|
22
|
+
</template>
|
|
23
|
+
</TButton>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<template #sendResult="{ row }">
|
|
27
|
+
<TTag v-if="row.sendResult === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
|
|
28
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<template #sendTime="{ row }">
|
|
32
|
+
{{ formatTime(row.sendTime) }}
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<template #detail="scope">
|
|
36
|
+
<DetailPanel :data="scope.row" :fields="$Data.columns">
|
|
37
|
+
<template #sendResult="slotScope">
|
|
38
|
+
<TTag v-if="slotScope.value === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
|
|
39
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
40
|
+
</template>
|
|
41
|
+
<template #sendTime="slotScope">
|
|
42
|
+
{{ formatTime(slotScope.value) }}
|
|
43
|
+
</template>
|
|
44
|
+
<template #content="slotScope">
|
|
45
|
+
<div class="email-content" v-html="slotScope.value"></div>
|
|
46
|
+
</template>
|
|
47
|
+
</DetailPanel>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<template #dialogs="scope">
|
|
51
|
+
<PageDialog v-model="$Data.sendDialogVisible" title="发送邮件" :confirm-loading="$Data.sending" @confirm="(context) => onSend(scope.reload, context)" @cancel="onCancelSend" @close="onCancelSend">
|
|
52
|
+
<TForm ref="sendFormRef" :data="$Data.sendForm" :rules="$Data.sendRules" label-width="80px">
|
|
53
|
+
<TFormItem label="收件人" name="to">
|
|
54
|
+
<TInput v-model="$Data.sendForm.to" placeholder="请输入收件人邮箱" />
|
|
55
|
+
</TFormItem>
|
|
56
|
+
<TFormItem label="抄送" name="cc">
|
|
57
|
+
<TInput v-model="$Data.sendForm.cc" placeholder="多个邮箱用逗号分隔(可选)" />
|
|
58
|
+
</TFormItem>
|
|
59
|
+
<TFormItem label="主题" name="subject">
|
|
60
|
+
<TInput v-model="$Data.sendForm.subject" placeholder="请输入邮件主题" />
|
|
61
|
+
</TFormItem>
|
|
62
|
+
<TFormItem label="内容" name="content">
|
|
63
|
+
<TTextarea v-model="$Data.sendForm.content" placeholder="请输入邮件内容(支持HTML)" :autosize="{ minRows: 6, maxRows: 12 }" />
|
|
64
|
+
</TFormItem>
|
|
65
|
+
</TForm>
|
|
66
|
+
</PageDialog>
|
|
67
|
+
</template>
|
|
68
|
+
</PagedTableDetail>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { reactive, ref } from "vue";
|
|
73
|
+
import { Button as TButton, Form as TForm, FormItem as TFormItem, Input as TInput, MessagePlugin, Space as TSpace, Tag as TTag, Textarea as TTextarea } from "tdesign-vue-next";
|
|
74
|
+
import { CheckCircleIcon, RefreshIcon, SendIcon } from "tdesign-icons-vue-next";
|
|
75
|
+
import PageDialog from "@/components/pageDialog.vue";
|
|
76
|
+
import DetailPanel from "@/components/detailPanel.vue";
|
|
77
|
+
import { $Http } from "@/plugins/http";
|
|
78
|
+
import PagedTableDetail from "@/components/pagedTableDetail.vue";
|
|
79
|
+
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
80
|
+
|
|
81
|
+
type TDesignFormInstance = {
|
|
82
|
+
validate: () => Promise<unknown>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const sendFormRef = ref<TDesignFormInstance | null>(null);
|
|
86
|
+
|
|
87
|
+
// 响应式数据
|
|
88
|
+
const $Data = reactive({
|
|
89
|
+
columns: withDefaultColumns([
|
|
90
|
+
{ colKey: "username", title: "发送人", fixed: "left" },
|
|
91
|
+
{ colKey: "toEmail", title: "收件人" },
|
|
92
|
+
{ colKey: "subject", title: "主题" },
|
|
93
|
+
{ colKey: "sendTime", title: "发送时间" },
|
|
94
|
+
{ colKey: "sendResult", title: "发送结果" },
|
|
95
|
+
{ colKey: "nickname", title: "发送人昵称", detail: true },
|
|
96
|
+
{ colKey: "ccEmail", title: "抄送", detail: true },
|
|
97
|
+
{ colKey: "bccEmail", title: "密送", detail: true },
|
|
98
|
+
{ colKey: "content", title: "内容", detail: true },
|
|
99
|
+
{ colKey: "messageId", title: "消息ID", detail: true },
|
|
100
|
+
{ colKey: "failReason", title: "失败原因", detail: true }
|
|
101
|
+
]),
|
|
102
|
+
endpoints: {
|
|
103
|
+
list: {
|
|
104
|
+
path: "/core/email/logList",
|
|
105
|
+
dropValues: [""]
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
sendDialogVisible: false,
|
|
109
|
+
sending: false,
|
|
110
|
+
sendForm: {
|
|
111
|
+
to: "",
|
|
112
|
+
cc: "",
|
|
113
|
+
subject: "",
|
|
114
|
+
content: ""
|
|
115
|
+
},
|
|
116
|
+
sendRules: {
|
|
117
|
+
to: [{ required: true, message: "请输入收件人邮箱", trigger: "blur" }],
|
|
118
|
+
subject: [{ required: true, message: "请输入邮件主题", trigger: "blur" }],
|
|
119
|
+
content: [{ required: true, message: "请输入邮件内容", trigger: "blur" }]
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
function onReload(reload: (options: { keepSelection?: boolean }) => void): void {
|
|
124
|
+
reload({ keepSelection: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function onCancelSend(): void {
|
|
128
|
+
$Data.sendDialogVisible = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function openSendDialog(): void {
|
|
132
|
+
$Data.sendForm = {
|
|
133
|
+
to: "",
|
|
134
|
+
cc: "",
|
|
135
|
+
subject: "",
|
|
136
|
+
content: ""
|
|
137
|
+
};
|
|
138
|
+
$Data.sendDialogVisible = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type PageDialogEventContext = {
|
|
142
|
+
close: () => void;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
async function onSend(reload: ((options: { keepSelection?: boolean; resetPage?: boolean }) => void) | null, context?: PageDialogEventContext): Promise<void> {
|
|
146
|
+
const form = sendFormRef.value;
|
|
147
|
+
if (!form) {
|
|
148
|
+
MessagePlugin.warning("表单未就绪");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const valid = await form.validate();
|
|
153
|
+
if (valid !== true) return;
|
|
154
|
+
|
|
155
|
+
$Data.sending = true;
|
|
156
|
+
try {
|
|
157
|
+
const res = await $Http.post("/core/email/send", {
|
|
158
|
+
to: $Data.sendForm.to,
|
|
159
|
+
subject: $Data.sendForm.subject,
|
|
160
|
+
content: $Data.sendForm.content,
|
|
161
|
+
cc: $Data.sendForm.cc || undefined,
|
|
162
|
+
isHtml: true
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (res.code === 0) {
|
|
166
|
+
MessagePlugin.success("发送成功");
|
|
167
|
+
if (context && typeof context.close === "function") {
|
|
168
|
+
context.close();
|
|
169
|
+
} else {
|
|
170
|
+
$Data.sendDialogVisible = false;
|
|
171
|
+
}
|
|
172
|
+
if (reload) {
|
|
173
|
+
reload({ keepSelection: false, resetPage: true });
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
MessagePlugin.error(res.msg || "发送失败");
|
|
177
|
+
}
|
|
178
|
+
} catch (_error) {
|
|
179
|
+
MessagePlugin.error("发送失败");
|
|
180
|
+
} finally {
|
|
181
|
+
$Data.sending = false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function onVerify(): Promise<void> {
|
|
186
|
+
try {
|
|
187
|
+
const res = await $Http.post("/core/email/verify");
|
|
188
|
+
if (res.code === 0) {
|
|
189
|
+
MessagePlugin.success("邮件服务配置正常");
|
|
190
|
+
} else {
|
|
191
|
+
MessagePlugin.error(res.msg || "配置异常");
|
|
192
|
+
}
|
|
193
|
+
} catch (_error) {
|
|
194
|
+
MessagePlugin.error("验证失败");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function formatTime(timestamp: unknown): string {
|
|
199
|
+
if (!timestamp) return "-";
|
|
200
|
+
const date = new Date(timestamp as never);
|
|
201
|
+
const year = date.getFullYear();
|
|
202
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
203
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
204
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
205
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
206
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
207
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
208
|
+
}
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<style scoped lang="scss">
|
|
212
|
+
.email-content {
|
|
213
|
+
max-height: 300px;
|
|
214
|
+
overflow-y: auto;
|
|
215
|
+
padding: 8px;
|
|
216
|
+
background: #f5f5f5;
|
|
217
|
+
border-radius: 4px;
|
|
218
|
+
font-size: 13px;
|
|
219
|
+
line-height: 1.6;
|
|
220
|
+
}
|
|
221
|
+
</style>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PagedTableDetail class="page-login-log page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['loginResult', 'loginTime', 'deviceType']">
|
|
3
|
+
<template #toolRight="scope">
|
|
4
|
+
<TButton shape="circle" @click="onReload(scope.reload)">
|
|
5
|
+
<template #icon>
|
|
6
|
+
<RefreshIcon />
|
|
7
|
+
</template>
|
|
8
|
+
</TButton>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<template #loginResult="{ row }">
|
|
12
|
+
<TTag v-if="row.loginResult === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
|
|
13
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template #loginTime="{ row }">
|
|
17
|
+
{{ formatTime(row.loginTime) }}
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<template #deviceType="{ row }">
|
|
21
|
+
<TTag shape="round" variant="light-outline">{{ row.deviceType || "desktop" }}</TTag>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<template #detail="scope">
|
|
25
|
+
<DetailPanel :data="scope.row" :fields="$Data.columns">
|
|
26
|
+
<template #loginResult="slotScope">
|
|
27
|
+
<TTag v-if="slotScope.value === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
|
|
28
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
29
|
+
</template>
|
|
30
|
+
<template #loginTime="slotScope">
|
|
31
|
+
{{ formatTime(slotScope.value) }}
|
|
32
|
+
</template>
|
|
33
|
+
<template #deviceType="slotScope">
|
|
34
|
+
<TTag shape="round" variant="light-outline">{{ slotScope.value || "desktop" }}</TTag>
|
|
35
|
+
</template>
|
|
36
|
+
</DetailPanel>
|
|
37
|
+
</template>
|
|
38
|
+
</PagedTableDetail>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import { reactive } from "vue";
|
|
43
|
+
import { Button as TButton, Tag as TTag } from "tdesign-vue-next";
|
|
44
|
+
import { RefreshIcon } from "tdesign-icons-vue-next";
|
|
45
|
+
import DetailPanel from "@/components/detailPanel.vue";
|
|
46
|
+
import PagedTableDetail from "@/components/pagedTableDetail.vue";
|
|
47
|
+
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
48
|
+
|
|
49
|
+
// 响应式数据
|
|
50
|
+
const $Data = reactive({
|
|
51
|
+
columns: withDefaultColumns([
|
|
52
|
+
{ colKey: "username", title: "用户名", fixed: "left" },
|
|
53
|
+
{ colKey: "ip", title: "登录IP" },
|
|
54
|
+
{ colKey: "browserName", title: "浏览器" },
|
|
55
|
+
{ colKey: "osName", title: "操作系统" },
|
|
56
|
+
{ colKey: "deviceType", title: "设备类型" },
|
|
57
|
+
{ colKey: "loginTime", title: "登录时间" },
|
|
58
|
+
{ colKey: "loginResult", title: "登录结果" },
|
|
59
|
+
{ colKey: "nickname", title: "昵称", detail: true },
|
|
60
|
+
{ colKey: "browserVersion", title: "浏览器版本", detail: true },
|
|
61
|
+
{ colKey: "osVersion", title: "系统版本", detail: true },
|
|
62
|
+
{ colKey: "deviceVendor", title: "设备厂商", detail: true },
|
|
63
|
+
{ colKey: "deviceModel", title: "设备型号", detail: true },
|
|
64
|
+
{ colKey: "engineName", title: "渲染引擎", detail: true },
|
|
65
|
+
{ colKey: "cpuArchitecture", title: "CPU架构", detail: true },
|
|
66
|
+
{ colKey: "failReason", title: "失败原因", detail: true }
|
|
67
|
+
]),
|
|
68
|
+
endpoints: {
|
|
69
|
+
list: {
|
|
70
|
+
path: "/core/loginLog/list",
|
|
71
|
+
dropValues: [""]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function onReload(reload: (options: { keepSelection?: boolean }) => void): void {
|
|
77
|
+
reload({ keepSelection: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatTime(timestamp: unknown): string {
|
|
81
|
+
if (!timestamp) return "-";
|
|
82
|
+
const date = new Date(timestamp as never);
|
|
83
|
+
const year = date.getFullYear();
|
|
84
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
85
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
86
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
87
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
88
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
89
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style scoped lang="scss">
|
|
94
|
+
// 样式继承自全局 page-table
|
|
95
|
+
</style>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PagedTableDetail class="page-operate-log page-table" :columns="$Data.columns" :endpoints="$Data.endpoints" :table-slot-names="['result', 'operateTime', 'duration', 'action']">
|
|
3
|
+
<template #toolLeft="scope">
|
|
4
|
+
<TSelect v-model="$Data.filter.module" placeholder="操作模块" clearable style="width: 150px" @change="handleFilter(scope.reload)">
|
|
5
|
+
<TOption v-for="item in $Data.moduleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
6
|
+
</TSelect>
|
|
7
|
+
<TSelect v-model="$Data.filter.action" placeholder="操作类型" clearable style="width: 150px" @change="handleFilter(scope.reload)">
|
|
8
|
+
<TOption v-for="item in $Data.actionOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
9
|
+
</TSelect>
|
|
10
|
+
<TSelect v-model="$Data.filter.result" placeholder="操作结果" clearable style="width: 120px" @change="handleFilter(scope.reload)">
|
|
11
|
+
<TOption label="成功" :value="1" />
|
|
12
|
+
<TOption label="失败" :value="0" />
|
|
13
|
+
</TSelect>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template #toolRight="scope">
|
|
17
|
+
<TButton shape="circle" @click="onReload(scope.reload)">
|
|
18
|
+
<template #icon>
|
|
19
|
+
<RefreshIcon />
|
|
20
|
+
</template>
|
|
21
|
+
</TButton>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<template #result="{ row }">
|
|
25
|
+
<TTag v-if="row.result === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
|
|
26
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<template #operateTime="{ row }">
|
|
30
|
+
{{ formatTime(row.operateTime) }}
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template #duration="{ row }">
|
|
34
|
+
<TTag shape="round" :theme="row.duration > 1000 ? 'warning' : 'default'" variant="light-outline">{{ row.duration }}ms</TTag>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<template #action="{ row }">
|
|
38
|
+
<TTag shape="round" variant="light-outline">{{ row.action }}</TTag>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<template #detail="scope">
|
|
42
|
+
<DetailPanel :data="scope.row" :fields="$Data.columns">
|
|
43
|
+
<template #result="slotScope">
|
|
44
|
+
<TTag v-if="slotScope.value === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
|
|
45
|
+
<TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
|
|
46
|
+
</template>
|
|
47
|
+
<template #operateTime="slotScope">
|
|
48
|
+
{{ formatTime(slotScope.value) }}
|
|
49
|
+
</template>
|
|
50
|
+
<template #duration="slotScope">
|
|
51
|
+
<TTag shape="round" :theme="slotScope.value > 1000 ? 'warning' : 'default'" variant="light-outline">{{ slotScope.value }}ms</TTag>
|
|
52
|
+
</template>
|
|
53
|
+
<template #params="slotScope">
|
|
54
|
+
<pre class="json-content">{{ formatJson(slotScope.value) }}</pre>
|
|
55
|
+
</template>
|
|
56
|
+
<template #response="slotScope">
|
|
57
|
+
<pre class="json-content">{{ formatJson(slotScope.value) }}</pre>
|
|
58
|
+
</template>
|
|
59
|
+
</DetailPanel>
|
|
60
|
+
</template>
|
|
61
|
+
</PagedTableDetail>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup lang="ts">
|
|
65
|
+
import { reactive } from "vue";
|
|
66
|
+
import { Button as TButton, Option as TOption, Select as TSelect, Tag as TTag } from "tdesign-vue-next";
|
|
67
|
+
import { RefreshIcon } from "tdesign-icons-vue-next";
|
|
68
|
+
import DetailPanel from "@/components/detailPanel.vue";
|
|
69
|
+
import PagedTableDetail from "@/components/pagedTableDetail.vue";
|
|
70
|
+
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
71
|
+
|
|
72
|
+
// 响应式数据
|
|
73
|
+
const $Data = reactive({
|
|
74
|
+
columns: withDefaultColumns([
|
|
75
|
+
{ colKey: "username", title: "操作人", fixed: "left", width: 100 },
|
|
76
|
+
{ colKey: "module", title: "模块", width: 100 },
|
|
77
|
+
{ colKey: "action", title: "操作", width: 80 },
|
|
78
|
+
{ colKey: "path", title: "请求路径", ellipsis: true },
|
|
79
|
+
{ colKey: "ip", title: "IP地址", width: 130 },
|
|
80
|
+
{ colKey: "duration", title: "耗时", width: 100 },
|
|
81
|
+
{ colKey: "operateTime", title: "操作时间", width: 170 },
|
|
82
|
+
{ colKey: "result", title: "结果", width: 80 },
|
|
83
|
+
{ colKey: "nickname", title: "操作人昵称", detail: true },
|
|
84
|
+
{ colKey: "method", title: "请求方法", detail: true },
|
|
85
|
+
{ colKey: "params", title: "请求参数", detail: true },
|
|
86
|
+
{ colKey: "response", title: "响应内容", detail: true },
|
|
87
|
+
{ colKey: "remark", title: "备注", detail: true }
|
|
88
|
+
]),
|
|
89
|
+
endpoints: {
|
|
90
|
+
list: {
|
|
91
|
+
path: "/core/operateLog/list",
|
|
92
|
+
dropValues: [""],
|
|
93
|
+
dropKeyValue: {
|
|
94
|
+
module: [""],
|
|
95
|
+
action: [""]
|
|
96
|
+
},
|
|
97
|
+
buildData: () => {
|
|
98
|
+
return {
|
|
99
|
+
module: $Data.filter.module,
|
|
100
|
+
action: $Data.filter.action,
|
|
101
|
+
result: $Data.filter.result
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
filter: {
|
|
107
|
+
module: "",
|
|
108
|
+
action: "",
|
|
109
|
+
result: null
|
|
110
|
+
},
|
|
111
|
+
moduleOptions: [
|
|
112
|
+
{ label: "管理员", value: "管理员" },
|
|
113
|
+
{ label: "角色", value: "角色" },
|
|
114
|
+
{ label: "菜单", value: "菜单" },
|
|
115
|
+
{ label: "接口", value: "接口" },
|
|
116
|
+
{ label: "字典", value: "字典" }
|
|
117
|
+
],
|
|
118
|
+
actionOptions: [
|
|
119
|
+
{ label: "新增", value: "新增" },
|
|
120
|
+
{ label: "编辑", value: "编辑" },
|
|
121
|
+
{ label: "删除", value: "删除" },
|
|
122
|
+
{ label: "查询", value: "查询" }
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
function handleFilter(reload: (options: { keepSelection?: boolean; resetPage?: boolean }) => void): void {
|
|
127
|
+
reload({ keepSelection: false, resetPage: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function onReload(reload: (options: { keepSelection?: boolean }) => void): void {
|
|
131
|
+
reload({ keepSelection: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatTime(timestamp: unknown): string {
|
|
135
|
+
if (!timestamp) return "-";
|
|
136
|
+
const date = new Date(timestamp as never);
|
|
137
|
+
const year = date.getFullYear();
|
|
138
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
139
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
140
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
141
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
142
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
143
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function formatJson(value: unknown): string {
|
|
147
|
+
if (!value) return "-";
|
|
148
|
+
try {
|
|
149
|
+
const obj = typeof value === "string" ? JSON.parse(value) : value;
|
|
150
|
+
return JSON.stringify(obj, null, 2);
|
|
151
|
+
} catch {
|
|
152
|
+
return String(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
<style scoped lang="scss">
|
|
158
|
+
.json-content {
|
|
159
|
+
margin: 0;
|
|
160
|
+
padding: 8px;
|
|
161
|
+
background: var(--td-bg-color-container);
|
|
162
|
+
border-radius: 4px;
|
|
163
|
+
font-size: 12px;
|
|
164
|
+
max-height: 200px;
|
|
165
|
+
overflow: auto;
|
|
166
|
+
white-space: pre-wrap;
|
|
167
|
+
word-break: break-all;
|
|
168
|
+
}
|
|
169
|
+
</style>
|