Skip to content

Commit de134bd

Browse files
authored
feat: add log related simple views (#12)
1 parent 12baf9f commit de134bd

File tree

9 files changed

+566
-8
lines changed

9 files changed

+566
-8
lines changed

apps/web-antd/src/adapter/vxe-table.ts

Lines changed: 207 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
import type { Recordable } from '@vben/types';
2+
13
import { h } from 'vue';
24

5+
import { IconifyIcon } from '@vben/icons';
6+
import { $te } from '@vben/locales';
37
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
8+
import { get, isFunction, isString } from '@vben/utils';
9+
10+
import { objectOmit } from '@vueuse/core';
11+
import { Button, Image, Popconfirm, Switch, Tag } from 'ant-design-vue';
412

5-
import { Button, Image } from 'ant-design-vue';
13+
import { $t } from '#/locales';
614

715
import { useVbenForm } from './form';
816

@@ -15,7 +23,7 @@ setupVbenVxeTable({
1523
columnConfig: {
1624
resizable: true,
1725
},
18-
minHeight: 180,
26+
minHeight: 320,
1927
formConfig: {
2028
// 全局禁用vxe-table的表单配置,使用formOptions
2129
enabled: false,
@@ -36,6 +44,15 @@ setupVbenVxeTable({
3644
},
3745
});
3846

47+
/**
48+
* 解决vxeTable在热更新时可能会出错的问题
49+
*/
50+
vxeUI.renderer.forEach((_item, key) => {
51+
if (key.startsWith('Cell')) {
52+
vxeUI.renderer.delete(key);
53+
}
54+
});
55+
3956
// 表格配置项可以用 cellRender: { name: 'CellImage' },
4057
vxeUI.renderer.add('CellImage', {
4158
renderTableDefault(_renderOpts, params) {
@@ -56,6 +73,186 @@ setupVbenVxeTable({
5673
},
5774
});
5875

76+
// 单元格渲染:Tag
77+
vxeUI.renderer.add('CellTag', {
78+
renderTableDefault({ options, props }, { column, row }) {
79+
const value = get(row, column.field);
80+
const tagOptions = options ?? [
81+
{ color: 'success', label: $t('common.enabled'), value: 1 },
82+
{ color: 'error', label: $t('common.disabled'), value: 0 },
83+
];
84+
const tagItem = tagOptions.find((item) => item.value === value);
85+
return h(
86+
Tag,
87+
{
88+
...props,
89+
...objectOmit(tagItem ?? {}, ['label']),
90+
},
91+
{ default: () => tagItem?.label ?? value },
92+
);
93+
},
94+
});
95+
96+
vxeUI.renderer.add('CellSwitch', {
97+
renderTableDefault({ attrs, props }, { column, row }) {
98+
const loadingKey = `__loading_${column.field}`;
99+
const finallyProps = {
100+
checkedChildren: $t('common.enabled'),
101+
checkedValue: 1,
102+
unCheckedChildren: $t('common.disabled'),
103+
unCheckedValue: 0,
104+
...props,
105+
checked: row[column.field],
106+
loading: row[loadingKey] ?? false,
107+
'onUpdate:checked': onChange,
108+
};
109+
async function onChange(newVal: any) {
110+
row[loadingKey] = true;
111+
try {
112+
const result = await attrs?.beforeChange?.(newVal, row);
113+
if (result !== false) {
114+
row[column.field] = newVal;
115+
}
116+
} finally {
117+
row[loadingKey] = false;
118+
}
119+
}
120+
return h(Switch, finallyProps);
121+
},
122+
});
123+
124+
/**
125+
* 注册表格的操作按钮渲染器
126+
*/
127+
vxeUI.renderer.add('CellOperation', {
128+
renderTableDefault({ attrs, options, props }, { column, row }) {
129+
const defaultProps = { size: 'small', type: 'link', ...props };
130+
let align = 'end';
131+
switch (column.align) {
132+
case 'center': {
133+
align = 'center';
134+
break;
135+
}
136+
case 'left': {
137+
align = 'start';
138+
break;
139+
}
140+
default: {
141+
align = 'end';
142+
break;
143+
}
144+
}
145+
const presets: Recordable<Recordable<any>> = {
146+
delete: {
147+
danger: true,
148+
text: $t('common.delete'),
149+
},
150+
edit: {
151+
text: $t('common.edit'),
152+
},
153+
};
154+
const operations: Array<Recordable<any>> = (
155+
options || ['edit', 'delete']
156+
)
157+
.map((opt) => {
158+
if (isString(opt)) {
159+
return presets[opt]
160+
? { code: opt, ...presets[opt], ...defaultProps }
161+
: {
162+
code: opt,
163+
text: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt,
164+
...defaultProps,
165+
};
166+
} else {
167+
return { ...defaultProps, ...presets[opt.code], ...opt };
168+
}
169+
})
170+
.map((opt) => {
171+
const optBtn: Recordable<any> = {};
172+
Object.keys(opt).forEach((key) => {
173+
optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key];
174+
});
175+
return optBtn;
176+
})
177+
.filter((opt) => opt.show !== false);
178+
179+
function renderBtn(opt: Recordable<any>, listen = true) {
180+
return h(
181+
Button,
182+
{
183+
...props,
184+
...opt,
185+
icon: undefined,
186+
onClick: listen
187+
? () =>
188+
attrs?.onClick?.({
189+
code: opt.code,
190+
row,
191+
})
192+
: undefined,
193+
},
194+
{
195+
default: () => {
196+
const content = [];
197+
if (opt.icon) {
198+
content.push(
199+
h(IconifyIcon, { class: 'size-5', icon: opt.icon }),
200+
);
201+
}
202+
content.push(opt.text);
203+
return content;
204+
},
205+
},
206+
);
207+
}
208+
209+
function renderConfirm(opt: Recordable<any>) {
210+
return h(
211+
Popconfirm,
212+
{
213+
getPopupContainer(el) {
214+
return el.closest('tbody') || document.body;
215+
},
216+
placement: 'topLeft',
217+
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
218+
...props,
219+
...opt,
220+
icon: undefined,
221+
onConfirm: () => {
222+
attrs?.onClick?.({
223+
code: opt.code,
224+
row,
225+
});
226+
},
227+
},
228+
{
229+
default: () => renderBtn({ ...opt }, false),
230+
description: () =>
231+
h(
232+
'div',
233+
{ class: 'truncate' },
234+
$t('ui.actionMessage.deleteConfirm', [
235+
row[attrs?.nameField || 'name'],
236+
]),
237+
),
238+
},
239+
);
240+
}
241+
242+
const btns = operations.map((opt) =>
243+
opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt),
244+
);
245+
return h(
246+
'div',
247+
{
248+
class: 'flex table-operations',
249+
style: { justifyContent: align },
250+
},
251+
btns,
252+
);
253+
},
254+
});
255+
59256
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
60257
// vxeUI.formats.add
61258
},
@@ -64,4 +261,12 @@ setupVbenVxeTable({
64261

65262
export { useVbenVxeGrid };
66263

264+
export type OnActionClickParams<T = Recordable<any>> = {
265+
code: string;
266+
row: T;
267+
};
268+
269+
export type OnActionClickFn<T = Recordable<any>> = (
270+
params: OnActionClickParams<T>,
271+
) => void;
67272
export type * from '@vben/plugins/vxe-table';

apps/web-antd/src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './auth';
2+
export * from './log';
23
export * from './menu';
34
export * from './user';

apps/web-antd/src/api/log.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { PaginationResult } from '#/types';
2+
3+
import { requestClient } from './request';
4+
5+
export interface LoginLogParams {
6+
username?: string;
7+
status?: number;
8+
ip?: string;
9+
page?: number;
10+
size?: number;
11+
}
12+
13+
export interface LoginLogResult {
14+
id: number;
15+
username: string;
16+
status: 0 | 1;
17+
ip: string;
18+
country?: string;
19+
region?: string;
20+
os?: string;
21+
browser?: string;
22+
device?: string;
23+
msg: string;
24+
login_time: string;
25+
}
26+
27+
export type OperaLogParams = LoginLogParams;
28+
29+
export interface OperaLogResult {
30+
id: number;
31+
trace_id: string;
32+
username?: string;
33+
method: string;
34+
title: string;
35+
path: string;
36+
ip: string;
37+
country?: string;
38+
os?: string;
39+
browser?: string;
40+
device?: string;
41+
args?: JSON;
42+
status: 0 | 1;
43+
code: string;
44+
msg: string;
45+
cost_time: number;
46+
opera_time: string;
47+
}
48+
49+
export function getLoginLogListApi(params: LoginLogParams) {
50+
return requestClient.get<PaginationResult>('/api/v1/logs/login', { params });
51+
}
52+
53+
export function getOperaLogListApi(params: OperaLogParams) {
54+
return requestClient.get<PaginationResult>('/api/v1/logs/opera', { params });
55+
}

apps/web-antd/src/locales/langs/en-US/page.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
"title": "Dashboard",
1414
"workspace": "Workspace"
1515
},
16+
"form": {
17+
"query": "Query",
18+
"select": "Please select",
19+
"status": "Status"
20+
},
1621
"menu": {
1722
"admin": "System Admin",
1823
"automation": "System Auto",
@@ -29,5 +34,11 @@
2934
"sysMenu": "System User",
3035
"sysRole": "System Role",
3136
"sysUser": "System User"
37+
},
38+
"table": {
39+
"created_time": "Create Time",
40+
"id": "ID",
41+
"mark": "Mark",
42+
"operation": "Operation"
3243
}
3344
}

apps/web-antd/src/locales/langs/zh-CN/page.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
"title": "概览",
1414
"workspace": "工作台"
1515
},
16+
"form": {
17+
"query": "查询",
18+
"select": "请选择",
19+
"status": "状态"
20+
},
1621
"menu": {
1722
"admin": "系统管理",
1823
"automation": "系统自动化",
@@ -29,5 +34,11 @@
2934
"sysMenu": "系统菜单",
3035
"sysRole": "系统角色",
3136
"sysUser": "系统角色"
37+
},
38+
"table": {
39+
"created_time": "创建时间",
40+
"id": "序号",
41+
"mark": "备注",
42+
"operation": "操作"
3243
}
3344
}

apps/web-antd/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './pagination';

apps/web-antd/src/types/pagination.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
interface PaginationResult {
2+
items: Array<any>;
3+
page: number;
4+
size: number;
5+
total: number;
6+
total_pages: number;
7+
links: any;
8+
}
9+
10+
export type { PaginationResult };

0 commit comments

Comments
 (0)